74일차 자료 추가(답 제외), 73일차 정답 공개(LAB-A · VIBE-A · answers · tscan), 64 · 65 · 68~73일차 교안 · 실습 문서 수정 반영

This commit is contained in:
mcchae committed 2026-10-27 23:16:10 +09:00
1 parent 4ef0464525
commit bb719d81fa
191 files changed
+36875 -64

No files matched your search

-1
View File
@@ -34,7 +34,6 @@ flowchart LR
| --- | --- |
| `00-DAY64-PREP.md` | 실습 준비: 환경 설치·확인, JSON 기초, 지난 실습 복습, 생각해 올 업무 시나리오 (수업 전) |
| `00-DAY64-OVERVIEW.md` | 개요 26장 (PPT 대신 Markdown, 가로줄로 장 구분): 개요(1~3), 1단계(4~12), 2단계(13~20), 3단계(21~24), 실습(25), 퀴즈·예고(26). 각 단계 설명 때 함께 띄움 |
| `ppt-src/` | (참고) 이전 PPT 생성 스크립트 — pptx 파일은 삭제, 필요하면 다시 만들 수 있음 |
| `01-STAGE1-AI-WORKFLOW-CONCEPT.md` | 노코드가 필요한 이유, AI 워크플로우 정의, 같은 업무를 세 방식으로 비교, 기본 용어와 실행 기록 읽기, DAG, 전체 아키텍처, 노드 설계 원칙, n8n 미리 보기 |
| `02-STAGE2-LLM-TOOL-MEMORY.md` | LLM 노드 설정값과 출력 형식, LLM의 한계, 로컬·클라우드와 대체 경로, Tool 설계·실패·검색 한계, Memory 설계, 상호작용 패턴 4가지, 표현식과 데이터 흐름 |
| `03-STAGE3-AUTOMATION-SCENARIO.md` | 자동화 후보 선정과 점수, AS-IS·측정 지표, TO-BE와 예외 경로, 단계 → 노드 매핑, 채운 분석 템플릿, 사람 확인 위치, 시나리오별 구현 범위, 흔한 분석 실수 |
+2 -1
View File
@@ -409,7 +409,8 @@ flowchart TB
- 마지막 주관식 '더 알고 싶은 것'은 선택 · 점수 없음
> **66일차 예고**
> 빈 캔버스에서 시작하는 **단일 업무 자동화 워크플로우**
> **CLI 코딩 에이전트**에게 요청문으로 만드는 **단일 업무 자동화 워크플로우** (회의록 → 할 일)
> 준비: CLI 코딩 에이전트(Codex · Claude Code · Antigravity) 하나 설치·로그인
> 복습: 어제 시나리오를 TCA 설계표로 정리
### *오늘의 한 문장: 언제 시작하고, 어느 길로 가고, 무엇을 하는지 — 그리고 모든 길에 끝을*
+4 -2
View File
@@ -6,7 +6,7 @@
```mermaid
flowchart LR
A["1. 요청 하나,<br/>업무 여럿"] --> B["2. 분석 다섯 칸"] --> C["3. 업무 관계"] --> D["4. 나누기는 LLM,<br/>판단은 규칙"] --> E["5. 분석표를<br/>요청문으로"]
A["1. 요청 하나,<br/>업무 여럿"] --> B["2. 분석 다섯 칸"] --> C["3. 업무 관계"] --> D["4. 나누기는 LLM,<br/>판단은 규칙"] --> E["5. 분석표를<br/>요청문(프롬프트)으로"]
```
## 1. 요청 하나에 업무가 여럿
@@ -80,7 +80,9 @@ flowchart LR
> 67일차의 "LLM이 정하면 안 되는 값"(요청한 사람)과 같은 생각입니다. **매번 같아야 하는 것은 규칙으로.**
## 5. 분석표를 요청문으로
## 5. 분석표를 요청문(프롬프트)으로
요청문(프롬프트)은 실습에서 CLI 코딩 에이전트에게 보내는 글입니다. 이 절은 이 단계에서 배운 것을 요청문에 어떻게 적을지 예로 보여 줍니다.
67일차 요청문 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)는 그대로입니다. 오늘은 ③ 처리에 **분석표의 기준**이 들어갑니다. Lab과 다른 업무(총무 요청)로 예를 듭니다.
+4 -2
View File
@@ -6,7 +6,7 @@
```mermaid
flowchart LR
A["1. 왜 역할로<br/>나누나"] --> B["2. 역할 카드"] --> C["3. 하지 않는 일"] --> D["4. 규칙 검토와<br/>LLM 검토"] --> E["5. 역할 카드를<br/>요청문으로"]
A["1. 왜 역할로<br/>나누나"] --> B["2. 역할 카드"] --> C["3. 하지 않는 일"] --> D["4. 규칙 검토와<br/>LLM 검토"] --> E["5. 역할 카드를<br/>요청문(프롬프트)으로"]
```
## 1. 왜 역할로 나누나
@@ -69,7 +69,9 @@ IT팀 공지에 있었던 사고 세 가지: 점검 **시간이 빠짐**, 담당
> LLM 검토자도 틀립니다. 테스트가 실패하면 **워크플로우 · 테스트 자료 · 기대 결과** 중 무엇이 틀렸는지부터 봅니다.
## 5. 역할 카드를 요청문으로
## 5. 역할 카드를 요청문(프롬프트)으로
요청문(프롬프트)은 실습에서 CLI 코딩 에이전트에게 보내는 글입니다. 이 절은 이 단계에서 배운 것을 요청문에 어떻게 적을지 예로 보여 줍니다.
요청문 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)에 **역할 카드**를 넣습니다. Lab과 다른 업무(신입 사원 환영 메일)로 예를 듭니다.
+4 -2
View File
@@ -6,7 +6,7 @@
```mermaid
flowchart LR
A["1. 복사의 비용"] --> B["2. 같은 것 · 다른 것 ·<br/>우연히 다른 것"] --> C["3. 설정 표"] --> D["4. 표에 없으면<br/>멈춤"] --> E["5. 요청문으로"]
A["1. 복사의 비용"] --> B["2. 같은 것 · 다른 것 ·<br/>우연히 다른 것"] --> C["3. 설정 표"] --> D["4. 표에 없으면<br/>멈춤"] --> E["5. 요청문(프롬프트)으로"]
```
## 1. 복사의 비용
@@ -66,7 +66,9 @@ flowchart LR
> 템플릿은 "값만 바꾸면 된다"가 장점이지만, **값이 없을 때**를 정하지 않으면 조용히 틀린 결과를 냅니다.
## 5. 요청문으로
## 5. 요청문(프롬프트)으로
요청문(프롬프트)은 실습에서 CLI 코딩 에이전트에게 보내는 글입니다. 이 절은 이 단계에서 배운 것을 요청문에 어떻게 적을지 예로 보여 줍니다.
Lab과 다른 업무(사내 교육 신청 확인 메일)로 예를 듭니다.
+5 -3
View File
@@ -7,7 +7,7 @@
```mermaid
flowchart LR
A["1. 왜 RAG인가"] --> B["2. 임베딩과<br/>가깝다는 것"] --> C["3. 정답표 먼저"] --> D["4. 청크 · 메타데이터"] --> E["5. 메모리 저장소"] --> F["6. 요청문으로"]
A["1. 왜 RAG인가"] --> B["2. 임베딩과<br/>가깝다는 것"] --> C["3. 정답표 먼저"] --> D["4. 청크 · 메타데이터"] --> E["5. 메모리 저장소"] --> F["6. 요청문(프롬프트)으로"]
```
| 절 | 말하는 시간 (예) | 띄울 것 |
@@ -17,7 +17,7 @@ flowchart LR
| 3. 정답표 먼저 | 7분 | 정답표 · 질문 종류 |
| 4. 청크 · 메타데이터 | 8분 | 규정 8개 표 · 청크 하나 |
| 5. 메모리 저장소 | 5분 | 한살이 그림 |
| 6. 요청문으로 · 확인 질문 | 5분 | 요청문 예 |
| 6. 요청문(프롬프트)으로 · 확인 질문 | 5분 | 요청문 예 |
## 1. 왜 RAG인가
@@ -286,7 +286,9 @@ flowchart LR
오늘 배운 흐름(청크 → 임베딩 → 저장 → 검색)은 그대로이고, 저장소 노드만 바뀝니다. 7교시 `tdoc`은 SQLite 파일에 저장해 "껐다 켜도 남게" 직접 만듭니다.
## 6. 요청문으로
## 6. 요청문(프롬프트)으로
요청문(프롬프트)은 실습에서 CLI 코딩 에이전트에게 보내는 글입니다. 이 절은 이 단계에서 배운 것을 요청문에 어떻게 적을지 예로 보여 줍니다.
Lab과 다른 문서(사내 교육 안내 문서)로 예를 듭니다.
+27 -2
View File
@@ -8,7 +8,7 @@
```mermaid
flowchart LR
A["1. 재기 전에<br/>고치지 않기"] --> B["2. 시간을 말하는 말<br/>p50 · p95 · max"] --> C["3. 첫 호출"] --> D["4. 어디서 재나"] --> E["5. 단계별 시간"]
E --> F["6. 종류별로 재기"] --> G["7. 어림 계산"] --> H["8. 가장 느린 단계"] --> I["9. 시간 예산"] --> J["10. 요청문으로"]
E --> F["6. 종류별로 재기"] --> G["7. 어림 계산"] --> H["8. 가장 느린 단계"] --> I["9. 시간 예산"] --> J["10. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
@@ -354,10 +354,24 @@ flowchart LR
- 기본 예산 3초에서 VPN이 3023ms로 **23ms 넘었습니다.** 지금 워크플로우는 3초 근처라 실행마다 `true` · `false`가 바뀝니다. 그래서 과제 2 테스트는 기본값일 때 `budget_ms: 3000`만 보고, 4교시 Lab 02에서 LLM을 줄이면 넉넉히 들어옵니다.
- `llm_ms`가 `total_ms`의 98%: 넘은 까닭이 LLM이라는 것이 결과만 봐도 보입니다.
## 10. 요청문으로
## 10. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
Lab과 다른 업무로 예를 듭니다 (주문 문의 워크플로우, **실습에는 없는 예**: `my-work/order.json`은 가상의 파일). Lab 01 예제 2 · 과제 1 · 과제 2의 요청문을 쓸 때 같은 칸을 채웁니다.
아래 표의 줄(넣을 것)은 위 다섯 칸을 이 단계 주제에 맞게 쪼갠 것이고, 실습 요청문(프롬프트)을 쓸 때도 같은 줄을 채웁니다.
| 넣을 것 | 예 |
| --- | --- |
| 바탕 · 바꿀 것 | "`my-work/order.json`은 그대로 두고 결과에 필드만 더한다" |
@@ -367,6 +381,17 @@ Lab과 다른 업무로 예를 듭니다 (주문 문의 워크플로우, **실
| 예산 | "본문 `budget_ms`(없으면 2000), `over_budget`, `llm_ms`는 `timings` 밖" |
| 기대 결과 | 테스트 파일 |
표를 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
my-work/order.json 은 그대로 두고 결과에 필드만 더해 줘. ← 그대로 · 무엇을
주문 조회 뒤와 답 LLM 뒤에 시각을 남겨, ← 어디에
결과에 timings: {lookup_ms, answer_ms, total_ms}, steps, llm_calls 를 넣어 줘. ← 어떤 모양으로
지나가지 않은 노드는 isExecuted 로 확인해 빼고,
본문 budget_ms(없으면 2000)를 넘으면 over_budget: true (llm_ms 는 timings 밖).
기대 결과 cases/<테스트 파일>.json. ← 확인
```
> "빠르게 해 줘"로 시작하지 않습니다. **"재게 해 줘"** 가 먼저입니다.
## 11. 자주 하는 실수
+26 -2
View File
@@ -8,7 +8,7 @@
```mermaid
flowchart LR
A["1. 줄일 수 있는<br/>LLM 호출"] --> B["2. 규칙 먼저"] --> C["3. 캐시"] --> D["4. 적중률"] --> E["5. 캐시 키 설계"]
E --> F["6. 검토를 검사로"] --> G["7. 잃은 것 적기"] --> H["8. 테스트도 바뀐다"] --> I["9. 요청문으로"]
E --> F["6. 검토를 검사로"] --> G["7. 잃은 것 적기"] --> H["8. 테스트도 바뀐다"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
@@ -313,10 +313,24 @@ Lab 02 과제 1 · 과제 2를 마칠 때 `my-work/PERF.md` 3절에 바꾼 것
> 수업 준비 때 처음 쓴 품질 테스트에 `llm_calls: 3`, `steps`에 `review` 같은 **구조**가 들어 있어 정당하게 빨라진 워크플로우가 실패했습니다.
## 9. 요청문으로
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
Lab과 다른 업무로 예를 듭니다 (사내 식당 메뉴 안내 워크플로우, **실습에는 없는 예**). Lab 02 예제 1 · 2 · 과제 1 · 2의 요청문을 쓸 때 같은 칸을 채웁니다.
아래 표의 줄(넣을 것)은 위 다섯 칸을 이 단계 주제에 맞게 쪼갠 것이고, 실습 요청문(프롬프트)을 쓸 때도 같은 줄을 채웁니다.
| 넣을 것 | 예 |
| --- | --- |
| 무엇을 줄이나 | "날짜가 있으면 규칙으로, 없을 때만 LLM이 날짜를 읽게" |
@@ -325,6 +339,16 @@ Lab과 다른 업무로 예를 듭니다 (사내 식당 메뉴 안내 워크플
| 결과에 남길 것 | `cached`, `cache_by`, `llm_calls` |
| 품질 확인 | "빨라진 뒤에도 원래 테스트가 통과" |
표를 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
질문에 날짜가 있으면 규칙(Code)으로 날짜를 읽고, 없을 때만 LLM 이 읽게 해 줘. ← 무엇을 · 어디에
메뉴 답은 날짜 + 식당 이름을 키로 캐시해 줘 (질문 글자가 아니라).
오늘 남은 수량은 바뀌므로 캐시하지 마. ← 하지 말 것
결과에 cached, cache_by, llm_calls 를 남겨 줘. ← 어떤 모양으로
빨라진 뒤에도 원래 테스트 cases/<테스트 파일>.json 이 통과해야 해. ← 확인
```
> "빠르게 해 줘"가 아니라 **"이 LLM 호출을 이 조건일 때 건너뛰어 줘"** 처럼 한 가지씩.
## 확인 질문
+26 -2
View File
@@ -8,7 +8,7 @@
```mermaid
flowchart LR
A["1. 왜 보안인가"] --> B["2. 위협 다섯 가지"] --> C["3. 먼저 공격해 보기"] --> D["4. 입력 검증"] --> E["5. 400과 차단"]
E --> F["6. 관문이 아낀 시간"] --> G["7. 규칙과 정상 질문"] --> H["8. 숨긴 공격 · 글자 정리"] --> I["9. 요청문으로"]
E --> F["6. 관문이 아낀 시간"] --> G["7. 규칙과 정상 질문"] --> H["8. 숨긴 공격 · 글자 정리"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
@@ -422,10 +422,24 @@ JSON 안에서는 `\`를 두 번(`\\s`) 씁니다.
같은 규칙 목록으로, 규칙 앞에 정리 두 줄(`clean` · `compact`)만 더했습니다 ([AGENTS.md](../../Last3Mon/73/AGENTS.md) 5절 "글자 정리 뒤 규칙").
## 9. 요청문으로
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
Lab과 다른 업무로 예를 듭니다 (주문 문의 Agent).
아래 표의 줄(넣을 것)은 위 다섯 칸을 이 단계 주제에 맞게 쪼갠 것이고, 실습 요청문(프롬프트)을 쓸 때도 같은 줄을 채웁니다.
| 넣을 것 | 예 |
| --- | --- |
| 관문의 위치 | "Agent 앞에, 고객 확인 → 검증 → 글자 정리 → 규칙 순서" |
@@ -434,6 +448,16 @@ Lab과 다른 업무로 예를 듭니다 (주문 문의 Agent).
| **막지 말 것의 예** | "'이전 주문', '시스템 점검'은 정상" |
| 기대 결과 | 공격 테스트 + 정상 질문 테스트 파일 |
표를 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
주문 문의 Agent 앞에 고객 확인 → 입력 검증 → 글자 정리 → 규칙 순서로 관문을 넣어 줘. ← 무엇을 · 어디에
입력이 잘못되면 400 {error, codes}, 규칙에 걸리면 200 {route: 차단, answer: 안내문, reason}. ← 어떤 모양으로
'이전 지시 무시', 'system prompt' 는 막고,
'이전 주문', '시스템 점검' 은 정상으로 지나가게 해 줘. ← 막지 말 것 (그대로)
공격 테스트와 정상 질문 테스트 파일이 모두 통과해야 해. ← 확인
```
- 요청문에 **막지 말 것의 예**가 없으면 에이전트는 가장 넓은 규칙을 씁니다 ("무시" 하나만으로).
- "테스트를 통과시키려고 기대 결과를 바꾸지 않는다"는 [AGENTS.md](../../Last3Mon/73/AGENTS.md) 8절에 있습니다. 에이전트가 테스트를 고치려 하면 멈춥니다.
+27 -2
View File
@@ -8,7 +8,7 @@
```mermaid
flowchart LR
A["1. 관문 하나를<br/>믿지 않기"] --> B["2. 틀리면 안 되는 값"] --> C["3. 지시문에<br/>비밀을 두지 않기"] --> D["4. 질문은 데이터"] --> E["5. 아는 것만 막는<br/>규칙의 한계"]
E --> F["6. 판별 LLM"] --> G["7. 관문의 비용"] --> H["8. 멈추면 막기<br/>fail-closed"] --> I["9. 요청문으로"]
E --> F["6. 판별 LLM"] --> G["7. 관문의 비용"] --> H["8. 멈추면 막기<br/>fail-closed"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
@@ -354,10 +354,24 @@ flowchart LR
- 오늘은 fail-closed로 정했습니다: 판별 LLM은 보안 관문이고, IT Agent도 같은 OpenAI를 쓰므로 판별이 멈춘 날은 어차피 답을 못 씁니다.
- 규칙 · 입력 검증은 LLM이 멈춰도 일합니다. **LLM에 기대지 않는 관문이 있다는 것**이 겹겹이 막기의 또 다른 값입니다.
## 9. 요청문으로
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
Lab과 다른 업무로 예를 듭니다 (사내 결재 문의 Agent).
아래 표의 줄(넣을 것)은 위 다섯 칸을 이 단계 주제에 맞게 쪼갠 것이고, 실습 요청문(프롬프트)을 쓸 때도 같은 줄을 채웁니다.
| 넣을 것 | 예 |
| --- | --- |
| 권한의 근거 | "결재자는 로그인 정보의 `user`에서, LLM이 정하지 않음. 남의 결재 건은 결재 API가 403" |
@@ -366,6 +380,17 @@ Lab과 다른 업무로 예를 듭니다 (사내 결재 문의 Agent).
| 멈췄을 때 | "판별 LLM 실패 → 200 정해진 안내, `reason: guard_down`, timeout 8초 · 재시도 0" |
| 기대 결과 | 새 공격 · 정상 테스트(`--repeat 3`), 멈춤 테스트(`before llm down`) |
표를 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
결재자는 로그인 정보의 user 에서 가져오고 LLM 이 정하지 않게 해 줘 (남의 결재 건은 결재 API 가 403). ← 무엇을 · 어디에
지시문에서 관리자 비밀번호 · 내부 메모를 빼 줘. ← 하지 말 것
규칙 뒤에 판별 LLM 을 넣어 줘: Tool 없음, JSON 만,
'결재 규칙' · '관리자 승인' 이 나와도 평범한 질문은 안전. ← 어떤 모양으로
판별 LLM 이 실패하면 200 정해진 안내 · reason guard_down (timeout 8초, 재시도 0).
새 공격 · 정상 테스트를 --repeat 3 으로, 멈춤 테스트(before llm down)도 통과. ← 확인
```
## 10. 자주 하는 실수
| 실수 | 생기는 일 | 대신 |
+27 -2
View File
@@ -8,7 +8,7 @@
```mermaid
flowchart LR
A["1. 나가는 답도<br/>검사"] --> B["2. 가리기 ·<br/>유출 차단"] --> C["3. 보안 기록"] --> D["4. 관문별<br/>시간과 기록"] --> E["5. 지나치게 ·<br/>덜 막기"]
E --> F["6. 감사 로그"] --> G["7. 데이터 최소화"] --> H["8. 남은 위험"] --> I["9. 요청문으로"]
E --> F["6. 감사 로그"] --> G["7. 데이터 최소화"] --> H["8. 남은 위험"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
@@ -354,10 +354,24 @@ flowchart LR
`my-work/SECURITY.md` 10절에 내 워크플로우의 남은 위험을 둘 이상 적습니다. 74일차(버전 관리와 운영 체계)에서 이 목록을 배포 전 점검표로 씁니다.
## 9. 요청문으로
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
Lab과 다른 업무로 예를 듭니다 (고객 주문 문의 Agent).
아래 표의 줄(넣을 것)은 위 다섯 칸을 이 단계 주제에 맞게 쪼갠 것이고, 실습 요청문(프롬프트)을 쓸 때도 같은 줄을 채웁니다.
| 넣을 것 | 예 |
| --- | --- |
| 출력 필터의 자리 | "답을 만드는 Code 뒤, 보내기 직전" |
@@ -367,6 +381,17 @@ Lab과 다른 업무로 예를 듭니다 (고객 주문 문의 Agent).
| 감사 로그 | "200으로 끝나면 `POST /audit`, 질문 · 답 글자 · 연락처는 넣지 않음, 실패해도 답은 나감" |
| 테스트 | "막을 것 · 막지 말 것 반반, 경계값, `--repeat 3`, `pii_in_log 0`" |
표를 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
답을 만드는 Code 뒤, 보내기 직전에 출력 필터를 넣어 줘. ← 어디에
전화는 앞 3 · 끝 4, 카드 번호는 끝 4만, 주소는 시 · 구까지 남기고,
답에 내부 쿠폰 코드 모양이 있으면 차단해 줘 (reason output_leak). ← 무엇을
모든 결과에 security {blocked_by, reason, masked} 를 길마다 같은 모양으로. ← 어떤 모양으로
200 으로 끝나면 POST /audit, 질문 · 답 글자 · 연락처는 넣지 말고, 실패해도 답은 나가게. ← 하지 말 것
막을 것 · 막지 말 것 반반 · 경계값 테스트를 --repeat 3, pii_in_log 0. ← 확인
```
## 10. 자주 하는 실수
| 실수 | 생기는 일 | 대신 |
+406
View File
@@ -0,0 +1,406 @@
# 74일차 개요 · 모델·Prompt·Workflow 버전 관리와 운영 체계
> 노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-28
> 30장 · 각 장은 가로줄(`---`)로 나뉩니다. 1교시에는 1~6장을 보고, 나머지 장은 각 단계 강의(1 · 3 · 5교시) 때 함께 띄웁니다.
> 숫자는 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/27 정답 워크플로우를 학생 순서대로 1회).
| 장 | 범위 | 띄우는 때 |
| --- | --- | --- |
| 1~6 | 표지 · 목표 · 시간표 · 버전이 붙은 헬프데스크 · 일하는 순서 · 실습 15개 | 1교시 시작 |
| 7~14 | 1단계 · 무엇이 바뀌나 | 1교시 강의 |
| 15~21 | 2단계 · 릴리스와 배포 전 게이트 | 3교시 강의 |
| 22~28 | 3단계 · 배포 · 되돌리기 · 카나리 | 5교시 강의 |
| 29~30 | 바이브 코딩 · 정리와 예고 (8교시 35분 정리는 [04-STAGE4-WRAPUP](04-STAGE4-WRAPUP.md)) | 7교시 · 8교시 |
---
## 1 / 30 · 표지
```mermaid
flowchart LR
A["바뀐 것 읽기"] --> V["답마다 버전"] --> R["릴리스"] --> G["게이트"] --> C["카나리"] --> B["되돌리기"]
classDef d fill:#0F3D3E,stroke:#0F3D3E,color:#fff
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
class A,B o
class V,R,G,C d
```
**74일차**
# 모델·Prompt·Workflow 버전 관리와 운영 체계
무엇이 바뀌었는지 남기고, 내보내기 전에 시험하고, 언제든 되돌린다
<sub>노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-28</sub>
---
## 2 / 30 · 오늘 할 수 있게 될 세 가지
| | 목표 | 내용 |
| --- | --- | --- |
| 1 | **바뀐 것을 읽고 남긴다** | 모델 · 지시문 · 흐름을 구분해 diff로 읽고, 흔들림과 바뀜을 반복해서 가르고, 답마다 `version`, 지시문은 저장소에서 |
| 2 | **내보내기 전에 시험한다** | 릴리스를 밖에서 읽고 후보는 고정해서, 게이트로 PASS/FAIL, 기준선, 강한 테스트, 망가진 릴리스는 크게 실패 |
| 3 | **조금씩 내보내고 되돌린다** | 값 하나로 배포 · 되돌리기, 카나리, 나쁜 카나리 끄기, 점검을 한 곳에서, 점검을 지난 것만 배포 |
---
## 3 / 30 · 오늘의 시간표
| 교시 | 시간 | 내용 |
| --- | --- | --- |
| 1 | 09:00~09:50 | 개요 · **1단계 강의** · 무엇이 바뀌나 |
| 2 | 10:00~10:50 | **Lab 01 실습 5개** · diff · version · 흔들림 · 저장소 · 저장소가 멈추면 |
| 3 | 11:00~11:50 | **2단계 강의** · 릴리스와 배포 전 게이트 |
| 4 | 12:00~12:50 | **Lab 02 실습 5개** · 릴리스 읽기 · 게이트 · 기준선 · 강한 테스트 · 망가진 릴리스 |
| - | 13:00~14:00 | 점심 |
| 5 | 14:00~14:50 | **3단계 강의** · 배포 · 되돌리기 · 카나리 |
| 6 | 15:00~15:50 | **Lab 03 실습 5개** · 배포 · 카나리 · 나쁜 카나리 · 점검 · 점검 기록 |
| 7 | 16:00~16:50 | 바이브 코딩 `tver`: 요청서 → TODO → SPEC → IMP-PLAN → 만들기 → 설치 |
| 8 | 17:00~17:50 | 발표 · **4단계 정리** (의미 · 효과 · 회사에서 쓰는 법) · 75일차 예고 |
---
## 4 / 30 · 오늘의 헬프데스크: 버전이 붙은
```mermaid
flowchart LR
subgraph API["데이터 API (사람이 관리)"]
P[("지시문 저장소<br/>p1 · p2 · p3")]
REL[("릴리스<br/>1.0.0 · 1.1.0 · 1.2.0 · 2.0.0 · 2.1.0<br/>active · canary · 정책")]
GT[("점검 기록<br/>/gates")]
end
Q(["질문 · user · release?"]) --> PK["릴리스 고르기<br/>고정 > 카나리 > active"] --> GP["지시문 가져오기<br/>장애면 기본 지시문"] --> A{{"답<br/>모델 = 릴리스의 모델"}} --> O(["답 + version"])
REL --> PK
P --> GP
G["게이트 · 릴리스 점검"] -.->|기록| GT
GT -.->|정책| REL
```
- 72 · 73일차와 같은 IT 문의 헬프데스크 (FAQ · 접수 건). 오늘은 **답을 바꾸는 것**(모델 · 지시문)을 워크플로우 밖에서 버전으로 다룹니다.
---
## 5 / 30 · 일하는 순서 (오늘은 "바뀐 것 → 버전 → 게이트")
```mermaid
flowchart LR
A["① 무엇이 바뀌나<br/>diff · call"] --> B["② 요청문을<br/>메모장에"] --> C["③ 에이전트에게"] --> D["④ 에이전트가<br/>my-work · check"] --> E["⑤ 내가<br/>push · test · gate"]
E -->|✘| F["⑥ 실패 줄 · 보고서 경로<br/>그대로 붙여 주기"] --> C
E -->|✔| G["⑦ CHANGELOG 한 줄"]
```
- 배포 · 카나리 · 되돌리기 · 정책은 **내가** `flow.py release`로. 끝나면 `release helpdesk --reset`.
- 후보는 `--set release=...`로 고정해 시험. 모든 명령이 `-v`를 받습니다.
---
## 6 / 30 · 오늘의 실습 15개
| | 예제 1 | 예제 2 | 예제 3 | 과제 1 | 과제 2 |
| --- | --- | --- | --- | --- | --- |
| Lab 01 | v1 → v2 diff (에이전트 없이) | 답마다 `version` | 흔들림인가 바뀜인가 (에이전트 없이) | 지시문을 저장소에서 | 저장소가 멈추면 기본 지시문 |
| Lab 02 | 릴리스 읽기 · 후보 고정 | 약한 golden 게이트 (에이전트 없이) | 기준선: 시간 · 반복 (에이전트 없이) | 나쁜 릴리스를 막는 게이트 | 망가진 릴리스는 400 |
| Lab 03 | 배포 · 되돌리기 (에이전트 없이) | 카나리 | 나쁜 카나리 끄기 (에이전트 없이) | 릴리스 점검 워크플로우 | 점검 기록 · 지난 것만 배포 |
- 실습 문서에 복사해 붙일 요청문은 없습니다. 막히면 `▶`, 요청문은 **빈칸 4개**를 채워서.
---
## 7 / 30 · 1단계 · 왜 버전 관리인가
| 질문 | 필요한 것 |
| --- | --- |
| 어제와 오늘 사이에 **무엇이** 바뀌었나 | 변경 기록 · diff |
| 이 답은 **어느 버전이** 냈나 | 답 · 로그의 버전 (추적성) |
| 어제로 **돌아갈 수** 있나 | 지워지지 않은 이전 버전 (되돌리기) |
| 같은 입력이면 같은 결과인가 | 재현성 |
- 일반 소프트웨어: 코드가 같으면 결과가 같다. **LLM 앱: 코드가 같아도 모델 · 지시문이 바뀌면 다르다.** → MLOps · LLMOps.
---
## 8 / 30 · 1단계 · 바뀌는 것 셋 (+ 공급자가 바꾸는 모델)
| 바뀌는 것 | 누가 | 오늘 |
| --- | --- | --- |
| 모델 | 개발자, **공급자** | 릴리스의 `model` |
| 지시문 | 기획 · 운영 · 개발자 | 저장소 p1 · p2 · p3 |
| 흐름 | 개발자 | 워크플로우 JSON |
**10/27 수업 준비 중 실제로**: v2의 `gpt-4.1-nano`가 OpenAI에서 **지원 종료** → 워크플로우는 그대로인데 HTTP 500. `gpt-5.4-nano`로 바꿈. 모델 이름도 버전이고, 사라집니다.
---
## 9 / 30 · 1단계 · 차이 읽기 (Lab 01 예제 1)
```text
+ node 'Review check' (code)
~ 'Answer' parameters.options.systemMessage: changed (307 → 419 chars)
~ 'Answer model' parameters.model.value: gpt-4.1-mini → gpt-5.4-nano
~ 'Question webhook' parameters.path: rel-v1 → rel-v2
+ link Answer → Review check (main) ... 7 change(s)
```
- 노드 단위 diff: 위치 · id 같은 뜻 없는 차이는 빼고. v2는 VPN 1.0초 · `[FAQ-003]` (v1 2.8초).
- **셋이 한꺼번에** 바뀌어 원인을 모릅니다. 한 번에 하나.
---
## 10 / 30 · 1단계 · 흔들림인가 바뀜인가 (Lab 01 예제 3)
| `lab01-compare --repeat 3` | v1 | v2 |
| --- | --- | --- |
| 해결 낱말 · 접수 현황 (3케이스) | ✔ × 9 | ✔ × 9 |
| 출처 번호 (2케이스) | ✘ × 6 | ✔ × 6 |
| 합계 | 9/15 | 15/15 |
- 세 번 모두 같은 쪽 → **신호**(바뀐 것). 나왔다 안 나왔다 → **잡음**(흔들림).
- VPN `bench -n 5`: v1 p50 1.23초 · v2 0.98초. 빨라진 원인은 아직 모름.
---
## 11 / 30 · 1단계 · 답마다 버전 (Lab 01 예제 2)
```json
{"route": "자동 답변", "answer": "…",
"version": {"release": "1.0.0", "prompt_version": "p1", "model": "gpt-4.1-mini"}}
```
- 출처 표시(provenance): 이 답을 **무엇이** 냈나.
- 모델 이름은 **한 곳**(릴리스 정보)에서 정하고 모델 노드와 결과가 **같은 곳을 읽게**. `lab01` 3/3.
---
## 12 / 30 · 1단계 · 지시문은 저장소로 (Lab 01 과제 1)
| 지시문이 워크플로우 안에 | 지시문이 저장소에 |
| --- | --- |
| 한 문장 고쳐도 push · 재시작 | 새 버전을 더함 |
| 흐름 변경과 섞임 | p1 → p2만 따로 |
| 예전 것을 찾기 어려움 | p1 · p2 · p3 나란히 (불변) |
- 업계: Prompt Hub · Langfuse · PromptLayer · MLflow Prompt Registry — 이름 + 불변 버전 + 꼬리표.
- `lab01-challenge` 2/3 → 3/3 (`prompt_source: registry`).
---
## 13 / 30 · 1단계 · 저장소가 멈추면 (Lab 01 과제 2)
| `lab01-challenge2` | 고치기 전 | 고친 뒤 |
| --- | --- | --- |
| 저장소 고장 (500) | ✘ HTTP 500 · 0.1초 | ✔ `fallback` · 1.3초 |
| 저장소 느림 (8초) | ✘ 9.1초 기다림 | ✔ `fallback` · 3.0초 |
| 평소 · 되돌린 뒤 | ✔ ✔ | ✔ ✔ |
- 부르는 곳이 늘면 장애점도 는다 (72일차). timeout 2초 + **마지막으로 잘 됐던 지시문(p1)**.
---
## 14 / 30 · 1단계 · 막을까, 계속 갈까
| | 73일차 판별 LLM | 오늘 지시문 저장소 |
| --- | --- | --- |
| 멈추면 위험 | 공격이 지나감 | 조금 예전 지시문 |
| 고른 쪽 | fail-closed (`guard_down`) | fail-open (`fallback`) |
- 계속 갔다는 사실은 **남깁니다** (`prompt_source`). 아무도 모르는 계속 가기는 사고를 숨깁니다 → 21장.
---
## 15 / 30 · 2단계 · 릴리스 = 지시문 + 모델
| 릴리스 | 지시문 | 모델 | 1.0.0과 다른 것 |
| --- | --- | --- | --- |
| 1.0.0 | p1 | mini | (기준) |
| 1.1.0 | p2 | mini | 지시문만 (출처 번호) |
| 1.2.0 | p3 | mini | 지시문만 (한 문장 · 포털로) |
| 2.0.0 | p2 | 5.4-nano | 1.1.0과 비교하면 모델만 |
| 2.1.0 | p9 | mini | 없는 지시문 (21장) |
- **한 릴리스에 한 가지.** 한 번 낸 릴리스는 바꾸지 않음 (불변 결과물).
---
## 16 / 30 · 2단계 · 밖에서 읽고, 후보는 고정 (Lab 02 예제 1)
```mermaid
flowchart LR
G["GET /releases"] --> P{"본문 release?"}
P -->|있음| F["그 릴리스 · pinned"]
P -->|없음| S["active · stable"]
```
- 배포 = 값 하나 (원격 설정 · 기능 플래그와 같은 생각). 없는 릴리스는 400.
- 고정의 문은 **좁게** (73일차: 공격면). `lab02` 1/5 → 5/5.
---
## 17 / 30 · 2단계 · 게이트 (Lab 02 예제 2)
| 약한 golden (200 · route만) | 결과 |
| --- | --- |
| 1.0.0 · 1.1.0 · 2.0.0 | 6/6 PASS |
| **1.2.0** | **6/6 PASS** |
1.2.0의 답: **"IT 포털에서 프린터 문제 해결 방법을 확인해 주세요."**
- 품질 게이트 = 평가 세트 + 판정 규칙 + 기준 + 기록. 종료 코드 0/1로 파이프라인이 멈춤.
---
## 18 / 30 · 2단계 · 게이트는 테스트만큼만 (Lab 02 과제 1)
| 릴리스 | 약한 golden | 강한 golden (해결 낱말 · 상태 · 떠넘기기) |
| --- | --- | --- |
| 1.0.0 · 1.1.0 | PASS | 9/9 PASS |
| **1.2.0** | **PASS** | **1/9 FAIL** |
| 2.0.0 | PASS | 9/9 PASS |
- 기대는 **데이터에서 사람이** (지금 답을 베끼지 않음). 금지 문구 "포털을 참고"는 "포털**에서**"를 놓침 → 해결 낱말이 더 튼튼.
---
## 19 / 30 · 2단계 · 기준선 (Lab 02 예제 3)
| VPN `bench -n 5` | p50 | p95 |
| --- | --- | --- |
| 1.0.0 (mini) | 1.23초 | 1.42초 |
| 2.0.0 (5.4-nano) | 1.12초 | 1.24초 |
- golden 1.0.0 × 3번 = **18/18** → 흔들리지 않는 게이트. `--max-p95 3`은 약 두 배 여유.
- 기준은 감이 아니라 **재서**. 흔들리는 테스트는 게이트를 무너뜨림.
---
## 20 / 30 · 2단계 · 작은 모델로 바꿔도 될까
| 저울 | 2.0.0 |
| --- | --- |
| 품질 (강한 golden) | 9/9 PASS |
| 속도 | p95 1.3초 (게이트), VPN p50 1.12초 |
| 비용 | 75일차 |
- 게이트 PASS = **게이트가 보는 것은** 괜찮다. 말투 · 드문 질문은? → 카나리로 먼저 (3단계).
---
## 21 / 30 · 2단계 · 망가진 릴리스는 크게 실패 (Lab 02 과제 2)
| 2.1.0 (p9, 저장소에 없음) | 고치기 전 | 고친 뒤 |
| --- | --- | --- |
| 고정해서 물으면 | 200 (기본 지시문이 받아 줌) | **400 `release broken`** |
| 강한 golden 게이트 | **9/9 PASS** (위험) | 0/9 FAIL |
- 장애(5xx · 시간 초과) → 견딘다. 설정 오류(4xx) → **fail fast**. 조용한 실패가 가장 비싸다.
---
## 22 / 30 · 3단계 · 배포와 되돌리기 (Lab 03 예제 1)
| 되돌리기 | 걸리는 일 |
| --- | --- |
| 예전 JSON을 찾아 push | 분 단위 · 재시작 · 다른 것도 섞임 |
| **`active` 값 하나** | 명령 한 줄, 다음 요청부터 |
- 배포(올려 두기) ≠ 릴리스(켜기). 먼저 되돌리고 원인을 찾는다. **되돌리기는 연습한다.**
- DORA: 배포 빈도 · 리드 타임 · 변경 실패율 · 복구 시간(MTTR). `lab03` 2/2.
---
## 23 / 30 · 3단계 · 배포 전략
| 전략 | 피해 반경 | 되돌리기 |
| --- | --- | --- |
| 한 번에 | 100% | 느릴 수 있음 |
| 블루그린 | 100% (한 번에 넘김) | 빠름 |
| **카나리** | 일부 (1~5%부터) | 끄면 끝 |
| 그림자 | 0% (답은 버림) | 필요 없음 |
| A/B | 나눔 | "더 좋은가"를 봄 |
---
## 24 / 30 · 3단계 · 카나리: 같은 사람은 같은 쪽 (Lab 03 예제 2)
| 50% 카나리 | bucket (이름 글자 코드 합 % 100) | 받는 릴리스 |
| --- | --- | --- |
| 최유진 · 정우성 | 0 · 42 | 카나리 |
| 한지훈 · 이준호 · 박서연 · 김민지 | 52 · 56 · 57 · 80 | active |
- 무작위로 나누지 않음 (고정 배정, sticky bucketing). 순서: **고정 > 카나리 > active**. `lab03-canary` 3/6 → 6/6.
---
## 25 / 30 · 3단계 · 나쁜 카나리를 보고 끄기 (Lab 03 예제 3)
| 1.2.0 50% 카나리 (`lab03-watch`) | 결과 |
| --- | --- |
| 켠 상태 | **4/6** — 카나리 둘이 "대기열" 없는 답 |
| `--no-canary` 뒤 | 6/6 |
- 카나리는 **보는 것까지** (카나리 분석). 전원 배포였다면 6/6명이 영향.
---
## 26 / 30 · 3단계 · 점검을 한 곳에서 (Lab 03 과제 1)
```mermaid
flowchart LR
W(["release"]) --> G["질문 6개<br/>항목 6개"] --> H["my-helpdesk<br/>release 고정"] --> J["Judge (Code)"] --> V(["promote · rollback"])
```
| `my-release-check` | 결과 |
| --- | --- |
| 1.1.0 · 2.0.0 | `promote` 6/6 |
| 1.2.0 | `rollback` 0/6 |
---
## 27 / 30 · 3단계 · 판단을 남기고, 정책으로 (Lab 03 과제 2)
| 정책 켬 (`require_gate`) | 고치기 전 | 고친 뒤 |
| --- | --- | --- |
| 점검 결과 | 말만 함 | `/gates`에 기록 · `recorded` |
| 1.1.0 배포 | **409 not gated** (기록 없음) | 200 |
| 1.2.0 배포 · 2.0.0 카나리 | 409 | 409 |
- 판단 · 기록 · 실행을 나눔 (점검은 배포하지 않음). 코드로 된 정책.
---
## 28 / 30 · 3단계 · 운영 체계
```mermaid
flowchart LR
N["새 릴리스"] --> G["게이트 · 기록"] -->|PASS| K["카나리"] --> C["점검 · 비교"] -->|promote| D["active"]
G -->|FAIL| X["멈춤"]
C -->|rollback| R["카나리 끄기"]
D -->|문제| B["되돌리기"]
```
- 누가 · 언제 · 어떤 순서로 · 무엇을 남기나 · 문제면(런북) · 연습. `CHANGELOG.md` · `RELEASE.md`.
---
## 29 / 30 · 7교시 · 바이브 코딩 `tver`
- n8n 워크플로우를 **git에 버전으로 저장**하고 두 버전을 **노드 단위로 비교**하는 TUI. 오늘 `flow.py diff`를 내 도구로.
- 순서: [요청서](../../Last3Mon/74/VIBE-REQ-TVER.md) → `Doc/TODO.md` → `Doc/SPEC.md` → `Doc/IMP-PLAN.md` → 단계별 구현 → `uv tool install` → 다른 폴더에서.
- 요구 14개 (R1~R14). 50분 안에 엔진 1 · 2단계 · TUI 1단계 · 설치.
```mermaid
flowchart LR
R["요청서"] --> T["TODO"] --> S["SPEC"] --> P["IMP-PLAN"] --> B["엔진 → TUI"] --> I["uv tool install"]
```
---
## 30 / 30 · 오늘의 정리 · 75일차 예고
| 오늘 | 한 줄 |
| --- | --- |
| 1단계 | 바뀐 것을 읽고(diff), 반복해서 재고, 답마다 버전, 지시문은 저장소 (멈추면 기본 지시문) |
| 2단계 | 릴리스를 밖에서 읽고 고정해서 시험, 게이트는 테스트만큼만, 기준은 재서, 설정 오류는 크게 실패 |
| 3단계 | 값 하나로 배포 · 되돌리기, 카나리는 보고 끈다, 점검을 남기고 정책으로 지킨다 |
**75일차 · 사용량과 비용**: 오늘 2.0.0(작은 모델)을 게이트로 품질 · 속도만 봤습니다. 내일은 **토큰 · 가격**으로 비용을 재고, 같은 게이트를 지키면서 비용을 줄입니다.
+96
View File
@@ -0,0 +1,96 @@
# 74일차 실습 준비 · 수업 전에 미리 해 두면 좋은 것
- 수업일: 2026-10-28 (74일차) · 모델·Prompt·Workflow 버전 관리와 운영 체계
- 준비 시간: 약 10분
- 먼저 WSL에서 `cd ~/aio-01d4-stu && git pull`로 오늘 자료를 받습니다 (수강생 저장소: https://gl.isitsoft.kr/publicproj/aio-01d4-stu).
- **수업 전날까지** 1~2절을 끝내 주세요.
```mermaid
flowchart LR
A["1. n8n · 데이터 API 켜기<br/>(74일차 판)"] --> B["2. v1 · v2 게시 ·<br/>doctor ready"] --> C["3. 미리 읽기"]
```
---
## 1. n8n과 데이터 API 켜기 (전날 필수, 3분)
오늘은 **74일차 판 데이터 API**(73일차 + 지시문 저장소 + 릴리스 + 점검 기록 · 배포 정책)를 씁니다. 같은 이름이라 이 폴더에서 다시 만들면 바뀝니다.
```bash
sudo systemctl disable --now ollama 2>/dev/null # WSL에 설치한 Ollama가 있으면 끄고 자동 시작도 끔
cd ~/aio-01d4-stu/Last3Mon/65/n8n
docker compose rm -sf ollama # Ollama 컨테이너가 있으면 지움 (없으면 그냥 지나감)
docker compose start
cd ~/aio-01d4-stu/Last3Mon/74/data-api
docker compose up -d --force-recreate
sleep 3 # 데이터 API가 뜰 때까지
curl -s localhost:8800/releases/helpdesk | head -c 80 # {"name": "helpdesk", "active": "1.0.0", ...
```
- **66일차부터 LLM은 OpenAI API 키(`Last3Mon/.env`, Credential `OpenAI (.env)`)만 씁니다.** 오늘은 같은 키로 작은 모델 `gpt-5.4-nano`도 씁니다 (10/27 수업 준비 중 `gpt-4.1-nano`가 OpenAI에서 지원 종료되어 바꿈 — 1단계 강의 2절).
- `network day65-n8n_default not found` → n8n이 켜져 있지 않음. 위 n8n 부분부터 다시.
## 2. v1 · v2 게시와 준비 확인 (전날 필수, 3분)
오늘 비교할 두 버전을 게시해 둡니다 (만들지 않고 게시만).
```bash
cd ~/aio-01d4-stu/Last3Mon/74
uv run tools/flow.py push reference/helpdesk-v1.json reference/helpdesk-v2.json
uv run tools/flow.py call rel-v1 requests/faq-vpn.json
uv run tools/flow.py doctor
```
```text
✔ n8n container day65-n8n-n8n-1
✔ n8n http://localhost:5678
✔ data API http://localhost:8800
✔ releases /releases/helpdesk · gates /gates/helpdesk (day 74 data API)
✔ n8n → http://data-api:8800
✔ credential 'OpenAI (.env)' (openAiCourse0001)
✔ node catalog (for check)
✔ CLI coding agent: codex, claude, agy
ready
```
| ✘ 항목 | 할 일 |
| --- | --- |
| n8n container · data API | 1절 |
| releases · gates | 데이터 API가 예전 판 → 1절 `--force-recreate` |
| `! release state is not the start state` | `uv run tools/flow.py release helpdesk --reset` |
| credential | `cd ~/aio-01d4-stu/Last3Mon/65/n8n && bash register-credentials.sh` |
| CLI coding agent | [67일차 준비](../67/00-DAY67-PREP.md) 2절 |
---
## 3. 미리 읽어 오면 좋은 내용 (선택, 약 5분)
### 3.1 어제와 오늘 답이 다르다
어제 잘 되던 헬프데스크가 오늘 이상한 답을 합니다. 누가 지시문을 고쳤나요, 모델을 바꿨나요, 노드를 바꿨나요? 언제부터였나요? 되돌리려면 무엇으로 되돌리나요? 오늘은 이 질문에 바로 답할 수 있게 만듭니다.
### 3.2 용어
| 용어 | 한 줄 설명 |
| --- | --- |
| 릴리스 | 한 번에 내보내는 묶음 (오늘은 지시문 버전 + 모델) |
| 게이트 | 배포 전에 통과해야 하는 테스트와 기준 (통과율 · p95) |
| 카나리 | 새 릴리스를 일부 사용자에게만 먼저 내보내기 |
| 되돌리기 (롤백) | 문제가 생긴 릴리스를 이전 것으로 바꾸기 |
| 고정 (pin) | 특정 요청만 특정 릴리스로 보내기 (후보 시험) |
| 기본 지시문 (fallback) | 지시문 저장소가 멈췄을 때 대신 쓰는, 워크플로우 안에 둔 검증된 지시문 |
### 3.3 7교시 요청서
[VIBE-REQ-TVER](../../Last3Mon/74/VIBE-REQ-TVER.md)를 한 번 읽어 옵니다. 운영팀장이 원하는 것 열두 가지 중 "꼭"은 무엇일까요?
## 4. 당일 준비물
- 충전된 노트북과 충전기, WSL의 Docker, Windows Terminal(WSL) 창 **두 개**, 웹 브라우저
- 68일차 `~/vibe/tflow` (7교시 `tver`의 바탕, 없어도 됨), 73일차 `~/vibe/tscan` (키 모양 찾기, 없어도 됨)
## 확인
- [ ] `curl -s localhost:8800/releases/helpdesk`에 `active 1.0.0`
- [ ] `call rel-v1`이 답을 돌려줌
- [ ] `uv run tools/flow.py doctor` → 마지막 줄 `ready`
+512
View File
@@ -0,0 +1,512 @@
# 1단계 · 무엇이 바뀌나
- 교시: 1교시 강의 (09:00~09:50) · 2교시 [Lab 01](../../Last3Mon/74/LAB-01-Q-WHAT-CHANGES.md) 실습 5개 (10:00~10:50)
- 이 단계가 끝나면: AI 워크플로우에서 바뀌는 것 세 가지(모델 · 지시문 · 흐름)를 구분하고, 두 버전의 차이를 노드 단위로 읽고, 그 차이가 진짜 바뀐 것인지 흔들림인지 반복해서 재서 가르고, 답마다 버전을 남기고, 지시문을 워크플로우 밖 저장소로 꺼내고, 저장소가 멈춰도 답이 나가게 할 수 있다.
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/27 실습을 5개로 늘린 뒤 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/74/answers/LAB-TEST-RESULT.md`에 있습니다.
- 실습 가리키기: 이 문서에서 "Lab 01 예제 1"은 2교시 [Lab 01 문서](../../Last3Mon/74/LAB-01-Q-WHAT-CHANGES.md)의 그 절입니다. 파일 경로(`cases/` · `requests/` · `my-work/` · `reference/`)는 실습 폴더 `~/aio-01d4-stu/Last3Mon/74` 기준입니다.
```mermaid
flowchart LR
A["1. 왜 버전 관리인가"] --> B["2. 바뀌는 것 셋"] --> C["3. 차이 읽기"] --> D["4. 흔들림과 바뀜"] --> E["5. 답마다 버전"]
E --> F["6. 지시문 저장소"] --> G["7. 저장소가 멈추면"] --> H["8. 버전 이름"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
| 시간 | 절 · 개요 | 하는 일 |
| --- | --- | --- |
| 09:00~09:06 | 개요 1~6장 | 오늘 하루, 버전이 붙은 헬프데스크, 실습 15개 |
| 09:06~09:12 | 1 · 개요 7장 | "어제는 됐는데", 재현성 · 추적성 · 되돌리기, MLOps · LLMOps |
| 09:12~09:18 | 2 · 개요 8장 | 모델 · 지시문 · 흐름 (+ 데이터 · 도구), 모델 이름이 사라지는 날 |
| 09:18~09:23 | 3 · 개요 9장 | 노드 단위 diff, 한꺼번에 바꾸면 원인을 모른다 |
| 09:23~09:29 | 4 · 개요 10장 | 비결정성, 신호 대 잡음, 반복 측정 |
| 09:29~09:34 | 5 · 개요 11장 | 답마다 `version`, 한 곳에서 정하기 |
| 09:34~09:40 | 6 · 개요 12장 | 지시문 저장소, 불변 버전, 업계 도구 |
| 09:40~09:45 | 7 · 개요 13~14장 | 의존성이 늘면 장애점도, 기본 지시문(fallback) |
| 09:45~09:50 | 8 · 9 · 10 · 확인 질문 | 시맨틱 버전, 요청문, 자주 하는 실수 |
### 2교시 Lab 01에서 이어서 하는 것
| 2교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
| --- | --- | --- | --- |
| 시작 (10:00) | — | 변경 기록 복사 | `cp templates/CHANGELOG-TEMPLATE.md my-work/CHANGELOG.md` (오늘 바뀐 것을 모두 여기에) |
| Lab 01 예제 1 (10:00~10:10, 에이전트 없이) | 2 · 3 | v1 → v2의 바뀐 것 7개 읽기 | `diff reference/helpdesk-v1.json reference/helpdesk-v2.json [--full]`, `call rel-v1` · `call rel-v2` → `CHANGELOG.md` 1절 |
| Lab 01 예제 2 (10:10~10:22) | 5 | 에이전트에게 릴리스 정보 · `version`을 넣게 하기 | `my-work/helpdesk.json` (v1을 복사), 테스트 `cases/lab01.json` |
| Lab 01 예제 3 (10:22~10:30, 에이전트 없이) | 4 | 같은 테스트를 v1 · v2에 3번씩, 시간 재기 | `test cases/lab01-compare.json --webhook rel-v1 --repeat 3` 등, `bench rel-v1` · `rel-v2` |
| Lab 01 과제 1 (10:30~10:40) | 6 | 지시문을 저장소에서 | 테스트 `cases/lab01-challenge.json` |
| Lab 01 과제 2 (10:40~10:50) | 7 | 저장소가 멈추면 기본 지시문으로 | 테스트 `cases/lab01-challenge2.json` (`fault prompts error` · `slow`) |
## 1. 왜 버전 관리인가: "어제는 됐는데요"
### 일반 설명
운영 중인 시스템에 가장 자주 들어오는 문의는 "어제는 됐는데 오늘은 안 돼요"입니다. 이 문의에 답하려면 세 가지를 알아야 합니다.
| 질문 | 필요한 것 | 이름 |
| --- | --- | --- |
| 어제와 오늘 사이에 **무엇이** 바뀌었나 | 바뀐 것의 목록과 차이 | 변경 관리 (change management) |
| 이 답은 **어느 버전이** 냈나 | 답 · 로그에 남은 버전 | 추적성 (traceability) |
| 어제 상태로 **돌아갈 수** 있나 | 지워지지 않은 이전 버전, 바꾸는 방법 | 되돌리기 (rollback) |
그리고 하나 더, **같은 입력을 다시 넣으면 같은 결과가 나오나** — 재현성(reproducibility)입니다. 재현할 수 없으면 고쳤는지도 확인할 수 없습니다.
**일반 소프트웨어는 이것을 오래 다듬어 왔습니다.** 코드는 git으로 버전을 남기고, 빌드는 같은 코드에서 같은 결과물을 만들고, 배포는 결과물(artifact)에 버전 번호를 붙여 내보내고, 문제가 생기면 이전 결과물로 되돌립니다. "코드가 같으면 결과가 같다"가 전제입니다.
**AI 앱은 그 전제가 깨집니다.**
| | 일반 소프트웨어 | LLM 워크플로우 |
| --- | --- | --- |
| 결과를 정하는 것 | 코드 | 코드(흐름) + **모델** + **지시문** + 데이터 |
| 같은 입력 · 같은 코드 | 같은 결과 | 대체로 비슷하지만 **조금씩 다를 수 있음** |
| 누가 바꾸나 | 개발자 | 개발자 + 기획 · 운영(지시문) + **공급자**(모델) |
| 바뀐 줄 아는 법 | git 기록 | 따로 기록하지 않으면 모름 |
그래서 AI 앱에서는 코드 말고도 **모델과 지시문을 각각 버전으로 관리**해야 합니다. 머신러닝 모델을 운영하는 방법을 MLOps라고 부르고, LLM 앱에 맞춘 것을 LLMOps라고 부릅니다. 이름은 달라도 하는 일은 같습니다: **무엇이 바뀌었는지 남기고, 바꾸기 전에 시험하고, 되돌릴 수 있게 한다.**
**버전 관리가 없으면 생기는 일.**
| 상황 | 버전 관리가 없으면 | 있으면 |
| --- | --- | --- |
| 지시문 한 줄을 고쳤더니 다른 질문의 답이 나빠짐 | 어느 수정 때문인지 기억에 의존 | 지시문 p2 → p3의 차이를 봄 |
| 공급자가 모델을 바꿈 | "갑자기 말투가 달라졌어요" | 답의 `model`이 바뀐 날짜를 봄 |
| 감사 · 분쟁 ("그때 AI가 이렇게 말했다") | 그때의 지시문을 다시 만들 수 없음 | 그때의 릴리스로 고정해 다시 물어봄 |
| 새 버전이 나쁨 | 예전 파일을 찾아 다시 올림 (분 단위) | 값 하나로 되돌림 (초 단위, 3단계) |
> 버전 관리는 "기록을 남기는 습관"이 아니라 **"사고가 났을 때 몇 분 안에 원인을 찾고 되돌리는 능력"**입니다.
**재현성은 왜 LLM 앱에서 더 어려운가.** 일반 프로그램의 버그는 "이 입력을 넣으면 늘 이 오류"라서 재현해 보고 고칩니다. LLM 앱의 이상한 답은 같은 질문을 다시 넣어도 다시 나오지 않을 수 있습니다. 그래서 운영하는 쪽은 **그때의 조건 전부**를 남겨 둡니다: 질문, 질문한 사람, 그때의 릴리스(지시문 · 모델), 그때 검색된 문서, 시각. 이 중 하나라도 없으면 "그때 왜 그랬는지"를 다시 만들 수 없습니다. 오늘 답마다 `version`을 남기는 것은 이 조건 중 가장 중요한 두 가지(지시문 · 모델)를 남기는 일입니다.
**변경은 사고의 가장 흔한 원인입니다.** 운영 사고를 모아 보면 상당수가 "누군가 무언가를 바꾼 직후"에 일어납니다. 그래서 운영 팀은 사고가 나면 가장 먼저 **최근 변경 목록**을 봅니다. 변경 목록이 없으면 사고 원인을 찾는 데 몇 시간이 걸리고, 있으면 몇 분이 걸립니다. 버전 관리는 이 목록을 **자동으로, 빠짐없이** 만드는 장치입니다.
| 버전 관리의 네 가지 일 | 질문 | 오늘 실습 |
| --- | --- | --- |
| 기록 | 무엇이 · 언제 · 누가 · 왜 바뀌었나 | `CHANGELOG.md`, 지시문 p1 · p2 · p3 |
| 비교 | 두 버전은 무엇이 다른가 | `flow.py diff`, 7교시 `tver` |
| 시험 | 바꾸기 전에 괜찮은지 어떻게 아나 | 2단계 고정 · 게이트 |
| 되돌리기 | 나쁘면 얼마나 빨리 돌아가나 | 3단계 값 하나 |
### 오늘 실습에서
오늘의 헬프데스크는 72 · 73일차와 같은 IT 문의 창구입니다 (FAQ 검색 + 접수 건 조회 + 답하는 Agent). 하루 동안 여기에 버전을 붙이고, 배포 전에 시험하고, 되돌릴 수 있게 만듭니다.
| 시간 | 할 일 | 실습 |
| --- | --- | --- |
| 1~2교시 | 무엇이 바뀌었나 읽기 · 흔들림 가르기 · 답마다 버전 · 지시문 저장소 · 저장소가 멈추면 | Lab 01 |
| 3~4교시 | 릴리스 읽기 · 후보 고정 · 게이트 · 기준선 · 강한 테스트 · 망가진 릴리스 | Lab 02 |
| 5~6교시 | 배포 · 되돌리기 · 카나리 · 나쁜 카나리 끄기 · 릴리스 점검 · 점검을 지난 것만 배포 | Lab 03 |
## 2. 바뀌는 것 셋: 모델 · 지시문 · 흐름
### 일반 설명
LLM 워크플로우의 답을 정하는 것을 나누면 다섯 가지입니다. 오늘은 앞의 셋을 버전으로 다룹니다.
| 바뀌는 것 | 예 | 누가 바꾸나 | 오늘 |
| --- | --- | --- | --- |
| **모델** | `gpt-4.1-mini` → 더 작은 모델 | 개발자, **또는 공급자** | 릴리스의 `model` |
| **지시문** (system prompt) | "출처 번호를 붙여라" 한 문장 | 기획 · 운영 · 개발자 | 지시문 저장소 p1 · p2 · p3 |
| **흐름** (workflow) | 노드 · 연결 · 설정 (timeout, 재시도) | 개발자 | 워크플로우 JSON (git) |
| 데이터 | FAQ 문서, 검색 색인 | 문서 담당 | 70 · 71일차 (색인 버전) |
| 도구 | 외부 API의 응답 모양 | 다른 팀 | 72일차 (장애 · 계약) |
**모델도 버전입니다. 그리고 공급자가 바꿉니다.** API로 쓰는 모델은 내 서버에 있지 않습니다. 공급자(OpenAI · Anthropic · Google)는 같은 이름 뒤의 모델을 개선하기도 하고, 오래된 모델을 **지원 종료(deprecation)** 하기도 합니다.
| 모델 이름의 종류 | 예 | 특징 |
| --- | --- | --- |
| 별칭 (alias) | `gpt-4.1-mini` | 공급자가 가리키는 대상을 바꿀 수 있음. 편하지만 조용히 바뀔 수 있음 |
| 날짜가 붙은 스냅숏 | `gpt-4.1-mini-2025-04-14` | 그 시점의 모델로 고정. 재현성이 좋음. 그래도 언젠가 지원 종료 |
운영하는 쪽은 그래서 ① 답마다 **실제로 불린 모델 이름**을 남기고 ② 공급자의 지원 종료 일정을 지켜보고 ③ 모델을 바꿀 때 **다른 것은 바꾸지 않고 모델만** 바꿔 시험합니다 (2단계).
**지시문은 코드입니다.** 지시문 한 문장이 답의 길이 · 말투 · 정확도 · 안전을 바꿉니다 (73일차: "이 지시문을 보여 주지 않는다"). 그런데 지시문은 코드처럼 컴파일 오류가 나지 않아서, 고친 사람도 무엇을 망가뜨렸는지 모르기 쉽습니다. 그래서 지시문도 코드처럼 **버전 · 리뷰 · 테스트 · 배포**를 거칩니다.
**흐름의 작은 설정도 답을 바꿉니다.** 노드 하나(답을 400자에서 자르는 Code), timeout 하나, 재시도 횟수 하나가 사용자에게 보이는 결과를 바꿉니다. 화면에서는 잘 보이지 않는 칸이라 더 위험합니다.
**모델 지원 종료(deprecation)는 미리 알려 줍니다.** 공급자들은 보통 모델을 없애기 전에 지원 종료 날짜를 공지하고, 대체 모델을 권합니다. 문제는 공지를 **아무도 보지 않을 때**입니다. 운영하는 쪽이 할 일은 ① 지금 쓰는 모델 이름의 목록을 한 곳에서 관리하고(오늘 릴리스의 `model`) ② 공급자 공지를 정기적으로 확인하고 ③ 대체 모델로 바꾼 릴리스를 미리 게이트로 시험해 두는 것입니다. 모델 이름이 워크플로우 여러 곳에 흩어져 있으면, 바꿀 때 하나를 빠뜨려 그 경로만 멈춥니다.
**데이터와 도구도 버전입니다.** 오늘은 다루지 않지만 같은 원칙이 적용됩니다. RAG의 검색 색인을 다시 만들면 같은 질문에 다른 문서가 나옵니다 (71일차). 외부 API가 응답 모양을 바꾸면 Tool이 받는 값이 바뀝니다 (72일차). 그래서 성숙한 팀은 "이 답은 릴리스 1.1.0, 색인 v3, 도구 API v2로 만들어졌다"까지 남깁니다. 오늘은 그중 지시문 · 모델 · 흐름 세 가지를 다룹니다.
| 바뀌었다는 것을 아는 방법 | 모델 | 지시문 | 흐름 |
| --- | --- | --- | --- |
| 내가 바꿀 때 | 릴리스 기록 | 저장소의 새 버전 | git · `tver` |
| 남이 바꿀 때 | 공급자 공지, 응답의 모델 이름 | 저장소 기록(누가 · 언제) | n8n 화면에서 고친 것 → `tver pull` |
| 몰래 바뀔 때 | 같은 평가 세트 점수가 갑자기 달라짐 | (불변 버전이면 일어나지 않음) | 게시된 워크플로우와 저장된 파일이 다름 |
### 오늘 실습에서
**수업 준비 중에 실제로 일어난 일.** 10/18에 만든 v2와 릴리스 2.0.0은 작은 모델로 `gpt-4.1-nano`를 썼습니다. 10/27 다시 검증하려고 부르자 v2가 **HTTP 500**을 냈습니다. OpenAI가 그 모델을 지원 종료했기 때문입니다 (`The model gpt-4.1-nano has been deprecated`, 날짜가 붙은 `gpt-4.1-nano-2025-04-14`도 같음). 워크플로우는 한 글자도 바뀌지 않았는데 **모델이 사라져** 멈춘 것입니다. 오늘 자료는 `gpt-5.4-nano`로 바꿨습니다 (같은 OpenAI 키, `temperature 0` 됨).
| 모델 | 10/27 OpenAI 응답 |
| --- | --- |
| `gpt-4.1-nano` · `gpt-4.1-nano-2025-04-14` | 지원 종료 (deprecated) |
| `gpt-4.1-mini` | 됨 (`gpt-4.1-mini-2025-04-14`로 응답) |
| `gpt-5.4-nano` | 됨 (`gpt-5.4-nano-2026-03-17`로 응답) |
`gpt-4.1-mini`를 부르면 응답의 모델 이름이 `gpt-4.1-mini-2025-04-14`입니다. 별칭이 스냅숏을 가리키고 있다는 뜻입니다.
## 3. 두 버전의 차이 읽기 (2교시 Lab 01 예제 1)
### 일반 설명
**diff는 버전 관리의 눈입니다.** 두 버전의 차이를 보여 주는 도구를 diff라고 합니다. 코드 리뷰(pull request)는 diff를 사람이 읽고 승인하는 일입니다. 바뀐 것이 작고 분명할수록 리뷰가 쉽고, 사고가 나도 원인 찾기가 쉽습니다.
**글자 diff와 구조 diff.** n8n 워크플로우는 JSON입니다. JSON을 그대로 글자 비교(`git diff`)하면 노드 위치(`position`) · id · 순서 같은 **뜻 없는 차이**가 섞입니다. 노드 하나를 화면에서 옆으로 옮기기만 해도 diff가 생깁니다. 그래서 구조를 아는 diff가 필요합니다.
| | 글자 diff | 구조(노드 단위) diff |
| --- | --- | --- |
| 비교 단위 | 줄 | 노드 이름 · 설정 칸 · 연결 |
| 노드 위치를 옮기면 | 차이로 나옴 | 무시 |
| 지시문이 바뀌면 | 긴 한 줄이 바뀜 | "`Answer` 지시문 307 → 419자" |
| 읽는 사람 | 개발자 | 운영 · 기획도 |
업계 도구도 같은 방향입니다. 데이터 과학 노트북(`.ipynb`)은 nbdime 같은 도구로 셀 단위 diff를 보고, 인프라 설정(Terraform)은 `plan`으로 "무엇이 생기고 바뀌고 없어지는지"를 보여 줍니다. 7교시 `tver`는 n8n 워크플로우에서 같은 일을 합니다.
**한꺼번에 바꾸지 않습니다.** 한 버전에 변화가 여러 개면, 결과가 달라졌을 때 어느 변화 때문인지 모릅니다. 실험 설계의 기본 원칙(한 번에 한 변수)이고, 72일차 성능 튜닝의 "한 번에 하나만 바꾸고 다시 잰다"입니다. 변경을 작게 나누는 것이 리뷰 · 테스트 · 되돌리기를 모두 쉽게 합니다.
**diff를 읽는 순서.** 바뀐 것이 많을 때는 아무 줄부터 읽지 않고 순서를 정합니다. ① 노드가 더해지거나 빠졌나 (흐름이 크게 바뀜) ② 연결이 바뀌었나 (어느 길로 가는지) ③ 모델이 바뀌었나 ④ 지시문 · 코드가 바뀌었나 (가장 길고, 답에 직접 영향) ⑤ 나머지 설정 (timeout · 재시도 · 경로). 이렇게 읽으면 "무엇이 위험한 변경인가"가 먼저 보입니다.
**리뷰할 수 있는 크기.** 코드 리뷰 연구들은 한 번에 리뷰하는 변경이 커질수록 결함을 찾는 비율이 떨어진다고 보고합니다. 수백 줄짜리 변경은 대충 훑고 승인하게 됩니다. 워크플로우도 같습니다. "지시문 한 문장"과 "모델 교체"를 따로 내보내면 리뷰하는 사람이 각각을 제대로 볼 수 있습니다.
### 오늘 실습에서
`flow.py diff reference/helpdesk-v1.json reference/helpdesk-v2.json` (10/27):
```text
74 IT helpdesk v1 (provided) → 74 IT helpdesk v2 (provided)
+ node 'Review check' (code)
~ 'Answer' parameters.options.systemMessage: changed (307 → 419 chars)
~ 'Answer model' parameters.model.value: gpt-4.1-mini → gpt-5.4-nano
~ 'Question webhook' parameters.path: rel-v1 → rel-v2
+ link Answer → Review check (main)
+ link Review check → Build result (main)
- link Answer → Build result (main)
7 change(s)
```
| 바뀐 것 7개 | 셋 중 어디 |
| --- | --- |
| 지시문 307 → 419자 (`--full`: "출처 번호를 붙여라 · 짧게" 두 문장) | 지시문 |
| 모델 mini → nano | 모델 |
| 노드 `Review check`(400자에서 자름) + 연결 셋 | 흐름 |
| 웹훅 경로 `rel-v1` → `rel-v2` | 흐름 (답에는 영향 없음) |
`call` 두 번의 VPN 답: v1 "… 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다 …" (2.8초), v2 "… 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. **[FAQ-003]**" (1.0초). 출처 번호는 지시문 때문, 빨라진 것은 모델 때문일 것 **같지만**, 셋이 한꺼번에 바뀌어 한 번의 비교로는 모릅니다.
## 4. 흔들림과 바뀜 (2교시 Lab 01 예제 3)
### 일반 설명
**LLM은 같은 입력에도 조금씩 다르게 답합니다.** 모델은 다음 낱말의 확률을 계산하고 그중 하나를 고릅니다. `temperature`가 높으면 덜 확실한 낱말도 고르고, 0이면 가장 확실한 낱말을 고릅니다. 그런데 `temperature 0`이어도 공급자 서버의 병렬 계산 순서, 같이 처리된 다른 요청 등으로 **결과가 조금씩 다를 수 있습니다.** 그래서 LLM 앱의 결과는 "하나의 값"이 아니라 "분포"로 봐야 합니다.
**신호 대 잡음.** 두 버전을 비교할 때 보이는 차이에는 두 가지가 섞여 있습니다.
| | 신호 (signal) | 잡음 (noise) |
| --- | --- | --- |
| 뜻 | 버전이 바뀌어서 생긴 차이 | 같은 버전에서도 생기는 흔들림 |
| 반복하면 | 늘 같은 방향으로 나옴 | 나왔다 안 나왔다 함 |
| 오늘 예 | v2는 매번 `[FAQ-003]`을 붙임 | 해결 낱말을 가끔 다른 말로 씀 |
신호가 잡음보다 커야 "바뀌었다"고 말할 수 있습니다. 그래서 같은 케이스를 **여러 번** 돌리고(`--repeat 3`), 시간도 여러 번 재서(`bench -n 5`) 비교합니다. 한 번 보고 결론을 내리는 것은 동전을 한 번 던지고 "앞면이 잘 나오는 동전"이라고 하는 것과 같습니다.
**테스트는 글자 전체가 아니라 꼭 있어야 할 것을 봅니다.** 답 글자 전체를 정답과 비교하면 잡음 때문에 매번 실패합니다. 대신 꼭 있어야 할 낱말(해결 방법의 핵심, 출처 번호, 상태)과 없어야 할 것(떠넘기기, 개인정보)을 봅니다. 업계의 LLM 평가 도구(promptfoo, DeepEval, OpenAI Evals 등)도 "포함 · 불포함 · 형식 · 점수" 같은 **판정 규칙**으로 봅니다.
**흔들리는 테스트(flaky test)는 고칩니다.** 같은 버전에서 통과했다 실패했다 하는 테스트는 신호를 가립니다. 기대 낱말이 너무 좁은지(같은 뜻의 다른 말) 먼저 보고, 정말 답이 흔들리면 지시문을 더 분명하게 하거나 반복 기준을 둡니다 (2단계 예제 3).
**얼마나 반복하면 되나.** 반복 횟수는 "얼마나 작은 차이를 가르고 싶은가"로 정합니다. 출처 번호처럼 늘 붙거나 늘 안 붙는 큰 차이는 3번이면 충분합니다. "해결 낱말이 90%에서 95%로 늘었나" 같은 작은 차이는 같은 질문 수십 번, 또는 서로 다른 질문 수십 개가 있어야 말할 수 있습니다. 그래서 평가 세트는 질문 수가 많을수록, 반복이 많을수록 믿을 만해지지만 비용(LLM 호출)도 늘어납니다. 오늘은 수업 시간 때문에 5케이스 × 3번입니다.
**시간도 분포입니다.** 72일차에 본 것처럼 같은 요청도 매번 시간이 다릅니다. 그래서 "v2가 0.25초 빠르다"는 다섯 번의 p50끼리 비교한 것이고, 다른 날 · 다른 시간대에 재면 달라질 수 있습니다. 같은 날 · 같은 조건에서 번갈아 재는 것이 공정한 비교입니다.
### 오늘 실습에서
`lab01-compare` (5케이스)를 v1 · v2에 `--repeat 3`으로 (10/27):
| 케이스 | v1 (3번) | v2 (3번) |
| --- | --- | --- |
| VPN · 해결 낱말 "인증서" | ✔ ✔ ✔ | ✔ ✔ ✔ |
| VPN · 출처 번호 `[FAQ-003]` | ✘ ✘ ✘ | ✔ ✔ ✔ |
| 프린터 · 해결 낱말 | ✔ ✔ ✔ | ✔ ✔ ✔ |
| 프린터 · 출처 번호 `[FAQ-` | ✘ ✘ ✘ | ✔ ✔ ✔ |
| 접수 현황 "처리 중" | ✔ ✔ ✔ | ✔ ✔ ✔ |
| 합계 | 9/15 | 15/15 |
출처 번호는 세 번 모두 같은 쪽으로 나왔습니다. **흔들림이 아니라 바뀐 것**입니다. VPN `bench -n 5`: v1 p50 1.23초 · p95 1.34초, v2 p50 0.98초 · p95 1.20초. v2가 빠르지만 지시문 · 모델 · 노드가 다 바뀌었으니 **무엇 때문인지는 아직 모릅니다** → 2단계에서 한 가지씩만 바꾼 릴리스로.
## 5. 답마다 버전을 남긴다 (2교시 Lab 01 예제 2)
### 일반 설명
**출처 표시(provenance).** 결과물이 **어디서 · 무엇으로 · 어떻게** 만들어졌는지의 기록을 출처 표시라고 합니다. 식품의 원산지 표시, 소프트웨어 결과물의 빌드 번호와 같습니다. LLM 앱에서는 답 하나마다 "어느 릴리스 · 어느 지시문 · 어느 모델"이 그 답을 냈는지가 출처 표시입니다.
**어디에 남기나.**
| 남기는 곳 | 좋은 점 | 오늘 |
| --- | --- | --- |
| 응답 본문 (`version`) | 문의한 사람의 화면 · 캡처에도 남음, 테스트가 바로 확인 | 예 |
| 실행 로그 · 추적 (trace) | 모든 요청을 모아 셀 수 있음 (77일차) | n8n Executions |
| 응답 머리글 (header) | 본문 모양을 바꾸지 않음 | 하지 않음 |
OpenTelemetry 같은 추적 표준도 요청마다 "서비스 이름 · 버전 · 모델 이름"을 속성으로 붙이게 합니다. 나중에 "2.0.0이 답한 요청만 골라 오류율을 보자"가 가능해집니다 (3단계 카나리, 77일차 로그 분석).
**한 곳에서 정하고(single source of truth), 모두 읽는다.** 모델 이름을 모델 노드에 적고, 결과에 남길 이름을 또 따로 적으면 둘이 어긋날 수 있습니다. 누군가 노드에서만 모델을 바꾸면 **기록이 거짓말**을 합니다. 그래서 값을 한 곳(릴리스 정보)에 두고, 모델 노드와 결과가 **같은 곳을 읽게** 합니다. 70일차 설정 표에서 한 일과 같은 원칙입니다.
```mermaid
flowchart LR
R["Release info<br/>release · prompt_version · model"] --> M["Answer model<br/>model = 읽음"]
R --> B["Build result<br/>version = 읽음"]
M -.-> A{{"Answer"}} --> B
```
**버전이 있어야 나눠 셀 수 있습니다.** 카나리(3단계)로 1.1.0을 일부에게 내보냈다면, "1.1.0이 답한 요청의 오류율"과 "1.0.0이 답한 요청의 오류율"을 나눠 세야 1.1.0이 나쁜지 알 수 있습니다. 답에 버전이 없으면 섞인 숫자만 봅니다. 그래서 버전 기록은 디버깅뿐 아니라 **배포 판단의 재료**입니다.
**사용자에게 보여 줄까.** 답 본문에 버전을 넣으면 사용자 화면에도 보일 수 있습니다. 보통은 화면에는 숨기고 로그 · 응답 머리글에만 남기거나, "문의 번호" 같은 짧은 id만 보여 주고 그 id로 버전을 찾게 합니다. 오늘은 테스트로 확인하기 쉽게 본문에 넣었습니다.
### 오늘 실습에서
Lab 01 예제 2의 결과 (10/27, `lab01` 3/3):
```json
{"route": "자동 답변", "answer": "…", "sources": ["FAQ-003"],
"version": {"release": "1.0.0", "prompt_version": "p1", "model": "gpt-4.1-mini"}}
```
Lab 02부터 `version`에 `channel`(stable · pinned · canary)과 `prompt_source`(registry · fallback)가 더해집니다.
## 6. 지시문은 저장소로 (2교시 Lab 01 과제 1)
### 일반 설명
**설정을 코드 밖으로.** 웹 서비스를 만드는 오래된 원칙 중 하나가 "설정은 코드 밖에 둔다"입니다 (12-Factor App의 설정 원칙). 데이터베이스 주소나 기능 켜기 · 끄기처럼 **배포마다 · 자주 바뀌는 값**을 코드에 박아 두면, 값 하나 바꿀 때마다 코드를 다시 배포해야 합니다. LLM 앱에서 가장 자주 바뀌는 값이 지시문입니다.
**지시문 저장소(prompt registry)가 하는 일.**
| 기능 | 뜻 | 오늘 |
| --- | --- | --- |
| 이름 + 버전 | `helpdesk`의 p1 · p2 · p3 | `GET /prompts/helpdesk/p1` |
| 불변 버전 | 한 번 낸 버전은 고치지 않고 새 버전을 더함 | p1을 고치지 않음 |
| 목록 · 비교 | 어떤 버전들이 있고 무엇이 다른지 | `GET /prompts/helpdesk` |
| 누가 · 언제 | 바꾼 사람 · 시각 · 이유 | `CHANGELOG.md` (오늘은 사람이) |
| 꼬리표 (label) | "production", "staging"이 어느 버전인지 | 2단계 릴리스가 대신함 |
업계에는 이 일을 하는 도구가 여럿 있습니다: LangSmith의 Prompt Hub, Langfuse Prompt Management, PromptLayer, MLflow Prompt Registry 등. 회사 안에서는 사내 설정 서버 · 데이터베이스 · git 저장소의 폴더로 같은 일을 하기도 합니다. 도구가 달라도 **이름 + 불변 버전 + 꼬리표**라는 모양은 같습니다.
**불변(immutable)이 왜 중요한가.** 불변이란 "한 번 이름을 붙여 내보낸 버전은 **절대 고치지 않는다**"는 약속입니다. 고치고 싶으면 그 버전을 손대지 않고 **새 이름의 버전을 하나 더** 만듭니다. p1에 오탈자가 있어도 p1을 고치지 않고 p4를 만듭니다.
종이 문서로 생각하면 쉽습니다. 회사가 계약서를 고칠 때 이미 서명한 원본에 수정액을 바르지 않습니다. "변경 계약서 2호"를 새로 만들고 원본은 그대로 보관합니다. 나중에 "그때 무엇에 서명했나"를 물으면 원본을 꺼내 보여 줄 수 있어야 하기 때문입니다. 버전도 같습니다.
**덮어쓰면 무엇이 깨지나 (장면 하나).** 월요일에 p1로 헬프데스크가 돌았고, 화요일 아침 누군가 p1의 글을 "답을 한 문장으로" 바꿔 **같은 이름 p1으로 저장**했다고 해 봅니다.
| 화요일 오후에 생긴 일 | p1을 덮어썼을 때 | p1은 그대로 두고 p4를 더했을 때 |
| --- | --- | --- |
| "월요일 답은 친절했는데 오늘은 왜 짧죠?" 문의 | 월요일 답도 오늘 답도 `prompt_version: p1`. **기록으로는 아무것도 안 바뀐 것**처럼 보임 | 월요일 p1, 오늘 p4. 답의 `version`만 봐도 지시문이 바뀐 것이 보임 |
| 월요일 답을 다시 만들어 보기 (재현) | 월요일의 p1 글이 사라져 다시 만들 수 없음 | p1을 그대로 불러 같은 조건으로 다시 물어볼 수 있음 |
| 되돌리기 | 돌아갈 "예전 p1"이 없음. 기억이나 다른 사람 컴퓨터에서 찾아야 함 | 릴리스가 p1을 가리키게 바꾸면 끝 (3단계 "값 하나") |
| 어제 통과한 게이트 · 테스트 | "p1은 테스트를 통과했다"는 기록이 거짓이 됨 (통과한 건 예전 p1) | "p1 통과", "p4는 아직"이 그대로 맞음 |
| 두 사람이 동시에 p1을 고침 | 나중에 저장한 사람 것만 남고 앞사람 것은 조용히 사라짐 | p4 · p5가 따로 생겨 둘 다 남고, 무엇을 쓸지 고를 수 있음 |
핵심은 **이름이 내용을 대신한다**는 점입니다. 우리는 기록 · 테스트 결과 · 릴리스에 지시문 글 전체가 아니라 "p1"이라는 이름만 적습니다. 이름 뒤의 내용이 바뀔 수 있다면, 그 이름을 적은 모든 기록이 한꺼번에 믿을 수 없게 됩니다. 반대로 이름 뒤의 내용이 절대 안 바뀐다면, "p1"이라는 두 글자만으로 그때의 지시문을 정확히 가리킬 수 있습니다.
**불변이 주는 것 넷.**
| 얻는 것 | 뜻 | 오늘 실습에서 |
| --- | --- | --- |
| 재현성 | 같은 이름이면 언제 꺼내도 같은 내용 | 고정(`release`)으로 예전 릴리스에 다시 물어보기 (2단계) |
| 되돌리기 | 예전 버전이 그대로 있으니 가리키기만 바꾸면 됨 | `release helpdesk 1.0.0` 한 줄 (3단계) |
| 믿을 수 있는 기록 | "이 버전은 게이트 통과"라는 기록이 늘 맞음 | `/gates` 점검 기록, 배포 정책 (3단계 과제 2) |
| 비교 | 두 버전을 나란히 놓고 차이를 볼 수 있음 | `flow.py diff`, 7교시 `tver` |
**바뀌는 것은 "어느 버전을 쓰나"뿐입니다.** 불변이라고 해서 아무것도 못 바꾸는 것은 아닙니다. 버전 자체는 그대로 두고, **"지금 무엇을 쓰나"를 가리키는 표시**만 옮깁니다. 오늘 실습에서는 릴리스(1.0.0 → p1, 1.1.0 → p2)와 `active`가 그 표시입니다. 책장에 책(버전)은 그대로 꽂혀 있고, "오늘 읽을 책" 책갈피(릴리스 · `active`)만 옮겨 꽂는 것과 같습니다.
```mermaid
flowchart LR
subgraph 저장소["지시문 저장소 (고치지 않음, 더하기만)"]
P1["p1"]
P2["p2"]
P3["p3"]
P4["p4 (새로 더함)"]
end
A(["active 1.0.0"]) -->|가리킴| P1
A -.->|배포 · 되돌리기는<br/>가리키는 곳만 바꿈| P4
```
**같은 원칙을 쓰는 곳.**
| 어디 | 불변인 것 | 옮기는 표시 |
| --- | --- | --- |
| git | 커밋 (한 번 만든 커밋은 바뀌지 않음, 고치면 새 커밋) | 브랜치 (`main`이 가리키는 커밋이 옮겨 감) |
| 컨테이너 이미지 | 내용 해시(`sha256:…`)로 가리킨 이미지 | 태그 (`latest`는 옮겨 갈 수 있어 위험, 운영에서는 고정 태그 · 해시를 씀) |
| 패키지 (PyPI · npm) | 한 번 올린 `1.2.3` (같은 번호로 다시 올릴 수 없음) | "가장 새 버전" |
| 모델 API | 날짜가 붙은 스냅숏 `gpt-4.1-mini-2025-04-14` | 별칭 `gpt-4.1-mini` (공급자가 옮길 수 있음, 2절) |
| 오늘 실습 | 지시문 p1 · p2 · p3, 릴리스 1.0.0 · 1.1.0 … | 릴리스의 `prompt_version`, `active`, 카나리 |
**불변의 값.** 버전이 계속 쌓이므로 저장 공간이 늘고, 목록이 길어집니다. 그래서 오래 쓰지 않은 버전은 "보관(archive)"으로 옮기되 **지우지는 않는** 것이 보통입니다. 지시문 글은 작아서 쌓여도 부담이 거의 없습니다. 그리고 "고치지 않는다"는 사람의 약속만으로는 부족해서, 저장소가 **같은 이름으로 다시 저장하는 것을 막아야** 지켜집니다 (패키지 저장소가 같은 번호를 거절하듯이).
**나누면 생기는 일.**
| 얻는 것 | 새로 생기는 것 |
| --- | --- |
| 지시문만 따로 고치고 기록 | 부르는 곳이 하나 늘어남 → **멈출 수 있음** (7절) |
| 기획 · 운영이 n8n 없이 고침 | 누가 무엇을 바꿨는지 권한 · 리뷰가 필요 |
| 지시문 변경과 흐름 변경이 섞이지 않음 | 지시문과 흐름이 서로 맞는지(지시문이 기대하는 입력) 따로 확인 |
**누가 지시문을 고칠 수 있나.** 지시문이 저장소로 나가면 n8n을 모르는 사람(기획 · 운영 · 상담 담당)도 고칠 수 있게 됩니다. 이것은 장점이자 위험입니다. 그래서 저장소에는 권한(누가 새 버전을 만들 수 있나)과 리뷰(누가 승인하나)가 붙습니다. 그리고 새 지시문 버전을 만들었다고 바로 운영에 쓰이지 않게, **어느 버전을 쓸지는 릴리스가 정합니다** (2단계). 지시문 저장소는 "후보 목록", 릴리스는 "지금 쓰는 것"입니다.
**지시문에도 테스트를.** 지시문 버전마다 같은 평가 세트를 돌려 점수를 남겨 두면, "p3에서 해결 낱말이 사라졌다"를 사람이 읽기 전에 알 수 있습니다 (2단계 게이트). 업계의 프롬프트 관리 도구들은 대개 버전 화면 옆에 평가 결과를 같이 보여 줍니다.
### 오늘 실습에서
```bash
$ curl -s localhost:8800/prompts/helpdesk
{"name": "helpdesk", "versions": ["p1", "p2", "p3"]}
```
| 지시문 | 내용 (요지) |
| --- | --- |
| p1 | FAQ · 접수 건만 보고 3문장 이내, 지어내지 않음, 합니다체 |
| p2 | p1 + "FAQ에서 온 답이면 끝에 `[FAQ-003]`처럼 번호" |
| p3 | "한 문장으로, 데이터의 절차를 되풀이하지 말고 IT 포털을 보라고" (나쁜 지시문, 2단계) |
Lab 01 과제 1 (10/27): 예제 2 상태에서 `lab01-challenge` 2/3 (`prompt_source`가 없음) → 저장소에서 읽게 고친 뒤 3/3, `lab01`도 3/3.
## 7. 저장소가 멈추면: 기본 지시문 (2교시 Lab 01 과제 2)
### 일반 설명
**의존하는 것이 늘면 장애가 들어올 문도 늘어납니다.** 지시문을 저장소로 꺼내면 워크플로우는 질문마다 저장소를 부릅니다. 저장소가 99.9% 동작해도, 워크플로우가 부르는 곳이 다섯 군데면 모두 동작할 확률은 그보다 낮아집니다 (0.999⁵ ≈ 99.5%). 72일차에 본 그대로입니다: **부르는 곳마다 timeout과 실패했을 때의 길**이 있어야 합니다.
**실패했을 때 두 가지 길.**
| | 막는다 (fail-closed) | 계속 간다 (fail-open, graceful degradation) |
| --- | --- | --- |
| 뜻 | 확인할 수 없으면 거절 | 덜 좋은 방법으로라도 계속 |
| 어울리는 곳 | 보안 관문 (73일차 판별 LLM, 권한) | 편의 · 품질 관련 의존 (지시문, 추천, 캐시) |
| 오늘 | — | 지시문 저장소 → 기본 지시문 |
어느 쪽이 맞는지는 "멈췄을 때 계속 가면 무엇이 위험한가"로 정합니다. 판별 LLM 없이 계속 가면 공격이 지나갑니다(막아야 함). 지시문 저장소 없이 계속 가면 **조금 예전 지시문**으로 답할 뿐입니다(계속 가도 됨).
**마지막으로 잘 됐던 것(last known good).** 계속 가려면 "무엇으로" 계속 갈지가 있어야 합니다. 가장 최근에 **검증된** 설정을 미리 복사해 두는 것이 보통입니다. 설정 서버 클라이언트들은 마지막으로 받은 값을 캐시해 두고 서버가 멈추면 그것을 씁니다. 오늘은 p1과 같은 글을 워크플로우 안에 둡니다.
**오래 기다리지 않게.** 멈춘 서버는 바로 오류를 주지만, **느린** 서버는 응답이 올 때까지 붙잡아 둡니다. 사용자에게는 느린 것이 멈춘 것보다 나쁠 때가 많습니다. timeout은 "이만큼 기다려도 안 오면 실패로 본다"는 약속입니다.
**계속 갔다는 것을 남깁니다.** 기본 지시문으로 답했다면 `prompt_source: fallback`으로 남깁니다. 아무도 모르게 계속 가면, 저장소가 한 달 동안 멈춰 있어도 아무도 모릅니다 (77일차 로그 분석에서 셉니다).
```mermaid
flowchart LR
G["Get prompt<br/>timeout 2초"] -->|성공| R["저장소 지시문<br/>registry"]
G -->|500 · 시간 초과| F["기본 지시문 (p1)<br/>fallback"]
R --> A{{"Answer"}}
F --> A
```
**서킷 브레이커와 같은 생각.** 72일차의 서킷 브레이커는 자꾸 실패하는 곳을 잠시 부르지 않고 바로 대체 길로 가게 했습니다. 지시문 저장소가 계속 멈춰 있으면 요청마다 2초씩 기다렸다가 기본 지시문으로 가는 것도 낭비입니다. 운영에서는 "최근에 실패했으면 잠시 저장소를 부르지 않고 바로 기본 지시문"을 더하기도 합니다. 오늘은 timeout만 둡니다.
**기본값은 낡습니다.** 워크플로우 안에 복사해 둔 기본 지시문은 저장소의 지시문이 바뀌어도 그대로입니다. 기본값이 너무 오래되면 "멈췄을 때 나가는 답"이 운영의 답과 많이 달라집니다. 그래서 기본값도 **언제 마지막으로 맞췄는지** 기록하고, 릴리스를 바꿀 때 함께 확인하는 것이 좋습니다.
### 오늘 실습에서
`lab01-challenge2` (10/27):
| 케이스 | 고치기 전 (과제 1 상태) | 고친 뒤 |
| --- | --- | --- |
| 평소 · 저장소에서 | ✔ 1.3초 | ✔ |
| 저장소 고장 (`prompts error`) | ✘ **HTTP 500** `Error in workflow` (0.1초) | ✔ 200, `fallback`, "인증서" (1.3초) |
| 저장소 느림 (`prompts slow` 8초) | ✘ **9.1초** 기다림 (`registry`) | ✔ `fallback`, 3.0초 |
| 되돌린 뒤 | ✔ | ✔ `registry` |
| 합계 | 2/4 | 4/4 |
느림 케이스: 고치기 전에는 8초를 다 기다린 뒤 저장소의 지시문으로 답했습니다(답은 맞지만 9초). 고친 뒤에는 2초에 포기하고 기본 지시문으로 3초 안에 답했습니다.
이 기본 지시문에는 숨은 문제가 하나 있습니다. **"그런 지시문이 없다(404)"도 실패로 보고 기본 지시문으로 넘어갑니다.** 이 문제는 2단계 과제 2에서 다룹니다.
## 8. 버전 이름
### 일반 설명
**시맨틱 버전(SemVer).** 소프트웨어 버전 번호의 가장 흔한 규칙은 `주.부.수`(MAJOR.MINOR.PATCH)입니다.
| 자리 | 올리는 때 | 받는 쪽이 할 일 |
| --- | --- | --- |
| 주 (MAJOR) `2.0.0` | 하위 호환이 깨지는 변경 | 확인하고 맞춰야 함 |
| 부 (MINOR) `1.1.0` | 하위 호환인 기능 추가 · 개선 | 그대로 써도 됨 |
| 수 (PATCH) `1.0.1` | 버그 수정 | 그대로 써도 됨 |
LLM 앱에서는 "하위 호환"을 **답을 받는 쪽(사람 · 다음 시스템)이 그대로 받아들일 수 있나**로 읽습니다. 출처 번호를 끝에 붙이는 것은 부 버전, 모델을 통째로 바꾸는 것은 말투 · 길이 · 정확도가 크게 달라질 수 있으니 주 버전으로 둡니다. 정답이 있는 규칙이라기보다 **팀이 정하고 지키는 약속**입니다.
**릴리스 버전과 지시문 버전은 따로.** 지시문 p2는 1.1.0과 2.0.0 두 릴리스에서 같이 쓰입니다. 릴리스는 "지시문 + 모델 + 메모"의 묶음 이름이고, 지시문 버전은 지시문 자체의 이름입니다. 70일차 모듈 버전(같은 id = 하위 호환, 새 id = 깨지는 변경)과 같은 생각입니다.
**버전 번호는 약속입니다.** 받는 쪽이 버전 번호만 보고 "그대로 써도 되는지"를 판단할 수 있게 하는 것이 목적입니다. 그래서 팀 안에서 "지시문의 어떤 변경이 주 버전인가"를 미리 정해 두면 좋습니다. 예: 답의 형식(JSON 모양 · 길이 제한)이 바뀌면 주 버전, 말투 · 예시만 바뀌면 부 버전, 오탈자 수정은 수 버전.
### 오늘 실습에서
| 릴리스 | 지시문 | 모델 | 바뀐 것 (1.0.0과 비교) |
| --- | --- | --- | --- |
| 1.0.0 | p1 | gpt-4.1-mini | 첫 릴리스 |
| 1.1.0 | p2 | gpt-4.1-mini | 지시문만 (출처 번호) |
| 1.2.0 | p3 | gpt-4.1-mini | 지시문만 (한 문장 · 포털로) |
| 2.0.0 | p2 | gpt-5.4-nano | 모델만 (1.1.0과 비교하면) |
| 2.1.0 | p9 | gpt-4.1-mini | 저장소에 없는 지시문 (2단계 과제 2) |
2.1.0은 일부러 저장소에 없는 지시문(p9)을 가리키게 만든 망가진 릴리스입니다. 왜 위험한지는 2단계 8절에서 봅니다.
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
오늘도 요청문의 뼈대는 같습니다: **무엇을 · 어디에 · 어떤 모양으로 · 무엇은 그대로 · 무엇으로 확인**. 버전 요청에서는 하나가 더 붙습니다: **무엇이 워크플로우 안에 남고 무엇이 밖으로 나가는가**.
아래 표의 열은 이 단계에서 에이전트를 쓰는 실습, 줄은 위 다섯 칸입니다. 각 칸에 그 실습에서 채울 것을 적었습니다.
| 요청문에 | Lab 01 예제 2 | Lab 01 과제 2 |
| --- | --- | --- |
| 무엇을 | 릴리스 정보 · `version` | 저장소 실패 시 기본 지시문 |
| 어디에 | `Has question?` 뒤 Set, `Build result` | `Get prompt`의 오류 출력 |
| 어떤 모양으로 | `version: {release, prompt_version, model}` | `prompt_source: registry \| fallback` |
| 그대로 | 지시문 | 저장소에서 읽는 성공 쪽 |
| 확인 | `cases/lab01.json` | `cases/lab01-challenge2.json` |
예를 들어 Lab 01 예제 2 열을 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
릴리스 정보와 version 을 만들어 줘. ← 무엇을
Has question? 뒤에 Set(Release info), 그리고 Build result 에. ← 어디에
결과에 version: {release, prompt_version, model}. ← 어떤 모양으로
지시문은 그대로. ← 그대로
cases/lab01.json 이 통과해야 해. ← 확인
```
## 10. 자주 하는 실수
| 실수 | 왜 문제인가 | 이렇게 |
| --- | --- | --- |
| 한 번에 지시문 · 모델 · 노드를 다 바꿈 | 결과가 달라져도 원인을 모름 | 한 번에 하나, 바뀐 것을 `CHANGELOG.md`에 |
| 한 번 돌려 보고 "좋아졌다" | 흔들림일 수 있음 | `--repeat 3`, `bench -n 5` |
| 모델 이름을 노드와 결과에 따로 적음 | 둘이 어긋나면 기록이 거짓말 | 한 곳(릴리스 정보)에서 읽게 |
| 지시문 p1을 고쳐서 덮어씀 | 어제 답을 다시 만들 수 없고 되돌릴 곳이 없음 | 새 버전(p4)을 더함 |
| 저장소를 부르면서 timeout · 오류 길이 없음 | 저장소가 멈추면 전체가 500, 느리면 사용자가 기다림 | timeout + 기본 지시문 + `fallback` 기록 |
| 기본 지시문으로 답한 것을 남기지 않음 | 저장소가 멈춘 줄 아무도 모름 | `prompt_source` |
## 확인 질문
1. 일반 프로그램과 달리 LLM 워크플로우에서 "코드가 같아도" 답이 바뀌는 까닭 두 가지는?
2. v1 → v2에서 답 끝에 출처 번호가 붙었습니다. 이것이 지시문 때문이라고 **확실히** 말하려면 무엇이 더 필요한가요?
3. 같은 케이스를 3번 돌렸더니 ✔ ✘ ✔가 나왔습니다. 이 케이스로 "v2가 더 낫다"고 말할 수 있나요?
4. 모델 이름을 모델 노드에 박아 두고 결과에도 따로 적으면 어떤 사고가 날 수 있나요?
5. 지시문 저장소가 멈췄을 때 막지 않고 기본 지시문으로 계속 가는 것이 맞는 까닭은? 73일차 판별 LLM과 무엇이 다른가요?
6. 수업 준비 때(10/27) `gpt-4.1-nano`로 된 v2가 500을 냈습니다. 워크플로우를 하나도 바꾸지 않았는데 왜 멈췄나요? 운영에서는 이것을 어떻게 미리 알 수 있을까요?
## 정리
- [ ] 바뀌는 것 셋: 모델 · 지시문 · 흐름 — 따로 관리한다 (모델은 공급자도 바꾼다)
- [ ] 노드 단위 diff로 읽고, 한꺼번에 바꾸지 않는다
- [ ] 흔들림과 바뀜은 반복해서 재야 가른다
- [ ] 답마다 `version`, 값은 한 곳에서 정하고 모두 읽는다
- [ ] 지시문은 저장소에서 불변 버전으로, 저장소가 멈추면 기본 지시문 + `fallback` 기록
+382
View File
@@ -0,0 +1,382 @@
# 2단계 · 릴리스와 배포 전 게이트
- 교시: 3교시 강의 (11:00~11:50) · 4교시 [Lab 02](../../Last3Mon/74/LAB-02-Q-RELEASE-GATE.md) 실습 5개 (12:00~12:50)
- 이 단계가 끝나면: 지시문과 모델을 릴리스로 묶어 밖에서 관리하고, 후보 릴리스를 운영을 건드리지 않고 시험하고, 게이트로 배포 여부를 숫자로 정할 수 있다. 게이트의 기준을 재서 정하고, 게이트가 약하면 나쁜 릴리스도 통과한다는 것을 알고 테스트를 강하게 고친다. 망가진 릴리스는 장애와 구분해 크게 실패하게 한다.
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/27 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/74/answers/LAB-TEST-RESULT.md`에 있습니다.
- 실습 가리키기: "Lab 02 예제 1"은 4교시 [Lab 02 문서](../../Last3Mon/74/LAB-02-Q-RELEASE-GATE.md)의 그 절입니다.
```mermaid
flowchart LR
A["1. 릴리스"] --> B["2. 설정을 밖에서 읽기"] --> C["3. 후보 고정"] --> D["4. 게이트"] --> E["5. 게이트는<br/>테스트만큼만"]
E --> F["6. 기준선"] --> G["7. 작은 모델 판단"] --> H["8. 망가진 릴리스"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
| 시간 | 절 · 개요 | 하는 일 |
| --- | --- | --- |
| 11:00~11:06 | 1 · 개요 15장 | 릴리스 = 지시문 + 모델, 한 릴리스에 한 가지, 불변 결과물 |
| 11:06~11:12 | 2 · 개요 16장 | 설정과 코드의 분리, 원격 설정 · 기능 플래그 |
| 11:12~11:17 | 3 · 개요 16장 | 후보 시험: 고정(pin) · 스테이징 · 그림자 |
| 11:17~11:24 | 4 · 개요 17장 | 품질 게이트, CI/CD, 평가 세트(golden set) |
| 11:24~11:31 | 5 · 개요 18장 | 게이트는 테스트만큼만, 기대는 데이터에서 |
| 11:31~11:36 | 6 · 개요 19장 | 기준선, 흔들리는 테스트, SLO |
| 11:36~11:40 | 7 · 개요 20장 | 모델 교체 판단: 품질 · 속도 · 비용 |
| 11:40~11:46 | 8 · 개요 21장 | 장애와 설정 오류, fail fast |
| 11:46~11:50 | 9 · 10 · 확인 질문 | 요청문, 자주 하는 실수 |
### 4교시 Lab 02에서 이어서 하는 것
| 4교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
| --- | --- | --- | --- |
| 시작 (12:00) | — | 릴리스 점검표 복사 | `cp templates/RELEASE-CHECKLIST-TEMPLATE.md my-work/RELEASE.md` |
| Lab 02 예제 1 (12:00~12:12) | 1 · 2 · 3 | 운영 릴리스를 데이터 API에서 읽고, 본문 `release`면 고정 | `my-work/helpdesk.json`, 테스트 `cases/lab02.json` |
| Lab 02 예제 2 (12:12~12:20, 에이전트 없이) | 4 · 5 | 약한 golden으로 네 릴리스 게이트, 1.2.0 답 읽기 | `gate cases/golden.json --set release=...` × 4 → `RELEASE.md` 1절 |
| Lab 02 예제 3 (12:20~12:27, 에이전트 없이) | 6 · 7 | 1.0.0 · 2.0.0 시간, golden 3번 반복 | `bench my-helpdesk requests/faq-vpn.json -n 5 --set release=...`, `gate ... --repeat 3` |
| Lab 02 과제 1 (12:27~12:40) | 5 · 7 | 1.2.0을 막는 `my-golden`, 2.0.0 판단 | `cases/my-golden.json`, `gate` × 4 |
| Lab 02 과제 2 (12:40~12:50) | 8 | 망가진 릴리스 2.1.0은 400, 장애는 기본 지시문 | 테스트 `cases/lab02-challenge2.json`, `gate cases/my-golden.json --set release=2.1.0` |
## 1. 릴리스 = 지시문 + 모델 (+ 메모)
### 일반 설명
**릴리스(release)는 "함께 내보내는 것의 묶음"에 붙인 이름입니다.** 일반 소프트웨어에서 릴리스는 빌드된 결과물(artifact: 실행 파일, 컨테이너 이미지)에 버전 번호를 붙인 것입니다. 결과물은 **한 번 만들면 바꾸지 않습니다(불변).** 같은 버전 번호면 언제 꺼내도 같은 것이어야, 시험한 것과 배포한 것이 같다고 믿을 수 있습니다.
LLM 앱의 릴리스는 "흐름 + 지시문 + 모델 + 그 밖의 설정(temperature, 검색 개수 …)"의 묶음입니다. 오늘은 흐름(워크플로우)은 그대로 두고 **지시문 버전 + 모델 이름**을 묶어 릴리스로 부릅니다.
| 일반 소프트웨어 | 오늘의 릴리스 |
| --- | --- |
| 결과물 (이미지 · 실행 파일) | `{prompt_version, model, note}` |
| 버전 번호 | `1.0.0` · `1.1.0` · `2.0.0` |
| 릴리스 노트 | `note`, `CHANGELOG.md` |
| 결과물 저장소 (registry) | 데이터 API `/releases/helpdesk` |
**한 릴리스에 한 가지 변화.** 1단계에서 v2는 셋이 한꺼번에 바뀌어 원인을 알 수 없었습니다. 릴리스를 **한 가지씩만** 다르게 만들면, 두 릴리스를 비교했을 때 차이는 그 한 가지 때문입니다. 작고 잦은 릴리스가 크고 드문 릴리스보다 안전한 까닭입니다 (문제가 생겨도 원인이 작고, 되돌릴 것도 작음).
**릴리스에 무엇을 넣을까.** 오늘은 지시문 버전과 모델 이름 둘이지만, 실제로는 답에 영향을 주는 설정을 모두 묶습니다: temperature, 최대 길이, 검색할 문서 수, 색인 버전, 도구 목록. 묶음에 빠진 설정이 있으면, 그 설정이 바뀌었을 때 "릴리스는 같은데 답이 다르다"가 생깁니다. 반대로 너무 많이 묶으면 사소한 변경에도 새 릴리스를 만들어야 합니다. 기준은 **"이것이 바뀌면 다시 시험해야 하나"**입니다.
**릴리스 노트.** 각 릴리스에는 "무엇이 왜 바뀌었는지" 한두 줄을 남깁니다 (오늘 `note`). 몇 달 뒤 "2.0.0은 왜 만들었지?"에 답하는 것이 이 한 줄입니다. 좋은 릴리스 노트는 변경 · 이유 · 영향(누가 무엇을 다르게 받나) · 시험 결과를 담습니다.
### 오늘 실습에서
| 릴리스 | 지시문 | 모델 | 1.0.0과 다른 것 |
| --- | --- | --- | --- |
| 1.0.0 | p1 | gpt-4.1-mini | (기준) |
| 1.1.0 | p2 | gpt-4.1-mini | **지시문만** (출처 번호) |
| 1.2.0 | p3 | gpt-4.1-mini | **지시문만** (한 문장 · 포털로) |
| 2.0.0 | p2 | gpt-5.4-nano | 1.1.0과 비교하면 **모델만** |
| 2.1.0 | p9 | gpt-4.1-mini | 저장소에 없는 지시문 (8절) |
## 2. 설정을 밖에서 읽는다 (4교시 Lab 02 예제 1)
### 일반 설명
**설정과 코드의 분리.** Lab 01의 릴리스 정보는 워크플로우 안의 Set 노드였습니다. 1.1.0을 내보내려면 값을 고치고 push 해야 합니다. 이것은 "설정이 코드 안에 있는" 상태입니다. 값을 **밖(데이터 API)에서 매번 읽게** 하면 배포는 그 값을 바꾸는 일이 됩니다.
**업계에서는 이것을 원격 설정 · 기능 플래그라고 부릅니다.**
| 이름 | 하는 일 | 대표 도구 |
| --- | --- | --- |
| 원격 설정 (remote config) | 앱을 다시 배포하지 않고 값을 바꿈 | Firebase Remote Config, AWS AppConfig |
| 기능 플래그 (feature flag) | 기능을 켜고 끄거나 일부에게만 켬 | LaunchDarkly, Unleash, Flagsmith |
| 모델 · 지시문 라우팅 | 요청마다 어느 모델 · 지시문을 쓸지 | LLM 게이트웨이 (LiteLLM, Portkey 등) |
이 도구들은 공통으로 ① 값을 한 곳에 두고 ② 앱이 짧은 간격으로(또는 요청마다) 읽고 ③ 바꾼 기록을 남기고 ④ 일부 사용자에게만 다른 값을 줄 수 있게 합니다 (④는 3단계 카나리).
**고르는 규칙은 코드가.** "본문에 `release`가 있으면 그것, 없으면 `active`"는 틀리면 안 되는 규칙입니다. 73일차 "틀리면 안 되는 값은 LLM이 정하지 않는다"처럼 Code 노드가 정합니다.
**읽는 비용과 캐시.** 요청마다 설정을 읽으면 바꾼 즉시 반영되지만, 설정 서버를 그만큼 자주 부릅니다. 그래서 많은 도구가 설정을 몇 초 ~ 몇 분 캐시합니다. 캐시가 길면 배포 · 되돌리기가 그만큼 늦게 반영됩니다. 오늘은 요청마다 읽어 "다음 요청부터 바로"를 확인합니다. 운영에서는 "되돌리기가 몇 초 안에 반영돼야 하나"로 캐시 시간을 정합니다.
**설정이 바뀐 기록.** 원격 설정 도구들은 값을 바꿀 때마다 누가 · 언제 · 무엇을 바꿨는지 남깁니다. 배포가 "값 하나"가 되면 배포 기록은 곧 **설정 변경 기록**입니다. 오늘 데이터 API는 지금 값만 들고 있고 기록은 남기지 않습니다 — 그래서 `CHANGELOG.md`에 사람이 적고, 3단계 과제 2에서 점검 기록을 데이터 API에 남깁니다.
### 오늘 실습에서
```mermaid
flowchart LR
G["GET /releases/helpdesk"] --> P["Pick release (Code)<br/>pinned > active"] --> K{"Known release?"}
K -->|예| GP["Get prompt<br/>prompt_version"] --> A{{"Answer<br/>model"}}
K -->|아니오| R(["400 unknown release"])
```
Lab 02 예제 1 (10/27): 고치기 전(Lab 01 과제 2 상태) `lab02` 1/5 — 고정 케이스가 모두 1.0.0으로 답함. 고친 뒤 5/5, `lab01-challenge` 3/3 · `lab01-challenge2` 4/4 그대로.
## 3. 후보는 고정해서 시험한다
### 일반 설명
**운영을 건드리지 않고 새 버전을 시험하는 방법들.**
| 방법 | 뜻 | 좋은 점 | 비용 |
| --- | --- | --- | --- |
| 스테이징 (staging) | 운영과 같은 모양의 별도 환경 | 운영과 완전히 분리 | 환경을 하나 더 유지 |
| 고정 (pin) | 같은 운영 환경에서 **요청이 버전을 지정** | 환경 하나로 충분, 바로 시험 | 지정하는 문을 아무에게나 열면 위험 |
| 그림자 (shadow) | 실제 요청을 새 버전에도 보내고 답은 버리고 비교만 | 실제 질문으로 시험 | 요청마다 두 배 비용 |
오늘은 **고정**을 씁니다. 본문에 `release: "1.1.0"`을 넣은 요청만 1.1.0으로 답하고, 나머지 운영 요청은 계속 1.0.0입니다. `flow.py test --set release=1.1.0`이 테스트의 모든 요청에 이 칸을 더합니다.
**고정의 문은 좁게.** 누구나 `release`를 지정할 수 있으면, 아직 검증 안 된 릴리스(또는 망가진 릴리스)를 아무나 쓸 수 있습니다. 73일차 관점으로는 **공격면**이 하나 늘어난 것입니다. 실제 서비스에서는 이 칸을 운영자 · 테스트 계정만 쓸 수 있게 막습니다 (인증된 사용자의 권한으로 판단).
**없는 버전은 거절한다.** 모르는 릴리스를 지정했을 때 조용히 `active`로 답하면, 테스트는 엉뚱한 릴리스를 시험하고 PASS를 받습니다. 400 `unknown release`로 **분명히 실패**해야 테스트를 쓴 사람이 오타를 압니다 (8절과 같은 원칙).
**환경을 나누는 까닭.** 개발 · 스테이징 · 운영을 나누는 것은 "시험하다 운영을 망가뜨리지 않기" 위해서입니다. 하지만 환경이 늘면 환경끼리 설정이 어긋납니다 (스테이징에서는 됐는데 운영에서는 안 됨). 고정(pin)은 운영 환경 **그대로** 후보를 시험하므로 이 어긋남이 없습니다. 대신 후보가 운영의 데이터 · 외부 시스템을 실제로 건드립니다 — 오늘 헬프데스크는 읽기만 하니 괜찮지만, 접수 건을 **만드는** 워크플로우라면 시험 요청에 "시험용" 표시를 붙여 실제로 만들지 않게 해야 합니다.
| 후보 시험이 건드리는 것 | 오늘 | 조심할 때 |
| --- | --- | --- |
| 읽기 (FAQ · 접수 건 조회) | 예 | 괜찮음 |
| 쓰기 (접수 만들기 · 메일 보내기) | 없음 | 시험 표시 · 가짜 대상 · 그림자로 |
| 비용 (LLM 호출) | 게이트 한 번에 6~9번 | 반복 · 여러 릴리스면 곱해짐 (75일차) |
### 오늘 실습에서
| `lab02` 케이스 | 기대 | 결과 (10/27) |
| --- | --- | --- |
| 운영 중인 릴리스 | 1.0.0 · `stable` · p1 | ✔ |
| 1.1.0 고정 | 1.1.0 · `pinned` · p2 · `[FAQ-003]` | ✔ |
| 2.0.0 고정 | `gpt-5.4-nano` | ✔ |
| 없는 릴리스 9.9.9 | 400, `known` | ✔ |
| 빈 질문 | 400 | ✔ |
## 4. 게이트: 배포 전에 숫자로 (4교시 Lab 02 예제 2)
### 일반 설명
**품질 게이트(quality gate).** 소프트웨어 배포 파이프라인(CI/CD)에는 "여기를 지나야 다음 단계로"인 관문이 여러 개 있습니다: 빌드가 되나, 단위 테스트를 통과하나, 보안 검사, 성능 기준. 각 관문은 **기준을 숫자로** 정하고 PASS/FAIL을 냅니다. 사람의 "괜찮아 보이는데요"를 숫자로 바꾸는 장치입니다.
```mermaid
flowchart LR
B["빌드"] --> U["단위 테스트"] --> S["보안 검사"] --> Q["품질 게이트<br/>평가 세트"] --> D["배포"]
Q -->|FAIL| X(["멈춤"])
```
**LLM 앱의 게이트는 평가 세트(evaluation set)로.** 정해 둔 질문과 기대(판정 규칙)의 모음을 평가 세트, 또는 **골든 세트(golden set)**라고 부릅니다. 새 지시문 · 모델을 내보내기 전에 평가 세트를 돌려 기준을 넘는지 봅니다. promptfoo, DeepEval, Ragas, OpenAI Evals, LangSmith Evaluation 같은 도구가 이 일을 합니다. 도구가 달라도 구조는 같습니다: **질문 목록 + 판정 규칙 + 기준(통과율 · 점수 · 시간) + 결과 기록**.
| 게이트 기준 | 뜻 | 오늘 |
| --- | --- | --- |
| 통과율 | 케이스 중 통과한 비율 | `--min-pass 1.0` (모두) |
| 처리시간 | 케이스 시간의 p95 | `--max-p95 3` (초) |
| 종료 코드 | 자동화가 다음 단계를 정함 | PASS 0 · FAIL 1 |
| 기록 | 누가 언제 무엇으로 게이트를 돌렸나 | `/gates/helpdesk` (3단계 과제 2에서 씀) |
**게이트는 자동화의 문.** 종료 코드가 0이면 다음 단계(배포)가 이어지고, 1이면 멈춥니다. 사람이 결과를 읽지 않아도 파이프라인이 멈춥니다. 그래서 게이트가 **무엇을 확인하는지**가 매우 중요합니다 (5절).
**평가 세트를 어떻게 모으나.** 처음에는 대표 질문 몇십 개로 시작하고, 운영하면서 **실제로 문제가 됐던 질문**을 더해 갑니다. 사고가 났던 질문을 평가 세트에 넣으면 같은 사고가 다시 나지 않는지 늘 확인할 수 있습니다 (회귀 테스트). 평가 세트는 한 번 만들고 끝나는 것이 아니라 **서비스와 함께 자라는 자산**입니다.
| 평가 세트에 넣을 것 | 예 |
| --- | --- |
| 자주 오는 질문 | VPN · 비밀번호 · 프린터 |
| 꼭 지켜야 할 것 | 접수 상태 · 예정일을 정확히, 지어내지 않음 |
| 사고가 났던 질문 | "포털만 보라"는 답이 나갔던 프린터 질문 |
| 경계 · 예외 | 빈 질문, 없는 접수 번호, 남의 건 (73일차) |
### 오늘 실습에서
운영팀의 `golden.json`(6케이스, `status 200` · `route 자동 답변`만 확인)으로 네 릴리스 (10/27):
| 릴리스 | 결과 | p95 |
| --- | --- | --- |
| 1.0.0 | 6/6 PASS | 1.6초 |
| 1.1.0 | 6/6 PASS | 1.1초 |
| **1.2.0** | **6/6 PASS** | 1.5초 |
| 2.0.0 | 6/6 PASS | 2.0초 |
1.2.0의 프린터 답: **"IT 포털에서 프린터 문제 해결 방법을 확인해 주세요."** — HTTP 200, `자동 답변`. 게이트는 PASS입니다.
## 5. 게이트는 테스트만큼만 막는다 (4교시 Lab 02 과제 1)
### 일반 설명
**PASS의 뜻은 테스트가 정합니다.** 게이트는 테스트를 돌릴 뿐입니다. 테스트가 "200이 왔나"만 보면 PASS는 "200이 왔다"는 뜻입니다. 나쁜 릴리스를 막지 못한 게이트는 게이트가 고장 난 것이 아니라 **테스트가 약한 것**입니다.
**테스트 오라클 문제.** 테스트를 쓰려면 "맞는 답이 무엇인지"를 알아야 합니다. 이것을 오라클(oracle)이라고 합니다. LLM 답은 정답 글자가 하나가 아니라서 오라클을 정하기 어렵습니다. 그래서 정답 전체 대신 **판정 규칙**을 씁니다.
| 판정 규칙 | 예 | 잡는 것 |
| --- | --- | --- |
| 꼭 있어야 할 낱말 (`contains`) | VPN 답에 "인증서" | 해결 방법을 빼먹은 답, 떠넘기기 |
| 없어야 할 말 (`not_contains`) | "포털을 확인" | 특정 문구 (글자가 정확해야) |
| 값 비교 (`equals`) | `route` = 자동 답변 | 갈래가 틀림 |
| 형식 | JSON · 길이 | 모양이 깨짐 |
| LLM 채점 | "이 답이 질문을 해결하나 1~5점" | 글자로 못 잡는 품질 (79일차, 채점도 흔들림) |
**기대는 데이터에서, 사람이.** 기대 낱말을 지금 답에서 베끼면, 지금 답이 틀린 것도 정답이 됩니다. 기대는 **원천 데이터**(FAQ의 해결 방법, 접수 건의 상태 · 예정일)를 보고 사람이 정합니다. 에이전트에게 테스트 파일을 쓰게 해도 되지만, 기대 낱말은 사람이 정해 요청문에 적습니다.
**굿하트의 법칙.** "숫자가 목표가 되면 그 숫자는 더 이상 좋은 숫자가 아니다." 게이트를 통과시키려고 테스트를 느슨하게 고치거나, 테스트에만 맞춘 지시문을 쓰면 게이트는 의미를 잃습니다. 그래서 오늘 규칙: **테스트를 통과시키려고 기대 결과를 바꾸지 않는다** (AGENTS.md 8절).
**좋은 판정 규칙의 조건.** ① 맞는 답이면 통과한다 (같은 뜻의 다른 표현도) ② 틀린 답이면 실패한다 ③ 같은 답이면 늘 같은 판정이다. ①이 약하면 정상 릴리스가 FAIL(지나치게 막기), ②가 약하면 나쁜 릴리스가 PASS(덜 막기), ③이 약하면 흔들리는 테스트. 73일차 보안 테스트의 "막을 것 · 막지 말 것"과 같은 균형입니다.
### 오늘 실습에서
강한 golden(`my-golden`, 9케이스: 해결 낱말 6 · 상태 · 예정일 · 떠넘기기 금지) (10/27):
| 릴리스 | 약한 golden | 강한 golden | p95 |
| --- | --- | --- | --- |
| 1.0.0 | 6/6 PASS | 9/9 PASS | 1.4초 |
| 1.1.0 | 6/6 PASS | 9/9 PASS | 1.7초 |
| **1.2.0** | **6/6 PASS** | **1/9 FAIL** | 1.3초 |
| 2.0.0 | 6/6 PASS | 9/9 PASS | 1.3초 |
1.2.0이 통과한 하나는 **"포털로 떠넘기지 않기"**(`not_contains: "IT 포털을 확인" · "포털을 참고"`)입니다. 실제 답은 "IT 포털**에서** … 확인해 주세요"라서 걸리지 않았습니다. 금지 문구는 글자가 정확해야 하고, 실제로 1.2.0을 막은 것은 **해결 낱말(`contains`)**이었습니다.
## 6. 기준선: 기준은 재서 정한다 (4교시 Lab 02 예제 3)
### 일반 설명
**기준선(baseline).** "3초 안"이라는 기준은 지금 운영 중인 버전이 보통 몇 초인지 알아야 의미가 있습니다. 성능 · 품질 기준은 **지금을 재서** 정하고, 흔들림만큼 여유를 둡니다.
| 기준을 정하는 법 | 예 | 특징 |
| --- | --- | --- |
| 감으로 | "3초면 되겠지" | 근거 없음, 너무 느슨하거나 빡빡함 |
| 기준선 대비 | "1.0.0 p95의 1.5배 이내" | 지금보다 많이 나빠지는 것만 막음 |
| 서비스 목표(SLO)에서 | "문의 95%는 5초 안에 답" | 사용자에게 한 약속에서 내려옴 (78일차) |
| 회귀 기준 | "1.0.0보다 통과율이 떨어지면 FAIL" | 새 버전이 나빠지지 않았는지 |
**흔들리는 테스트는 게이트를 무너뜨립니다.** 같은 릴리스로 돌려도 가끔 FAIL이 나는 테스트가 있으면, 사람들은 FAIL을 보고 "또 흔들렸네" 하고 다시 돌립니다. 그러다 진짜 FAIL도 무시하게 됩니다. 그래서 기준을 정할 때 **같은 릴리스로 여러 번** 돌려 흔들리지 않는지 먼저 봅니다.
**재는 횟수.** 다섯 번으로 잰 p95는 다섯 개 중 가장 느린 값입니다 (72일차 nearest-rank). 한 번 튄 값이 그대로 기준이 됩니다. 실제 기준을 정할 때는 수십 번 이상 재고, 다른 시간대에도 잽니다. 오늘은 수업 시간과 비용 때문에 다섯 번입니다.
**흔들림을 줄이는 방법.** 흔들리는 케이스를 찾으면 바로 지우지 않고 원인을 봅니다. 기대 낱말이 너무 좁으면(같은 뜻의 다른 말) 낱말을 더하고, 질문이 애매하면(두 FAQ에 걸침) 질문을 분명하게 하고, 정말 모델이 흔들리면 반복 기준("3번 중 3번")을 둡니다. 흔들리는 케이스를 그냥 지우면 평가 세트가 점점 쉬운 질문만 남습니다.
### 오늘 실습에서
VPN `bench -n 5` (10/27, 같은 워크플로우에서 `--set release`만 바꿈):
| 릴리스 | 모델 | p50 | p95 |
| --- | --- | --- | --- |
| 1.0.0 | gpt-4.1-mini | 1.23초 | 1.42초 |
| 2.0.0 | gpt-5.4-nano | 1.12초 | 1.24초 |
1.0.0의 golden을 `--repeat 3`: **18/18**, p95 1.5초. 흔들리지 않으므로 이 golden의 FAIL은 믿을 수 있습니다. 1.0.0 p95가 1.4~1.6초 근처이니 `--max-p95 3`은 약 두 배 여유입니다.
## 7. 작은 모델로 바꿔도 될까
### 일반 설명
**모델 교체는 세 가지 저울.** 더 작은 · 싼 모델로 바꾸는 결정은 품질 · 속도 · 비용을 함께 봅니다.
| 저울 | 재는 법 | 오늘 |
| --- | --- | --- |
| 품질 | 같은 평가 세트의 통과율 | 강한 golden |
| 속도 | p50 · p95 | `bench` |
| 비용 | 요청당 토큰 × 단가 | 75일차 |
**게이트를 통과했다 = 게이트가 보는 것은 괜찮다.** 평가 세트에 없는 것(말투 · 길이 · 드문 질문 · 긴 대화 · 다른 언어)은 게이트가 보지 않습니다. 그래서 모델 교체는 바로 전원에게가 아니라 **일부에게 먼저**(카나리, 3단계) 내보내고 실제 사용을 봅니다.
**공급자의 모델 일정도 판단에 들어갑니다.** 1단계에서 본 것처럼 모델은 지원 종료됩니다. 지금 쓰는 모델의 종료 일정, 대체 모델로 옮기는 데 드는 시험 비용도 판단 재료입니다. 10/27 수업 준비 때 `gpt-4.1-nano`가 그랬습니다.
**같은 값이면 더 싼 것.** 품질 기준을 넘는 모델이 여럿이면 더 싸고 빠른 것을 고릅니다. 그런데 "기준을 넘는다"는 평가 세트 안의 이야기입니다. 작은 모델은 평가 세트에 없는 어려운 질문 · 긴 대화 · 드문 언어에서 먼저 무너지는 경우가 많습니다. 그래서 모델을 작게 바꿀 때는 평가 세트에 **어려운 질문**을 일부러 더해 두고, 카나리로 실제 질문을 봅니다.
**섞어 쓰기.** 모든 질문에 같은 모델을 쓸 필요는 없습니다. 쉬운 질문(FAQ 그대로)은 작은 모델, 어려운 질문(여러 문서 · 판단)은 큰 모델로 보내는 라우팅도 흔합니다 (75일차 비용). 이때도 릴리스는 "어떤 규칙으로 어느 모델에 보내나"까지 묶어 버전으로 둡니다.
### 오늘 실습에서
2.0.0 (`gpt-5.4-nano`): 강한 golden 9/9 PASS, p95 1.3초, VPN p50 1.12초 (1.0.0 1.23초). 게이트로는 "내보내도 됨". 판단 한 줄 예: "품질 · 속도는 게이트 기준 통과 → 카나리 10%로 먼저, 비용은 75일차에 잰 뒤 배포".
## 8. 망가진 릴리스는 크게 실패하게 (4교시 Lab 02 과제 2)
### 일반 설명
**실패에는 두 종류가 있습니다.**
| | 장애 (outage) | 설정 오류 (misconfiguration) |
| --- | --- | --- |
| 예 | 저장소 서버가 멈춤 · 느림 (500, 시간 초과) | 릴리스가 **없는 지시문**을 가리킴 (404) |
| 다시 하면 | 될 수도 있음 (잠깐의 문제) | 절대 안 됨 (설정이 틀림) |
| 맞는 대응 | 견딘다: 기본 지시문으로 계속 (graceful degradation) | **크게 실패한다 (fail fast)**: 분명한 오류로 멈춤 |
| HTTP로 보면 | 5xx, 응답 없음 | 4xx |
**견디는 장치가 오류를 숨깁니다.** 1단계 과제 2에서 만든 기본 지시문은 저장소의 **모든** 실패를 받아 줍니다. 그래서 2.1.0(p9)처럼 설정이 틀린 릴리스도 기본 지시문(p1)으로 멀쩡하게 답합니다. 운영팀은 2.1.0의 새 지시문이 나간다고 믿지만, 실제로는 1.0.0의 지시문이 나갑니다. 게이트도 PASS를 줍니다(p1의 답이니까). **조용한 실패(silent failure)**가 가장 비싼 실패입니다. 늦게 발견되고, 발견했을 때는 원인이 멀리 있습니다.
**fail fast.** 다시 해도 안 될 오류는 가능한 한 **일찍, 분명하게** 실패해야 합니다. 그래야 게이트가 배포 **전에** 잡습니다. 컴파일 오류, 설정 파일 검사(schema validation), 시작할 때 필수 값 확인 같은 것이 모두 fail fast입니다.
**응답 코드로 가릅니다.** HTTP는 이미 둘을 나눠 줍니다: 4xx는 "요청이 틀렸다(다시 해도 안 됨)", 5xx는 "서버가 아프다(다시 하면 될 수도)". 72일차 재시도 원칙("4xx는 다시 하지 않는다")과 같은 구분을 여기서는 **기본 지시문으로 갈지**에 씁니다.
```mermaid
flowchart LR
G["Get prompt"] --> C{"응답 코드"}
C -->|200| R(["registry"])
C -->|404 설정 오류| B(["400 release broken<br/>→ 게이트 FAIL"])
C -->|500 장애| F(["fallback"])
G -->|시간 초과| F
```
**설정은 배포 전에 검사합니다.** 2.1.0 같은 망가진 릴리스는 사실 **릴리스를 올릴 때** 막을 수도 있습니다: "릴리스가 가리키는 지시문 버전이 저장소에 있는가, 모델 이름이 공급자 목록에 있는가"를 확인하는 검사입니다. 설정 파일의 형식 검사(스키마 검증)와 같은 일입니다. 오늘은 워크플로우에서 404를 크게 실패하게 만들어 게이트가 잡게 했지만, 운영에서는 둘 다 둡니다 — 올릴 때 한 번, 쓸 때 한 번.
### 오늘 실습에서
`lab02-challenge2` (10/27):
| 케이스 | 고치기 전 (예제 1 상태) | 고친 뒤 |
| --- | --- | --- |
| 망가진 릴리스 2.1.0 | ✘ **200** (기본 지시문으로 답, 1.3초) | ✔ 400 `release broken` · `p9` |
| 평소 · 1.1.0 | ✔ | ✔ `registry` · `[FAQ-003]` |
| 저장소 고장 · 1.1.0 | ✔ `fallback` | ✔ `fallback` |
| 저장소 고장 · 없는 릴리스 9.9.9 | ✔ 400 | ✔ 400 |
| 운영(active 1.0.0) | ✔ | ✔ |
| 합계 | 4/5 | 5/5 |
그리고 같은 강한 golden으로 2.1.0을 게이트: 고치기 전 **9/9 PASS** (p95 1.5초, 위험!) → 고친 뒤 FAIL (모든 케이스 400). 게이트가 잡지 못하던 설정 오류를 워크플로우가 크게 실패하게 만들자 게이트가 잡았습니다.
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
아래 표의 열은 이 단계에서 에이전트를 쓰는 실습, 줄은 위 다섯 칸입니다. 각 칸에 그 실습에서 채울 것을 적었습니다.
| 요청문에 | Lab 02 예제 1 | Lab 02 과제 1 | Lab 02 과제 2 |
| --- | --- | --- | --- |
| 무엇을 | 릴리스를 밖에서 읽고 고정 | 강한 golden (테스트만) | 404는 400, 장애는 기본 지시문 |
| 어디에 | `Release info` 대신 `Get release` → `Pick release` → `Known release?` | `cases/my-golden.json` | `Get prompt` 응답 옵션, 다음 Code · If |
| 어떤 모양 | `channel` stable · pinned, 400 `known` | `contains` 해결 낱말 · `not_contains` | 400 `{error: release broken, release, prompt_version}` |
| 그대로 | 기본 지시문 | 워크플로우 | 시간 초과 쪽 기본 지시문 |
| 확인 | `lab02` · `lab01-challenge` · `lab01-challenge2` | `gate` × 4 | `lab02-challenge2`, `gate my-golden 2.1.0` FAIL |
예를 들어 Lab 02 과제 2 열을 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
지시문 저장소가 404 면 기본 지시문으로 넘어가지 말고 크게 실패하게,
장애(500 · 시간 초과)일 때만 기본 지시문으로 답하게 해 줘. ← 무엇을
Get prompt 의 응답 옵션을 바꾸고 그 다음에 Code · If 를 넣어서. ← 어디에
404 면 HTTP 400 {error: 'release broken', release, prompt_version}. ← 어떤 모양으로
시간 초과 쪽 기본 지시문(Fallback prompt)은 그대로. ← 그대로
cases/lab02-challenge2.json 이 통과하고, gate my-golden --set release=2.1.0 은 FAIL 이어야 해. ← 확인
```
과제 1은 **테스트를 쓰는 요청**입니다. 기대 낱말은 내가 데이터에서 정해 요청문에 적습니다. 에이전트가 지금 답을 보고 기대를 만들게 하지 않습니다.
## 10. 자주 하는 실수
| 실수 | 왜 문제인가 | 이렇게 |
| --- | --- | --- |
| 한 릴리스에 지시문과 모델을 같이 바꿈 | 결과가 달라도 원인을 모름 | 한 릴리스에 한 가지 |
| 후보를 운영(active)으로 바꿔서 시험 | 시험하는 동안 직원 모두가 후보를 받음 | 고정(`--set release=`)으로 |
| 게이트 PASS면 끝 | 게이트가 보는 것만 괜찮다는 뜻 | 테스트가 무엇을 보는지 읽기, 카나리로 |
| 지금 답을 보고 기대를 만듦 | 지금 틀린 것도 정답이 됨 | 데이터에서 사람이 |
| 게이트가 FAIL이라 기대를 느슨하게 | 게이트가 의미를 잃음 | 답이 틀렸는지 먼저 확인, 같은 뜻이면 낱말을 더함 |
| 기준을 감으로 | 너무 느슨하거나 같은 릴리스도 FAIL | 기준선을 재고, 흔들리지 않는지 반복 |
| 모든 실패를 기본값으로 받음 | 설정 오류가 숨어 게이트도 통과 | 4xx는 크게 실패, 5xx · 시간 초과만 견딤 |
## 확인 질문
1. 릴리스 1.1.0과 2.0.0을 비교하면 무엇의 영향만 보이나요? 1.0.0과 2.0.0을 비교하면?
2. 본문 `release`로 고정하는 문을 모든 직원에게 열어 두면 생길 수 있는 문제는?
3. 약한 golden에서 1.2.0이 PASS였습니다. 게이트가 고장 난 것인가요?
4. 1.2.0의 답 "IT 포털에서 … 확인해 주세요"를 `not_contains: "포털을 참고"`가 못 잡았습니다. 무엇으로 잡았고, 그것이 더 튼튼한 까닭은?
5. 2.0.0이 강한 golden을 통과했습니다. 바로 전원에게 배포해도 되나요? 무엇을 더 봐야 하나요?
6. 2.1.0은 고치기 전 강한 golden을 9/9로 통과했습니다. 왜 그랬고, 어떻게 고쳤나요? "장애"와 "설정 오류"를 가르는 기준은?
## 정리
- [ ] 릴리스 = 지시문 + 모델, 한 릴리스에 한 가지 변화, 한 번 낸 릴리스는 바꾸지 않는다
- [ ] 운영 릴리스는 밖에서 읽고, 후보는 고정으로 시험한다 (고정의 문은 좁게)
- [ ] 게이트는 통과율 · p95로 PASS/FAIL — 그러나 테스트만큼만 막는다, 기대는 데이터에서 사람이
- [ ] 기준은 기준선을 재서, 흔들리지 않는지 반복해서
- [ ] 장애는 견디고, 설정 오류는 크게 실패하게 — 그래야 게이트가 잡는다
+372
View File
@@ -0,0 +1,372 @@
# 3단계 · 배포 · 되돌리기 · 카나리
- 교시: 5교시 강의 (14:00~14:50) · 6교시 [Lab 03](../../Last3Mon/74/LAB-03-Q-DEPLOY-AND-ROLLBACK.md) 실습 5개 (15:00~15:50)
- 이 단계가 끝나면: 배포와 되돌리기를 값 하나로 하고, 새 릴리스를 일부 사용자에게만 먼저 내보내고, 나쁜 카나리를 보고 끄고, 내보낼지 되돌릴지를 판단하는 점검을 n8n 워크플로우로 만들고, 점검을 지난 릴리스만 배포되게 하는 운영 체계를 설명할 수 있다.
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/27 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/74/answers/LAB-TEST-RESULT.md`에 있습니다.
- 실습 가리키기: "Lab 03 예제 1"은 6교시 [Lab 03 문서](../../Last3Mon/74/LAB-03-Q-DEPLOY-AND-ROLLBACK.md)의 그 절입니다.
```mermaid
flowchart LR
A["1. 배포와 릴리스"] --> B["2. 되돌리기"] --> C["3. 배포 전략"] --> D["4. 카나리 ·<br/>같은 사람은 같은 쪽"] --> E["5. 카나리를 보고 끄기"]
E --> F["6. 릴리스 점검을 한 곳에서"] --> G["7. 판단을 남기고<br/>정책으로 지키기"] --> H["8. 운영 체계"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
| 시간 | 절 · 개요 | 하는 일 |
| --- | --- | --- |
| 14:00~14:05 | 1 · 개요 22장 | 배포(deploy)와 릴리스(release)를 나누기 |
| 14:05~14:11 | 2 · 개요 22장 | 되돌리기 · 앞으로 고치기, MTTR, DORA 지표 |
| 14:11~14:17 | 3 · 개요 23장 | 한 번에 · 블루그린 · 카나리 · 점진 · 그림자 |
| 14:17~14:24 | 4 · 개요 24장 | 고정 배정(sticky bucketing), 해시, 순서 |
| 14:24~14:30 | 5 · 개요 25장 | 카나리 분석, 피해 반경, 무엇을 보나 |
| 14:30~14:37 | 6 · 개요 26장 | 점검을 한 곳에서, 지속적 배포 |
| 14:37~14:43 | 7 · 개요 27장 | 판단과 실행 분리, 코드로 된 정책, 변경 관리 |
| 14:43~14:47 | 8 · 개요 28장 | 운영 체계: 사람 · 시간 · 기록 · 연습 |
| 14:47~14:50 | 9 · 10 · 확인 질문 | 요청문, 자주 하는 실수 |
### 6교시 Lab 03에서 이어서 하는 것
| 6교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
| --- | --- | --- | --- |
| Lab 03 예제 1 (15:00~15:08, 에이전트 없이) | 1 · 2 | 배포 · 되돌리기를 값 하나로 | `release helpdesk 1.1.0` → `call` → `release helpdesk 1.0.0`, `test cases/lab03.json` → `RELEASE.md` 3절 |
| Lab 03 예제 2 (15:08~15:20) | 3 · 4 | 카나리: 이름 bucket < percent | `my-work/helpdesk.json`, 테스트 `cases/lab03-canary.json` |
| Lab 03 예제 3 (15:20~15:27, 에이전트 없이) | 5 | 나쁜 카나리(1.2.0 50%)를 보고 끄기 | `test cases/lab03-watch.json`, `release helpdesk --no-canary`, `--no-before`로 다시 |
| Lab 03 과제 1 (15:27~15:40) | 6 | 릴리스 점검 `my-release-check` | `my-work/release-check.json`, 테스트 `cases/lab03-challenge.json` |
| Lab 03 과제 2 (15:40~15:50) | 7 · 8 | 점검을 기록하고, 지난 것만 배포 | 테스트 `cases/lab03-challenge2.json` (`release helpdesk --require-gate on`) |
## 1. 배포와 릴리스를 나눈다 (6교시 Lab 03 예제 1)
### 일반 설명
**배포(deploy)와 릴리스(release)는 다른 일입니다.** 일상에서는 섞어 쓰지만, 운영에서는 나눠 생각합니다.
| | 배포 (deploy) | 릴리스 (release) |
| --- | --- | --- |
| 뜻 | 새 코드 · 설정을 운영 환경에 **올려 두는** 일 | 사용자가 그것을 **실제로 받게** 하는 일 |
| 오늘 | 데이터 API에 릴리스 1.1.0과 지시문 p2를 올려 둠 (이미 되어 있음) | `active`를 1.1.0으로 바꿈 |
| 위험 | 올리기만 해서는 사용자가 영향을 안 받음 | 바꾸는 순간 사용자가 영향을 받음 |
둘을 나누면, 새 버전을 **미리 올려 두고**(배포) 원하는 때에 · 원하는 사람에게만 **켤 수 있습니다**(릴리스). 기능 플래그 · 카나리가 모두 이 분리 위에 서 있습니다. 오늘 수업에서 "배포"라고 부르는 `flow.py release helpdesk 1.1.0`은 엄밀히는 이 표의 **릴리스**에 해당합니다.
**값 하나로 바꾼다.** 2단계에서 워크플로우가 `active`를 매번 읽게 했기 때문에, 릴리스는 데이터 API의 값 하나를 바꾸는 일입니다. push도, n8n 재시작도 없습니다. 바꾸는 일이 작고 빠르면 자주 · 안전하게 할 수 있습니다.
**작게, 자주.** 한 달에 한 번 크게 내보내는 팀은 그날 무엇이 잘못됐는지 찾기 어렵고, 되돌리면 한 달 치 변경이 다 사라집니다. 하루에 여러 번 작게 내보내는 팀은 문제가 생겨도 원인이 작고, 되돌릴 것도 작습니다. 그래서 "배포가 무섭다"는 팀일수록 배포를 **더 자주, 더 작게** 하라는 것이 업계의 오랜 조언입니다. 오늘 릴리스가 한 가지씩만 바뀌는 것도 같은 생각입니다.
**배포 시각도 정합니다.** 금요일 밤 · 연휴 전날 · 큰 행사 직전 배포를 피하는 팀이 많습니다. 문제가 생겼을 때 대응할 사람이 없거나, 문제의 영향이 가장 큰 때이기 때문입니다. 반대로 "배포가 안전하다면 언제 해도 괜찮아야 한다"는 의견도 있습니다 — 둘 다 맞고, 안전장치(카나리 · 값 하나 되돌리기)가 늘수록 배포 시각의 제약을 줄일 수 있습니다.
### 오늘 실습에서
Lab 03 예제 1 (10/27): `release helpdesk 1.1.0` → 다음 요청의 `version.release` 1.1.0, 답 끝 `[FAQ-003]` → `release helpdesk 1.0.0` → 다음 요청부터 1.0.0. 명령은 바로 끝났고 push · 재시작은 없었습니다. `lab03` 2/2.
## 2. 되돌리기
### 일반 설명
**되돌리기(rollback)와 앞으로 고치기(roll forward).** 새 버전이 나쁠 때 할 수 있는 일은 둘입니다.
| | 되돌리기 | 앞으로 고치기 |
| --- | --- | --- |
| 뜻 | 이전 버전으로 돌아감 | 고친 새 버전을 빨리 내보냄 |
| 빠르기 | 이전 버전이 남아 있으면 가장 빠름 | 고치고 시험하는 시간이 듦 |
| 언제 | 원인을 모를 때, 피해가 클 때 (먼저 멈추기) | 원인이 작고 분명할 때, 되돌릴 수 없을 때 (데이터 변경) |
사고 때는 대개 **먼저 되돌리고, 그다음 원인을 찾습니다.** 원인을 찾는 동안 사용자가 계속 피해를 보지 않게 하는 것이 먼저입니다.
**되돌릴 수 있으려면.** ① 이전 릴리스가 **지워지지 않고 남아 있어야** 하고(1단계 불변 버전) ② 되돌리는 방법이 **쉽고 연습되어 있어야** 합니다. 한 번도 해 보지 않은 되돌리기는 사고 날 처음 해 보게 되고, 그날 실패합니다. 그래서 운영 팀은 되돌리기를 **연습**합니다 (오늘 `RELEASE.md` 3절).
**DORA 지표.** 소프트웨어 운영 성과를 재는 널리 쓰이는 지표 네 개가 있습니다 (DevOps Research and Assessment 연구에서 나옴).
| 지표 | 뜻 | 오늘 실습과 |
| --- | --- | --- |
| 배포 빈도 | 얼마나 자주 내보내나 | 값 하나라서 자주 할 수 있음 |
| 변경 리드 타임 | 고친 것이 운영에 나가기까지 | 게이트 · 카나리 · 점검이 이 안에 |
| 변경 실패율 | 배포 중 문제를 일으킨 비율 | 게이트 · 점검이 줄임 |
| 복구 시간 (MTTR) | 문제가 난 뒤 회복까지 | 되돌리기 · `--no-canary`가 줄임 |
좋은 팀은 **자주 · 작게** 내보내면서도 **실패율이 낮고 복구가 빠릅니다.** 오늘 만드는 것(작은 릴리스, 게이트, 값 하나 되돌리기, 카나리)이 이 네 숫자를 함께 좋게 하는 도구입니다.
**되돌릴 수 없는 변경.** 모든 것이 값 하나로 되돌아가지는 않습니다. 데이터베이스 구조를 바꿨거나, 사용자에게 이미 메일을 보냈거나, 외부 시스템에 접수 건을 만들었다면 되돌리기만으로는 원래대로 돌아가지 않습니다. 그래서 되돌릴 수 없는 변경은 따로 표시하고, 더 조심해서(더 작은 카나리, 더 긴 관찰) 내보냅니다. 오늘의 지시문 · 모델 변경은 다행히 되돌릴 수 있는 쪽입니다 — 이미 나간 답은 되돌릴 수 없지만, 다음 답부터는 돌아갑니다.
**되돌리기를 정하는 기준을 미리.** 사고 때 "되돌릴까 말까"를 그 자리에서 토론하면 늦습니다. 미리 정해 둡니다: 오류율이 평소의 두 배를 넘으면, p95가 기준을 넘으면, 사용자 신고가 몇 건 넘으면 **묻지 않고 되돌린다.** 되돌리는 것은 실패가 아니라 정해진 절차라는 문화가 있어야 사람들이 망설이지 않습니다.
### 오늘 실습에서
| 되돌리기 방법 | 오늘 |
| --- | --- |
| 예전 JSON을 찾아 push | 하지 않음 (n8n 재시작, 그 사이 다른 것도 바뀔 수 있음) |
| `active` 값 하나 | `release helpdesk 1.0.0`, 다음 요청부터 바로 |
| 확인 | `call my-helpdesk requests/faq-vpn.json -v`의 `version.release` |
## 3. 배포 전략
### 일반 설명
새 버전을 **누구에게 · 얼마나 · 어떤 순서로** 내보낼지가 배포 전략입니다.
| 전략 | 방법 | 좋은 점 | 나쁜 점 |
| --- | --- | --- | --- |
| 한 번에 (big bang) | 모두를 한 번에 새 버전으로 | 단순 | 나쁘면 모두가 피해 |
| 블루그린 (blue-green) | 새 환경(green)을 옆에 띄우고 트래픽을 한 번에 넘김 | 되돌리기가 빠름 (blue로) | 환경 두 벌, 여전히 모두가 한 번에 |
| 카나리 (canary) | 일부 사용자에게만 먼저 | 피해 반경이 작음, 실제 사용으로 확인 | 나누는 규칙 · 비교하는 일이 필요 |
| 점진 배포 (progressive) | 카나리 비율을 1 → 5 → 25 → 100%로 | 문제를 일찍, 작게 | 시간이 걸림 |
| 그림자 (shadow) | 실제 요청을 새 버전에도 보내고 답은 버림 | 사용자 영향 0 | 비용 두 배, 답의 품질은 따로 판단 |
| A/B 시험 | 두 버전을 나눠 받고 **성과**를 비교 | 무엇이 더 나은지 숫자로 | 통계적으로 충분한 사람 수 · 기간 |
카나리와 A/B는 모양이 비슷하지만 목적이 다릅니다. **카나리는 "나쁘지 않은가"(안전)**, **A/B는 "더 좋은가"(성과)** 를 봅니다.
**LLM 앱에서 카나리가 특히 맞는 까닭.** 지시문 · 모델 변경은 평가 세트(게이트)가 다 잡지 못합니다. 사용자의 실제 질문은 훨씬 다양합니다. 그래서 게이트를 지난 뒤에도 **실제 사용자 일부**에게 먼저 내보내 봅니다.
**LLM 앱에서 그림자 배포의 쓰임.** 새 모델 · 지시문을 실제 질문으로 시험하고 싶지만 사용자에게 보이면 위험할 때 그림자 배포를 씁니다. 실제 질문을 새 릴리스에도 보내 답을 기록만 하고, 나중에 사람이나 채점기가 운영 답과 비교합니다. 비용이 두 배라 일부 요청만 보내는 경우가 많습니다. 카나리 전에 그림자로 한 번 걸러 내는 팀도 있습니다.
**전략은 섞어 씁니다.** 실제로는 하나만 쓰지 않습니다. 예: 그림자로 실제 질문 품질을 먼저 보고 → 사내 직원 카나리 → 고객 1% → 5% → 25% → 전원, 문제가 생기면 그 단계에서 끄기. 단계가 많을수록 안전하지만 느립니다. 무엇을 얼마나 조심할지는 **변경의 위험**으로 정합니다 — 오탈자 수정과 모델 교체는 같은 길을 걸을 필요가 없습니다.
### 오늘 실습에서
오늘은 **카나리**를 씁니다. 직원 여섯 명이라 50%로 둘이 카나리가 되게 했습니다. 실제 서비스에서는 1~5%로 시작하는 것이 보통입니다.
## 4. 카나리: 같은 사람은 같은 쪽 (6교시 Lab 03 예제 2)
### 일반 설명
**누구를 카나리로 할지 정하는 규칙.** 요청마다 무작위로 고르면 같은 사람이 질문할 때마다 다른 버전의 답을 받습니다. 대화가 이어질 때 말투 · 형식이 바뀌고, 문제가 생겼을 때 누가 어느 버전을 받았는지 추적하기 어렵습니다. 그래서 **바뀌지 않는 값**(사용자 id · 이름 · 사번 · 기기 id)으로 나눕니다. 이것을 고정 배정(sticky bucketing)이라고 합니다.
**해시로 고르게 나눕니다.** 사용자 id를 해시 함수에 넣어 0~99의 숫자(bucket)로 바꾸고, `bucket < percent`면 카나리로 정합니다. 해시는 같은 입력에 늘 같은 출력이 나오고(고정), 여러 입력을 고르게 흩습니다(공정). 기능 플래그 도구들이 대개 이렇게 합니다. 비율을 10% → 30%로 올리면 원래 카나리였던 10%는 그대로 카나리에 남고 20%가 새로 들어옵니다(이미 받은 사람이 왔다 갔다 하지 않음).
**오늘의 해시는 단순합니다.** 이름 글자 코드의 합 % 100. 실제 해시보다 고르지 않지만(비슷한 이름은 비슷한 bucket), 손으로 계산해 볼 수 있고 `flow.py`와 워크플로우가 같은 결과를 내는지 확인하기 쉽습니다. 운영에서는 SHA-256 같은 해시를 씁니다.
**정하는 순서.** 오늘 순서는 **고정 > 카나리 > active**입니다. 고정이 가장 먼저여야 테스트 · 점검이 카나리에 섞이지 않습니다. 카나리 대상인 최유진도 `release: 1.0.0`을 고정하면 1.0.0을 받습니다.
```mermaid
flowchart LR
Q(["질문 · user · release?"]) --> P{"본문 release?"}
P -->|있음| F["그 릴리스 · pinned"]
P -->|없음| C{"카나리 켜짐 ·<br/>bucket < percent?"}
C -->|예| K["카나리 · canary"]
C -->|아니오| S["active · stable"]
```
**카나리 대상을 고르는 다른 기준.** 무작위 비율 말고도 대상을 고르는 방법이 있습니다: 사내 직원부터(가장 너그러운 사용자), 특정 지역 · 특정 고객군, 신청한 사람(베타 테스터). 무엇으로 고르든 원칙은 같습니다 — **누가 어느 버전을 받는지 정해져 있고, 기록되고, 언제든 끌 수 있다.**
**나누는 값이 바뀌면.** 고정 배정은 "나누는 값이 바뀌지 않는다"에 기댑니다. 사용자가 로그인하지 않아 매번 다른 id로 오거나, 이름을 바꾸면 다른 쪽으로 옮겨 갑니다. 그래서 가장 오래 바뀌지 않는 값(사번 · 계정 id)을 고르고, 없으면 기기 · 세션 id로 대신합니다. 오늘은 이름이라 같은 사람이 이름 철자를 다르게 보내면 다른 bucket이 됩니다 — 운영에서는 이름 대신 id를 씁니다.
### 오늘 실습에서
| 50% 카나리 (1.1.0) | bucket | 받는 릴리스 |
| --- | --- | --- |
| 최유진 | 0 | 1.1.0 (canary) |
| 정우성 | 42 | 1.1.0 (canary) |
| 한지훈 · 이준호 · 박서연 | 52 · 56 · 57 | 1.0.0 (stable) |
| 김민지 | 80 | 1.0.0 (stable) |
`lab03-canary` (10/27): 고치기 전(Lab 02 상태) 3/6 — 카나리 대상 둘과 "같은 사람은 늘 같은 쪽"이 stable로 답함. 고친 뒤 6/6, `lab02` 5/5, `lab02-challenge2` 5/5.
## 5. 카나리를 보고 끄기 (6교시 Lab 03 예제 3)
### 일반 설명
**카나리는 보는 것까지가 카나리입니다.** 일부에게 내보내기만 하고 아무도 보지 않으면 그냥 느린 배포입니다. 카나리 쪽과 운영 쪽을 **같은 지표로 비교**하고, 차이가 기준을 넘으면 멈춥니다. 이것을 **카나리 분석(canary analysis)**이라고 하고, 넷플릭스 · 구글이 만든 Kayenta 같은 도구는 이 비교를 자동으로 해서 "계속 / 멈춤"을 냅니다.
| 비교하는 것 | 일반 서비스 | LLM 앱에서 더 |
| --- | --- | --- |
| 오류율 | 5xx 비율 | 모델 오류 · 시간 초과 |
| 시간 | p50 · p95 | LLM 호출 시간 |
| 품질 | (보통 없음) | 해결 낱말 · 사용자 평가(👍👎) · 사람이 읽은 샘플 · 이관 비율 |
| 비용 | 서버 비용 | 요청당 토큰 (75일차) |
**피해 반경(blast radius).** 문제가 생겼을 때 영향을 받는 범위입니다. 전원 배포의 피해 반경은 100%, 5% 카나리는 5%입니다. 카나리 비율 · 기간 · 대상(사내 직원부터)을 정하는 것은 피해 반경을 정하는 일입니다.
**끄는 일은 되돌리기보다 작습니다.** 카나리를 끄면(`--no-canary`) 카나리 대상도 다시 `active`를 받습니다. `active`는 처음부터 바뀌지 않았으므로 운영 사용자에게는 아무 일도 없습니다. 아직 릴리스하지 않은 것을 **거두는** 일입니다.
**언제 넓히나.** 카나리를 얼마 동안 보고 넓힐지는 "문제가 드러나는 데 걸리는 시간"으로 정합니다. 오류는 몇 분이면 보이지만, "답이 미묘하게 나빠짐"은 사용자 평가가 쌓이는 며칠이 필요할 수 있습니다. 그래서 점진 배포는 보통 1% → 5% → 25% → 100%처럼 단계를 두고, 단계마다 기준(오류율 · p95 · 평가)을 넘으면 다음으로, 못 넘으면 끕니다. 이 판단을 사람이 하면 느리고, 자동으로 하면 기준을 잘 정해야 합니다.
**비교할 짝을 같게.** 카나리 쪽과 운영 쪽을 비교할 때 두 무리가 비슷해야 공정합니다. 카나리에 신입 직원만 몰렸다면 질문 종류가 달라 오류율도 다를 수 있습니다. 해시로 고르게 나누는 까닭이 이것이고, 오늘 `lab03-watch`처럼 **같은 질문을 양쪽에** 보내 비교하는 것도 한 방법입니다. 표본이 작으면(오늘 여섯 명) 차이가 커야만 알아볼 수 있다는 것도 기억합니다.
### 오늘 실습에서
`lab03-watch` (1.2.0을 50% 카나리로, 여섯 직원이 같은 프린터 질문) (10/27):
| | 카나리 2명 (최유진 · 정우성) | 운영 4명 | 합계 |
| --- | --- | --- | --- |
| 켠 상태 | ✘ ✘ "대기열" 없음 (1.2.0의 한 문장 답) | ✔ ✔ ✔ ✔ | 4/6 |
| `--no-canary` 뒤 | ✔ ✔ | ✔ ✔ ✔ ✔ | 6/6 |
전원에게 1.2.0을 바로 냈다면 여섯 명 모두 "포털에서 확인하세요"를 받았을 것입니다. 카나리라서 두 명, 끄는 데는 명령 한 줄.
## 6. 릴리스 점검을 한 곳에서 (6교시 Lab 03 과제 1)
### 일반 설명
**노트북마다 다른 결과.** `flow.py gate`는 각자의 노트북에서 돕니다. 사람마다 다른 테스트 파일 · 다른 기준 · 다른 시각에 돌리면 같은 릴리스에 다른 결과가 나옵니다. "내 노트북에서는 PASS였는데요"가 생깁니다.
**한 곳에서, 같은 기준으로.** 회사에서는 이 일을 CI/CD 서버(GitHub Actions, GitLab CI, Jenkins)가 합니다. 누가 요청하든 같은 환경 · 같은 테스트 · 같은 기준으로 돌고 결과가 남습니다. 오늘은 n8n 워크플로우 `my-release-check`가 그 자리를 맡습니다.
**지속적 배포(continuous delivery/deployment).** 바뀐 것이 생기면 자동으로 게이트 → 카나리 → 분석 → 확대 → 배포까지 이어지는 흐름을 지속적 배포라고 합니다. 사람은 기준을 정하고 예외를 판단합니다. 오늘은 점검(판단)까지 자동, 릴리스(실행)는 사람이 합니다.
**점검은 고정으로 묻습니다.** 점검이 `release` 없이 헬프데스크를 부르면 운영 중인 릴리스(1.0.0)가 답합니다. 1.2.0을 점검한다면서 1.0.0을 채점하게 됩니다. 그래서 고정하고, 답의 `version.release`가 점검하는 릴리스와 같은지도 채점에 넣습니다.
**점검도 버전입니다.** 릴리스 점검 워크플로우의 질문 · 기대 낱말이 바뀌면 같은 릴리스의 판정도 바뀝니다. 그래서 점검 기록에는 "어느 점검(어떤 질문 세트)으로 판단했나"도 남겨야 합니다 (오늘 `source` 칸). 점검을 고칠 때도 바꾼 이유와 시험 결과를 남깁니다.
**자동과 사람의 경계.** 무엇을 자동으로 하고 무엇을 사람이 할지는 팀의 신뢰 수준에 따라 다릅니다. 처음에는 점검만 자동이고 배포는 사람이(오늘), 점검이 믿을 만해지면 카나리까지 자동, 나중에는 전 과정 자동에 사람은 기준만 정합니다. 어느 단계든 **자동이 내린 판단을 사람이 볼 수 있게 남기는 것**은 같습니다.
### 오늘 실습에서
```mermaid
flowchart LR
W(["release 1.2.0"]) --> G["Golden questions<br/>항목 6개"] --> H["Ask helpdesk<br/>release 고정"] --> J["Judge (Code)"] --> V(["promote · rollback"])
```
`lab03-challenge` (10/27): 1.1.0 → `promote` 6/6 (3.1초), 1.2.0 → `rollback` 0/6 (1.6초), 본문에 `release`가 없으면 400. `call my-release-check '{"release": "2.0.0"}'` → `promote` 6/6 (1.8초).
## 7. 판단을 남기고, 정책으로 지킨다 (6교시 Lab 03 과제 2)
### 일반 설명
**판단과 실행 사이의 틈.** 과제 1의 점검은 `rollback`이라고 **말할** 뿐입니다. 사람이 점검을 안 보거나 잘못 보면 그 릴리스가 그대로 나갑니다. 사고 보고서에 흔히 나오는 문장이 "점검에서는 경고가 떴지만 배포는 진행되었다"입니다.
**틈을 닫는 방법 셋.**
| 방법 | 뜻 | 오늘 |
| --- | --- | --- |
| 기록 | 판단을 남김 (누가 · 언제 · 무엇으로 · 결과) | `POST /gates/helpdesk` |
| 확인 | 실행하는 쪽이 기록을 보고 결정 | 데이터 API의 배포 정책 `require_gate` |
| 분리 | 판단하는 쪽은 실행하지 않음 | 워크플로우는 `/releases`에 POST하지 않음 |
**코드로 된 정책(policy as code).** "점검을 지난 것만 배포한다"를 문서나 약속으로 두면 바쁜 날 지켜지지 않습니다. 배포를 **받는 곳**이 규칙을 지키게 하면(조건이 안 맞으면 거절) 누가 실수해도 막힙니다. 쿠버네티스의 입장 제어(admission control), 저장소의 브랜치 보호 규칙("리뷰 승인 없이 main에 합치지 못함"), 배포 승인 단계가 모두 같은 생각입니다.
**판단하는 쪽은 실행하지 않습니다.** 점검 워크플로우가 직접 `active`를 바꾸게 하면 편하지만, 점검이 고장 나거나 속으면 나쁜 릴리스가 **저절로** 나갑니다. 판단(점검) · 기록(데이터 API) · 실행(사람의 `flow.py release`)을 나누면 하나가 틀려도 나머지가 받칩니다. 73일차 "판별 LLM에는 Tool을 주지 않는다"(최소 권한)와 같은 원칙입니다.
**변경 관리(change management).** 큰 조직에서는 운영 변경마다 "무엇을 · 왜 · 어떤 시험을 거쳤고 · 실패하면 어떻게 되돌리나"를 기록하고 승인받습니다 (ITIL의 변경 관리, 금융권의 변경 통제). 오늘의 점검 기록 + 정책 + `CHANGELOG.md`가 그 작은 판입니다. 감사 때 "이 릴리스는 언제 누가 무슨 근거로 내보냈나"에 답할 수 있습니다.
**정책은 예외 길도 정합니다.** 급한 보안 수정처럼 점검을 기다릴 수 없는 경우가 있습니다. 좋은 정책은 예외를 막지 않고 **예외를 기록하게** 합니다: 누가 · 왜 · 어떤 승인으로 정책을 건너뛰었는지. 예외가 기록되면 나중에 "예외가 너무 잦다 → 점검이 너무 느리다"를 알 수 있습니다. 오늘 실습의 `--require-gate off`도 그런 예외 길이지만, 누가 껐는지는 남지 않습니다 — 운영에서는 남겨야 합니다.
**감사 질문에 답하기.** 감사나 사고 조사에서 받는 질문은 대개 정해져 있습니다: 이 릴리스는 언제 운영에 나갔나? 누가 승인했나? 무엇으로 시험했나? 결과는? 문제가 나서 언제 되돌렸나? 오늘의 점검 기록 · `CHANGELOG.md` · 답의 `version`이 이 질문들에 하나씩 답합니다.
**기록은 바꾸지 않습니다.** 점검 · 배포 기록은 나중에 고칠 수 없게(덧붙이기만) 두는 것이 원칙입니다. 기록을 고칠 수 있으면 "점검을 지났다"를 나중에 만들어 넣을 수 있습니다. 오늘 `/gates`는 덧붙이기만 하고 지우기(`clear`)는 테스트용으로만 씁니다 — 운영에서는 지우기를 막거나 지운 사실도 남깁니다.
### 오늘 실습에서
```mermaid
flowchart LR
C["my-release-check"] -->|기록| G[("/gates/helpdesk")]
F["flow.py gate"] -->|기록| G
P(["release helpdesk 1.2.0"]) --> D{"require_gate:<br/>마지막 기록이 promote?"}
G --> D
D -->|예| OK(["active 바뀜"])
D -->|아니오| NO(["409 not gated"])
```
`lab03-challenge2` (10/27, `before`: 기록 비우기 · reset · 정책 켜기):
| 케이스 | 고치기 전 (과제 1 정답) | 고친 뒤 |
| --- | --- | --- |
| 1.2.0 점검 · 기록 | ✘ `recorded` 없음 | ✔ `rollback` · `recorded` true |
| 1.1.0 점검 · 기록 | ✘ `recorded` 없음 | ✔ `promote` · `recorded` true |
| 1.2.0 배포 거부 (409 기대) | ✔ 409 (기록이 아예 없어서) | ✔ 409 (마지막 기록이 rollback) |
| 1.1.0 배포 | **멈춤**: 409 `not gated` · `last_gate: null` | ✔ 200, `version.release` 1.1.0 |
| 2.0.0 카나리 거부 (409 기대) | (앞에서 멈춰 돌지 않음) | ✔ 409, 최유진도 1.1.0 · `stable` |
고치기 전에는 점검이 `promote`라고 **말했지만** 아무 데도 남기지 않아, 정책이 1.1.0의 배포도 막았습니다. 점검 → 기록 → 정책이 이어져야 배포가 됩니다.
## 8. 운영 체계
### 일반 설명
**운영 체계는 도구 + 사람 + 시간 + 기록입니다.**
| 정할 것 | 예 |
| --- | --- |
| 누가 | 배포 · 되돌리기 권한이 있는 사람, 지시문을 고칠 수 있는 사람, 승인하는 사람 |
| 언제 | 업무 시간에만 배포 (밤 · 금요일 오후 금지), 큰 행사 전 동결 |
| 어떤 순서로 | 게이트 → 카나리 → 점검 → 확대 → 배포, 단계마다 기준 |
| 무엇을 남기나 | `CHANGELOG.md`, 점검 기록, 답의 `version` |
| 문제가 생기면 | 되돌리기 절차(런북), 알림을 받을 사람(온콜), 사후 검토 |
| 얼마나 자주 연습하나 | 되돌리기 · 카나리 끄기 연습 (오늘 `RELEASE.md` 3절) |
**런북(runbook).** "이런 일이 생기면 이 순서로"를 적은 문서입니다. 사고가 나면 사람은 당황해서 평소 알던 것도 잊습니다. 명령어 한 줄까지 적힌 런북이 있으면 처음 당번을 서는 사람도 되돌릴 수 있습니다.
```mermaid
flowchart LR
N["새 릴리스<br/>(지시문 · 모델)"] --> G["게이트<br/>고정으로"] -->|PASS · 기록| K["카나리<br/>일부 직원"] --> C["점검 · 비교"] -->|promote · 기록| D["active로"]
G -->|FAIL| X["멈춤"]
C -->|rollback| R["카나리 끄기"]
D -->|문제| B["되돌리기<br/>(값 하나)"]
```
**사후 검토(postmortem)는 탓하지 않습니다.** 사고 뒤에는 무엇이 일어났고 왜 막지 못했는지를 적습니다. 목적은 사람을 탓하는 것이 아니라 **장치를 고치는 것**입니다 — "점검 결과를 놓쳤다"면 사람을 다그치는 대신 오늘처럼 정책으로 막습니다. 사고에서 나온 질문은 평가 세트에 더하고(2단계), 되돌리기가 느렸다면 런북을 고칩니다.
**온콜과 알림.** 문제가 생기면 누가 알게 되나도 운영 체계입니다. 카나리의 오류율이 기준을 넘으면, 정책이 배포를 막으면, 지시문 저장소가 계속 기본 지시문으로 답하면 — 그 일을 알아야 할 사람에게 알림이 가야 합니다 (76일차). 알림은 너무 많으면 무시되고(73일차 "경고가 수백 개면 아무도 안 써요"), 너무 적으면 사고를 늦게 압니다. "사람이 무언가를 해야 하는 일"만 알립니다.
### 오늘 실습에서
| 문서 | 내용 |
| --- | --- |
| `CHANGELOG.md` | 릴리스마다 무엇이 · 왜 · 게이트 결과 · 배포 · 되돌림 |
| `RELEASE.md` | 게이트 결과 표, 배포 순서 점검표, 되돌리기 · 카나리 끄기 연습 기록 |
| `/gates/helpdesk` | 점검 · 게이트 기록 (`release helpdesk`가 릴리스마다 마지막 점검을 보여 줌) |
79일차 `tops`(운영 대시보드)와 최종 프로젝트에서 이 순서를 그대로 씁니다.
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
아래 표의 열은 이 단계에서 에이전트를 쓰는 실습, 줄은 위 다섯 칸입니다. 각 칸에 그 실습에서 채울 것을 적었습니다.
| 요청문에 | Lab 03 예제 2 | Lab 03 과제 1 | Lab 03 과제 2 |
| --- | --- | --- | --- |
| 무엇을 | 카나리 | 릴리스 점검 | 점검 기록 |
| 어디에 | `Pick release` | 새 파일 `my-work/release-check.json` | `Judge`와 `Send verdict` 사이 |
| 어떤 모양 | bucket 식, `channel canary` | `{release, passed, total, failed, seconds, verdict}` | `POST /gates/helpdesk`, `recorded` |
| 그대로 · 하지 말 것 | 고정이 가장 먼저 | 운영(active)을 건드리지 않음 (고정으로) | 워크플로우가 `/releases`에 POST하지 않음 |
| 확인 | `lab03-canary`, `lab02` | `lab03-challenge` | `lab03-challenge2` |
예를 들어 Lab 03 과제 2 열을 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
릴리스 점검 결과를 기록해 줘. ← 무엇을
my-work/release-check.json 의 Judge 와 Send verdict 사이에. ← 어디에
POST http://data-api:8800/gates/helpdesk, 결과에 recorded: true|false. ← 어떤 모양으로
워크플로우에서 /releases 에는 POST 하지 마 (배포는 사람이). ← 하지 말 것
cases/lab03-challenge2.json 이 통과해야 해. ← 확인
```
## 10. 자주 하는 실수
| 실수 | 왜 문제인가 | 이렇게 |
| --- | --- | --- |
| 되돌리기를 "예전 파일 다시 올리기"로 | 느리고, 그 사이 다른 것도 바뀜 | 값 하나, 이전 릴리스를 남겨 둠 |
| 되돌리기를 한 번도 연습하지 않음 | 사고 날 처음 해 보고 실패 | `RELEASE.md` 3절처럼 연습 기록 |
| 카나리를 무작위로 나눔 | 같은 사람이 매번 다른 답, 추적 어려움 | 바뀌지 않는 값의 해시 |
| 카나리를 켜고 보지 않음 | 그냥 느린 배포 | 같은 질문 · 같은 지표로 비교 |
| 카나리가 고정보다 먼저 | 테스트 · 점검이 카나리에 섞임 | 고정 > 카나리 > active |
| 점검이 고정 없이 헬프데스크를 부름 | 운영 릴리스를 채점 | `release`를 고정, `version.release` 확인 |
| 점검 결과를 남기지 않음 | 판단과 실행 사이에 틈 | 기록 + 정책 |
| 점검 워크플로우가 직접 배포 | 점검이 틀리면 나쁜 릴리스가 저절로 | 판단 · 기록 · 실행을 나눔 |
## 확인 질문
1. 배포(deploy)와 릴리스(release)를 나누면 무엇을 할 수 있게 되나요?
2. 원인을 모르는 사고가 났을 때 되돌리기와 앞으로 고치기 중 무엇을 먼저 하나요? 왜?
3. 카나리를 요청마다 무작위로 나누면 생기는 문제 두 가지는?
4. `lab03-watch`에서 1.2.0 카나리에 영향 받은 사람은 몇 명이었고, 전원 배포였다면 몇 명이었나요?
5. 릴리스 점검이 헬프데스크를 부를 때 `release`를 고정하지 않으면 무엇을 점검하게 되나요?
6. 점검 워크플로우가 `promote`면 바로 `active`를 바꾸게 하면 편할 텐데, 오늘은 왜 그렇게 하지 않았나요?
## 정리
- [ ] 배포와 릴리스를 나누고, 릴리스 · 되돌리기는 값 하나로 (이전 릴리스는 남겨 둔다)
- [ ] 사고 때는 먼저 되돌리고 원인을 찾는다, 되돌리기는 연습해 둔다
- [ ] 카나리는 바뀌지 않는 값으로 나눠 같은 사람은 같은 쪽, 그리고 **보고** 나쁘면 끈다
- [ ] 점검은 한 곳에서, 고정으로
- [ ] 판단을 기록하고, 배포를 받는 곳이 정책으로 지킨다 — 판단하는 쪽은 실행하지 않는다
+206
View File
@@ -0,0 +1,206 @@
# 4단계 · 정리: 오늘 한 일의 의미 · 효과 · 실전 응용
- 교시: 8교시 17:10~17:45 (35분, 바이브 코딩 발표 10분 뒤) · 자료: 이 문서 + [개요](00-DAY74-OVERVIEW.md) 30장
- 이 시간이 끝나면: 오늘 1~3단계에서 한 일이 **무엇을 지키기 위한 것이었는지**, 숫자로 **무엇을 잡았고 무엇을 치렀는지**, 회사에서는 같은 생각을 **어떤 이름과 도구로** 쓰는지 말할 수 있고, 최종 프로젝트에 무엇을 가져갈지 정할 수 있다.
- 숫자: 오늘 교안의 실측(강사 노트북, 10/27 정답 워크플로우 1회)입니다. "예시"라고 적은 계산은 가정을 둔 어림입니다.
```mermaid
flowchart LR
A["1. 오늘 한 장"] --> B["2. 왜 버전 관리가<br/>운영의 일인가"] --> C["3. 효과와 값을 숫자로"] --> D["4. 회사에서 부르는 이름"]
D --> E["5. 산업별 응용"] --> F["6. 운영 대시보드"] --> G["7. 하지 말아야 할 때"] --> H["8. 직무 · 면접"] --> I["9. 최종 프로젝트로"]
```
## 0. 이 시간 (35분)
| 시간 | 절 | 하는 일 |
| --- | --- | --- |
| 17:10~17:15 | 1 · 2 | 오늘 한 장, 왜 버전 관리가 운영의 일인가 |
| 17:15~17:20 | 3 | 오늘 숫자로 본 효과와 값, 한 달에 릴리스 열 번이면 (예시) |
| 17:20~17:28 | 4 · 5 | 같은 생각의 업계 이름 · 도구, 산업별 응용 예 |
| 17:28~17:33 | 6 · 7 | 운영 지표 여섯 개, 하지 말아야 할 때 |
| 17:33~17:40 | 8 · 9 | 직무와 면접 질문, 최종 프로젝트 체크리스트 |
| 17:40~17:45 | 10 · 토론 | 다음 일차와의 연결, 질문 둘 |
## 1. 오늘 한 장
| 단계 | 한 일 | 오늘 숫자 |
| --- | --- | --- |
| 1단계 · 바뀐 것 | diff로 읽기, 반복해서 재기, 답마다 `version`, 지시문 저장소, 저장소가 멈추면 기본 지시문 | v1 → v2 **7개** 바뀜, 출처 번호 v1 **0/6** · v2 **6/6** (3번씩), 저장소 고장 HTTP 500 → **200 `fallback`**, 느림 9.1초 → **3.0초** |
| 2단계 · 내보내기 전 | 릴리스를 밖에서, 후보 고정, 게이트, 기준선, 강한 테스트, 망가진 릴리스 | 약한 golden 1.2.0 **PASS** → 강한 golden **1/9 FAIL**, 1.0.0 3번 **18/18**, 2.1.0 게이트 **PASS → FAIL** |
| 3단계 · 내보내기 | 값 하나 배포 · 되돌리기, 카나리, 나쁜 카나리 끄기, 점검, 점검 기록 · 정책 | 나쁜 카나리 영향 **2/6명** → 끈 뒤 6/6, 점검 1.1.0 promote · 1.2.0 rollback, 정책이 1.2.0 배포 **409** |
| 바이브 코딩 | 워크플로우 버전 저장 · 비교 `tver` (TODO → SPEC → IMP-PLAN → 설치) | 요구 14개 |
한 문장으로: **바뀐 것을 남기고, 내보내기 전에 숫자로 시험하고, 조금씩 내보내고, 언제든 값 하나로 되돌린다.**
## 2. 왜 버전 관리가 운영의 일인가
72일차는 "빠르게, 멈춰도 답하게", 73일차는 "속지 않게, 새지 않게"였습니다. 오늘은 "**바꿔도 망가지지 않게**"입니다. 서비스는 만들고 끝이 아니라 **계속 바뀝니다**. 지시문을 다듬고, 더 싼 모델로 옮기고, 공급자가 모델을 지원 종료합니다.
| | 만들기 | 운영하기 (변경 관리) |
| --- | --- | --- |
| 질문 | "지금 답이 맞나?" | "이걸 바꾸면 무엇이 달라지나? 나쁘면 어떻게 돌아가나?" |
| 기준 | 테스트 통과 | 같은 테스트로 **전 · 후 비교**, 기준선, 반복 |
| 실패 | 고치면 끝 | **먼저 되돌리고** 원인을 찾음, 기록으로 설명 |
| 바꾸는 사람 | 나 | 나 + 기획 · 운영(지시문) + **공급자**(모델) |
- LLM 앱의 변경 사고는 대개 **오류처럼 보이지 않습니다.** 1.2.0은 HTTP 200으로 친절하게 "포털을 보세요"라고 답했고, 2.1.0은 기본 지시문으로 멀쩡해 보였습니다. 테스트와 기록이 없으면 사고가 났는지도 모릅니다.
- 변경 관리는 속도와 **맞바꿈**이 아니라 속도의 **조건**입니다. 되돌리기가 값 하나면 자주 바꿔도 무섭지 않습니다.
## 3. 효과와 값을 숫자로
### 3.1 오늘 실측 전 · 후
| 무엇 | 전 | 후 | 바꾼 것 |
| --- | --- | --- | --- |
| 나쁜 릴리스(1.2.0)를 게이트가 막나 | 6/6 **PASS** (약한 golden) | **1/9 FAIL** | 해결 낱말 · 상태를 보는 강한 golden |
| 망가진 릴리스(2.1.0)를 게이트가 막나 | 9/9 **PASS** (기본 지시문이 숨김) | **0/9 FAIL** | 404는 400 `release broken` |
| 지시문 저장소 고장 | HTTP 500 | 200 · `fallback` (1.3초) | timeout + 오류 출력 + 기본 지시문 |
| 지시문 저장소 느림 (8초) | 9.1초 | 3.0초 | timeout 2초 |
| 나쁜 카나리의 피해 | (전원 배포라면 6/6명) | **2/6명**, 명령 한 줄로 끔 | 카나리 50% + 보고 끄기 |
| 점검 rollback인 릴리스 배포 | 사람이 놓치면 나감 | **409** `not gated` | 점검 기록 + 배포 정책 |
| 되돌리기 | 예전 JSON을 찾아 push (n8n 재시작) | 값 하나, 다음 요청부터 | 릴리스를 밖에서 읽기 |
**치른 값**: 질문마다 데이터 API를 두 번 더 부릅니다(릴리스 · 지시문). VPN p50은 릴리스 · 지시문을 읽는 1.0.0(Lab 02 예제 3)이 1.23초, 둘 다 읽지 않는 v1(Lab 01 예제 3)도 1.23초였습니다 — 5번씩 잰 값으로는 차이가 보이지 않았습니다 (같은 노트북 안의 데이터 API). 대신 **부르는 곳이 늘어** timeout · 기본 지시문 · 404 처리를 만들어야 했습니다 (Lab 01 과제 2, Lab 02 과제 2).
### 3.2 한 달에 릴리스 열 번이면 (예시)
가정: 지시문 · 모델 변경을 한 달에 10번 내보냄, 그중 2번은 무언가를 망가뜨림(오늘 1.2.0 · 2.1.0처럼), 하루 문의 1,000건.
| | 게이트 · 카나리 · 되돌리기 없음 | 오늘의 운영 체계 |
| --- | --- | --- |
| 나쁜 변경 2번이 사용자에게 | 2번 모두 전원에게 (하루 1,000건씩) | 게이트가 1.2.0 · 2.1.0 같은 것을 배포 전에 멈춤 |
| 게이트를 지나 버린 문제 | (해당 없음) | 카나리 5%면 하루 약 50건만 영향, 끄면 끝 |
| 되돌리는 시간 | 파일 찾기 · push · 확인 (분 단위) | 값 하나 (초 단위) |
| 원인 찾기 | "누가 무엇을 바꿨지?" | 답의 `version` · `CHANGELOG.md` · 점검 기록 |
- 게이트 · 카나리는 **문제를 0으로 만들지 않습니다.** 게이트는 테스트만큼만, 카나리는 비율만큼만 줄입니다. 그래서 둘을 **겹으로** 둡니다 (73일차 겹겹이 막기와 같은 생각).
## 4. 회사에서는 같은 생각을 이렇게 부른다
| 오늘 한 것 | 업계 이름 | 쓰는 도구 · 표준 (예) |
| --- | --- | --- |
| `flow.py diff`, `tver` | 구조 diff, 코드 리뷰 | git · pull request, nbdime, `terraform plan` |
| 답마다 `version` | 출처 표시, 추적성 | OpenTelemetry 속성, LLM 관측 도구 (LangSmith · Langfuse 등) |
| 지시문 저장소 p1 · p2 · p3 | 프롬프트 관리 · 레지스트리 | LangSmith Prompt Hub, Langfuse, PromptLayer, MLflow Prompt Registry |
| 릴리스를 밖에서 읽기 | 원격 설정, 기능 플래그 | LaunchDarkly, Unleash, Flagsmith, Firebase Remote Config, AWS AppConfig |
| 기본 지시문 · `fallback` | 우아한 저하, last known good | 설정 클라이언트 캐시, 서킷 브레이커 (72일차) |
| 게이트 · golden | 품질 게이트, 평가 세트, 회귀 테스트 | promptfoo, DeepEval, Ragas, OpenAI Evals, CI(GitHub Actions · GitLab CI) |
| 404는 크게 실패 | fail fast, 설정 검증 | 스키마 검사, 시작 시 확인 |
| 값 하나 배포 · 되돌리기 | 배포와 릴리스 분리, 롤백 | 블루그린, 기능 플래그 끄기 |
| 카나리 · bucket | 카나리 배포, 점진 배포, 고정 배정 | Argo Rollouts, Flagger, Kayenta(카나리 분석), 기능 플래그 % 배포 |
| 점검 기록 · `require_gate` | 변경 관리, 코드로 된 정책, 배포 승인 | 브랜치 보호 규칙, 쿠버네티스 입장 제어 · OPA, ITIL 변경 관리 |
| `CHANGELOG.md` · `RELEASE.md` | 릴리스 노트, 런북 | Keep a Changelog, 운영 런북 |
- 오늘은 n8n 노드 · 작은 데이터 API · `flow.py`로 직접 만들었습니다. 회사에서는 도구가 대신해 주는 경우가 많습니다. 그래도 **무엇을 버전으로 두고, 무엇으로 시험하고, 어떤 기준으로 내보낼지**는 오늘처럼 사람이 정합니다.
## 5. 산업별 응용 (예)
아래는 오늘 한 일을 다른 업무에 옮긴 예입니다 (실제 특정 회사의 사례가 아니라 흔한 모양).
| 업무 | 오늘의 무엇을 | 이렇게 |
| --- | --- | --- |
| 은행 · 보험 상담 챗봇 | 게이트 · 점검 기록 · 정책 | 지시문 변경마다 규제 문구 평가 세트(약관 고지 · 금지 표현)를 통과해야 배포, 승인 기록을 감사에 제출 |
| 쇼핑몰 상품 설명 생성 | 카나리 · 모델 교체 | 더 싼 모델을 상품 5%에만 먼저, 클릭률 · 반품 문의를 운영 쪽과 비교 |
| 콜센터 상담 요약 | 답마다 `version` · 되돌리기 | 요약마다 릴리스를 남겨, 이상한 요약이 나온 날 그 릴리스만 골라 다시 만들고 되돌림 |
| 사내 IT 헬프데스크 (오늘) | 전부 | 지시문은 운영팀이 저장소에서, 배포는 게이트 · 카나리 · 점검 뒤 |
| 사내 문서 RAG (71일차) | 데이터 버전 · 게이트 | 색인을 다시 만들 때 hit@k 게이트, 문제면 이전 색인으로 값 하나 되돌림 |
| 의료 · 법률 문서 보조 | fail fast · 고정 | 지시문 · 모델 이름이 틀리면 조용히 기본값으로 가지 않고 멈춤, 검토자는 고정 릴리스로 재현 |
**토론 거리**: 내 회사(또는 가고 싶은 회사)의 AI 기능 하나를 골라, 위 표의 한 줄을 채워 봅니다.
## 6. 운영 대시보드에 올릴 숫자 여섯
오늘 답 · 기록에 남긴 칸만으로 운영 지표가 됩니다 (77일차 로그 분석 · 79일차 운영 대시보드에서 실제로 모읍니다).
| 지표 | 어느 칸에서 | 무엇을 알 수 있나 |
| --- | --- | --- |
| 릴리스별 요청 수 · 오류율 | `version.release`, HTTP 상태 | 카나리 쪽이 운영 쪽보다 나쁜가 |
| 채널별 비율 | `version.channel` | 카나리가 의도한 비율로 나가나 |
| 기본 지시문 비율 | `version.prompt_source = fallback` | 지시문 저장소가 자주 멈추나 |
| 릴리스별 p95 | `version.release` + 시간 | 새 릴리스가 느려졌나 |
| 게이트 · 점검 결과 | `/gates` (`verdict`, `passed/total`) | 배포 전 실패율, 어느 테스트에서 떨어지나 |
| 배포 · 되돌리기 기록 | `CHANGELOG.md`, 릴리스 변경 시각 | DORA: 배포 빈도 · 변경 실패율 · 복구 시간 |
- 오늘 `version`에 `channel` · `prompt_source`까지 넣게 한 까닭이 이것입니다. **어느 버전이 답했는지 모르면 무엇을 되돌릴지도 모릅니다.**
## 7. 하지 말아야 할 때 (흔한 실수)
| 하기 쉬운 일 | 왜 위험한가 | 대신 |
| --- | --- | --- |
| 지시문 · 모델 · 노드를 한 번에 | 원인을 모름 (v2) | 한 릴리스에 한 가지 |
| 한 번 돌려 보고 "좋아졌다" | 흔들림일 수 있음 | `--repeat 3`, 기준선 |
| 지시문을 덮어써서 고침 | 재현 · 되돌리기 불가 | 새 버전을 더함 (불변) |
| 상태 코드만 보는 게이트 | 1.2.0 같은 답이 PASS | 데이터에서 정한 해결 낱말 |
| 게이트가 FAIL이라 기대를 느슨하게 | 게이트가 의미를 잃음 | 답이 틀렸는지 먼저 |
| 모든 실패를 기본값으로 받음 | 설정 오류가 숨음 (2.1.0) | 4xx는 크게 실패 |
| 카나리를 켜고 보지 않음 | 느린 배포일 뿐 | 같은 질문 · 같은 지표로 비교 |
| 점검 워크플로우가 직접 배포 | 점검이 틀리면 저절로 나감 | 판단 · 기록 · 실행을 나눔 |
| 모델 이름을 하드코딩하고 잊음 | 공급자가 지원 종료하면 멈춤 (수업 준비 때 gpt-4.1-nano) | 한 곳에서, 종료 일정을 지켜봄, 바꿀 때는 모델만 |
## 8. 직무와 면접
오늘 한 일은 다음 직무들이 날마다 하는 일입니다: **AI · LLM 애플리케이션 엔지니어, MLOps · LLMOps 엔지니어, 플랫폼 · DevOps · SRE, 백엔드 엔지니어, AI 서비스 운영 · 품질 담당.** 채용 공고의 "프롬프트 버전 관리", "LLM 평가 · 회귀 테스트", "카나리 · 점진 배포", "기능 플래그", "CI/CD", "변경 관리"가 오늘 내용입니다.
면접에서 흔히 받는 질문과 오늘 숫자로 답하는 법:
<details><summary>1. "프롬프트를 바꿨더니 다른 질문의 답이 나빠졌습니다. 어떻게 막나요?"</summary>
지시문을 워크플로우 밖 저장소에서 불변 버전(p1 · p2 · p3)으로 관리하고, 지시문만 바꾼 릴리스를 만들어 고정 요청으로 평가 세트를 돌립니다. 오늘 상태 코드만 보는 golden은 나쁜 1.2.0을 6/6 PASS로 통과시켰고, 데이터에서 정한 해결 낱말을 보는 golden으로 바꾸자 1/9 FAIL로 막았습니다. 1.0.0을 3번 돌려 18/18로 흔들리지 않는 것도 확인했습니다.
</details>
<details><summary>2. "더 싼 모델로 바꿔도 되는지 어떻게 판단하나요?"</summary>
모델만 바꾼 릴리스(오늘 2.0.0 = 1.1.0의 지시문 + 작은 모델)를 같은 평가 세트로 게이트하고, 시간 · 비용을 같은 날 같이 잽니다. 2.0.0은 강한 golden 9/9, VPN p50 1.12초(1.0.0 1.23초)였습니다. 게이트는 평가 세트에 있는 것만 보므로, 통과해도 바로 전원이 아니라 카나리로 먼저 내보냅니다.
</details>
<details><summary>3. "배포한 버전에 문제가 생기면?"</summary>
먼저 되돌리고 그다음 원인을 찾습니다. 워크플로우가 운영 릴리스를 데이터 API에서 읽게 해서 되돌리기가 값 하나이고, 다음 요청부터 바뀝니다. 아직 카나리 단계면 카나리만 끕니다 — 오늘 1.2.0 50% 카나리는 두 명에게만 나갔고 `--no-canary` 한 줄로 6/6으로 돌아왔습니다. 되돌릴 이전 릴리스와 지시문은 지우지 않고 남겨 둡니다.
</details>
<details><summary>4. "설정 서버가 멈추면 서비스도 멈추나요?"</summary>
멈추지 않게 timeout과 마지막으로 잘 됐던 설정을 둡니다. 오늘 지시문 저장소가 500이면 HTTP 500이던 것을 기본 지시문으로 1.3초에 답하게 했고, 8초 느림은 2초에 포기해 3.0초에 답했습니다. 단, 404처럼 설정 자체가 틀린 것은 기본값으로 숨기지 않고 400으로 크게 실패하게 해서 게이트가 잡게 했습니다 — 안 그러면 망가진 2.1.0이 게이트를 9/9로 통과했습니다.
</details>
<details><summary>5. "점검에서 떨어진 버전이 실수로 배포되는 것은 어떻게 막나요?"</summary>
점검 결과를 기록하고, 배포를 받는 쪽이 기록을 확인하는 정책을 둡니다. 오늘 정책을 켜면 마지막 점검이 promote인 릴리스만 active · 카나리가 되고, 아니면 409를 돌려줍니다. 점검 워크플로우는 기록만 하고 배포는 하지 않습니다(판단과 실행 분리). 브랜치 보호 규칙이나 배포 승인 단계와 같은 생각입니다.
</details>
## 9. 최종 프로젝트로 가져갈 것
최종 프로젝트(85일차~)의 워크플로우에 아래를 넣으면 "운영을 생각한 프로젝트"가 됩니다.
- [ ] 답(또는 로그)마다 `version` (릴리스 · 지시문 · 모델 · 채널)
- [ ] 지시문은 워크플로우 밖에서 불변 버전으로, 모델 이름은 한 곳에서
- [ ] 의존하는 저장소 · 설정에 timeout과 기본값, 설정 오류(4xx)는 크게 실패
- [ ] 평가 세트(golden): 데이터에서 정한 기대, 3번 반복으로 흔들리지 않는지
- [ ] 게이트 기준(통과율 · p95)은 기준선을 재서
- [ ] 후보는 고정해서 시험, 고정의 문은 좁게
- [ ] 값 하나로 배포 · 되돌리기, 되돌리기 연습 기록
- [ ] 카나리 (바뀌지 않는 값으로 나눔) + 비교할 지표
- [ ] 점검 결과 기록 + 배포 정책, `CHANGELOG.md` · `RELEASE.md`
## 10. 다음 일차와의 연결
| 일차 | 오늘의 무엇을 잇나 |
| --- | --- |
| 75 | 사용량과 비용 — 릴리스별 토큰 · 가격, 2.0.0(작은 모델)의 비용 판단, 게이트를 지키며 줄이기 |
| 76 | 에러 처리 · 알림 — 기본 지시문 · `release broken` · 409를 알림으로 |
| 77 | 로그 분석 — 답의 `version`으로 릴리스별 · 채널별로 나눠 세기 |
| 78 | 성능 기준(SLO) — 게이트의 p95 기준을 서비스 목표에서 |
| 79 | 운영 대시보드 · `tops` — 6절의 지표 여섯, `tver`로 워크플로우 변경 보기 |
## 토론 (5분)
1. 오늘 만든 장치(게이트 · 카나리 · 되돌리기 · 정책) 중 **하나만 남길 수 있다면** 무엇을 남기고, 그 까닭은?
2. 내 업무에서 "조용히 기본값으로 계속 가는 것"과 "크게 실패하는 것" 중 무엇이 맞는 의존을 하나씩 들어 봅니다.
## 정리
- [ ] 버전 관리도 **운영**: 바꿔도 망가지지 않게, 그리고 바뀐 것을 남긴다
- [ ] 효과는 같은 날 전 · 후 숫자로 (1.2.0 PASS → FAIL, 2.1.0 PASS → FAIL, 500 → fallback, 6명 → 2명)
- [ ] 같은 생각을 업계에서는 프롬프트 관리 · 원격 설정 · 품질 게이트 · 카나리 · 롤백 · 변경 관리 · 코드로 된 정책이라고 부른다
- [ ] 답의 `version`과 점검 기록이 곧 운영 지표가 된다
- [ ] 최종 프로젝트에 체크리스트 9개를 가져간다
+88
View File
@@ -0,0 +1,88 @@
# 7·8교시 · 바이브 코딩 (`tver`)
- 교시: 7교시 16:00~16:50 (각자 만들기), 8교시 17:00~17:50 (발표 10분 · [4단계 정리](04-STAGE4-WRAPUP.md) 35분 · 예고 5분)
- 과제: [VIBE-REQ-TVER](../../Last3Mon/74/VIBE-REQ-TVER.md)(요청서, 요청의 말 ①~⑫) → [VIBE-Q-TVER](../../Last3Mon/74/VIBE-Q-TVER.md)의 8단계: `Doc/TODO.md` → `Doc/SPEC.md` → `Doc/IMP-PLAN.md`(엔진 · TUI 단계) → 단계별 구현 → 확인 → `uv tool install` → 다른 폴더에서 사용 → 수업 뒤 `VIBE-A-TVER.md` 공개
- 요구 14개(R1~R14). 50분 안에는 **엔진 1단계(R1 · R2 · R3 · R4) + 엔진 2단계(R5 · R6) + TUI 1단계(R8) + 설치(R14)**, 나머지는 IMP-PLAN의 다음 단계로 집에서.
- 72일차 `tbench` · 73일차 `tscan`과 같은 흐름입니다: 사람의 말(요청서)을 **확인할 수 있는 요구**로 바꾸는 일을 학생이 먼저 하고, VIBE-Q는 그 다음 단계(비교 대상)로 씁니다.
- 오늘은 `tver`: n8n 워크플로우 JSON을 git에 버전으로 저장하고, 두 버전을 노드 단위로 비교해 바뀐 것을 **모델 · 지시문 · 흐름**으로 나눠 보여 주는 TUI. 오늘 `flow.py diff`로 한 일을 내 도구로.
```mermaid
flowchart LR
R["요청서 → TODO · SPEC ·<br/>IMP-PLAN 20분"] --> A["7교시<br/>엔진 1 · 2 · TUI · 설치 30분"] --> B["발표<br/>10분"] --> C["4단계 정리<br/>35분"] --> F["75일차 예고<br/>5분"]
```
## 1. 7교시 · 각자 만들기 (50분)
| 시간 | 단계 | 확인 |
| --- | --- | --- |
| 16:00~16:09 | 0 준비 · 1 요청서 → `Doc/TODO.md` (에이전트 없이) | 할 일 ①~⑫ · 꼭 / 나중 |
| 16:09~16:15 | 2 `Doc/SPEC.md` → VIBE-Q 요구 표(R1~R14)와 비교 · commit | 요구마다 확인 방법 (바뀌었다고 나와야 할 것 · 나오면 안 되는 것) |
| 16:15~16:20 | 3 `Doc/IMP-PLAN.md` (엔진 1~4단계 · TUI 1단계 · 설치) · commit | 단계마다 체크리스트 |
| 16:20~16:44 | 4 구현: 엔진 1단계 → 엔진 2단계 → TUI 1단계, 막히면 5 문제 해결 팁 | 단계마다 `pytest` · 직접 실행 · commit |
| 16:44~16:50 | 6 확인 · 7 `uv tool install --editable .` · 8 다른 폴더에서 `tver` | `cd ~/aio-01d4-stu/Last3Mon/74 && tver diff reference/helpdesk-v1.json reference/helpdesk-v2.json` |
| 볼 것 | 막혔을 때 한마디 |
| --- | --- |
| TODO · SPEC 없이 VIBE-Q를 통째로 붙여 넣었나 | "요청서로 Doc/TODO.md부터. 나오면 안 되는 차이가 SPEC에 없으면 시끄러운 diff를 모릅니다" |
| IMP-PLAN이 한 덩어리 · 첫 요청에 14개 | "엔진 1단계만. 단계마다 확인 · 체크 · commit" |
| 모든 노드가 "바뀜" | "id · position을 빼고 비교, 저장할 때도" |
| 노드 짝이 안 맞음 | "name으로 짝짓기" |
| 지시문 diff가 한 줄로 뭉개짐 | "80자 넘는 값은 difflib 줄 단위" |
| 같은 파일인데 커밋이 계속 | "정리 · 키 정렬 후 저장, 바뀐 게 없으면 커밋 안 함" |
| 웹훅 경로를 "모델"로 셈 | "분류 규칙을 SPEC 4절에 글로 정하고 테스트로 고정" |
| 테스트가 `~/.tver/store`를 씀 | "테스트 저장소는 tmp_path (`--repo`)" |
| `tver`를 다른 폴더에서 못 찾음 | "`uv tool list`, `uv tool update-shell` 뒤 새 터미널" |
| 실행해 보지 않고 다음 요청 | "에이전트가 됐다고 해도 직접 `uv run tver`" |
정답 예(R1~R14를 모두 넣은 `tver`와 정답본 `VIBE-A-TVER.md`)는 수업 뒤 공개됩니다. 강사 바이브 코딩(4절)은 시간이 남으면 · 수업 뒤 하고, 걸린 시간을 정답본(`VIBE-A-TVER.md`) 6절에 남깁니다.
## 2. 8교시 · 발표 (10분)
- 1~2명. **오늘 Lab의 helpdesk.json을 단계마다 저장해 비교한 사람**, 위치만 바꾼 파일이 "no change"가 되는 것을 보여 줄 수 있는 사람을 먼저 고릅니다.
- 3분 발표 · 2분 질문: **내 SPEC과 VIBE-Q가 달랐던 곳** / IMP-PLAN을 어떻게 나눴나 / 잘 먹힌 요청문 / 안 먹힌 요청문 / 에이전트가 틀린 곳 / 시연.
## 3. 강사 정리 (8교시 4단계 정리 안에서, 1절 표와 함께)
| | n8n 헬프데스크 (오늘 Lab) | tver |
| --- | --- | --- |
| 바뀐 것 읽기 | `flow.py diff` (Lab 01 예제 1) | 노드 단위 diff + 지시문 줄 diff |
| 바뀌는 것 셋 | 모델 · 지시문 · 흐름 (1단계 강의) | R4 한 줄 요약 |
| 뜻 없는 차이 | — | id · position · 순서 빼기 (R2) |
| 버전 저장 | 데이터 API 릴리스 · 지시문 저장소 (불변) | git (정리해서, 같으면 저장 안 함) |
| 되돌리기 | `release helpdesk 1.0.0` (값 하나) | `tver restore <rev>` → 사람이 push |
| 비밀 | Credential (지시문 · JSON에 키 없음) | 키 모양이면 저장 거부 (R7, 73일차 `tscan`) |
## 4. 강사 바이브 코딩 (시간이 남으면 · 수업 뒤, 15분)
| 주제 | 요청문 요지 | 보여 줄 것 |
| --- | --- | --- |
| 게이트 붙이기 | "tver save 전에 지정한 테스트 파일로 flow.py gate 를 돌려 FAIL 이면 저장 안 함" | 버전 저장 = 게이트 통과 (오늘 3단계 정책과 같은 생각) |
| 지시문만 따로 | "systemMessage 를 꺼내 prompts/<노드>.md 로 저장, 지시문 diff 만 보기" | 오늘 지시문 저장소와 같은 생각 |
| 코드 분류 | "jsCode 는 prompt 가 아니라 code 로 따로 세기" | 분류 규칙은 팀이 정한다 |
걸린 시간과 결과를 `Last3Mon/74/VIBE-A-TVER.md` 6절에 적어 둡니다.
## 5. 보충 · 팁 (읽을거리, 4단계 정리 7절과 함께)
| 팁 | 설명 |
| --- | --- |
| 요청서 → 요구가 먼저 | 확인 방법(바뀐 수 · 버전 수 · 종료 코드)이 있는 요구가 있어야 에이전트의 "완료"를 판단할 수 있음 |
| 비교 전에 정리 | 의미 없는 차이(id · position · 키 순서)를 먼저 없애기 — 비교에도 저장에도 |
| 무엇으로 짝짓나 | 바뀌지 않는 이름 · 키로 (오늘 카나리의 bucket과 같은 생각) |
| 바뀐 것을 분류 | 모델 · 지시문 · 흐름 — 원인 찾기가 빨라짐 |
| 바뀐 게 없으면 커밋하지 않기 | 기록이 깨끗해야 읽힌다 |
| 꺼내기는 사람이 올리게 | 도구가 n8n에 직접 쓰면 실수가 바로 운영으로 |
| 단계마다 `git commit` | 에이전트가 망가뜨려도 돌아갈 곳 |
## 6. 75일차 예고 (5분)
- 오늘: 무엇이 바뀌었나 · 게이트 · 배포 · 되돌리기. 내일: **컴퓨팅 자원 · API 사용량 관리, 비용 최적화** — 오늘 2.0.0(작은 모델)을 품질 · 속도로만 봤습니다. 내일은 토큰과 단가로 비용을 재고, 하루 사용량에 한도를 둡니다.
- 바이브 코딩: `tcost`. 오늘 `tver`는 지우지 마세요. 79일차 `tops`에서 씁니다.
## 확인 (강사)
- [ ] 16:00 요청서 → TODO(에이전트 없이) → SPEC → IMP-PLAN 20분을 지켰다, 16:20부터 엔진 1단계.
- [ ] 7교시 시작 전 `reference/helpdesk-v1.json` · `v2.json`을 시험 자료로 쓰라고 화면에 띄웠다 (`flow.py diff`와 같은 7개가 기준).
- [ ] 수업 뒤: `VIBE-A-TVER.md`의 강사 바이브 코딩 시간을 채우고, 요청이 있을 때 학생 저장소에 정답(LAB-A · `answers/` · VIBE-A · `tver/`)을 올린다.
- [ ] 발표자 1~2명, 강사 정리 표, 팁, 예고.
- [ ] 수업이 끝날 때 학생들의 릴리스가 `--reset` 상태인지, `fault prompts ok`인지 (`doctor`).
+41
View File
@@ -0,0 +1,41 @@
# 74일차 · 모델·Prompt·Workflow 버전 관리와 운영 체계 (바이브 코딩)
- 일자: 2026-10-28 (74일차)
- 교과목: 노코드 멀티 에이전트 워크플로우 설계 및 운영
- 단원: AI 워크플로우 운영 및 관리 역량 강화
- 학습 내용: 모델·Prompt·Workflow 버전 관리 및 운영 체계 구성
- 실습 폴더: [Last3Mon/74](../../Last3Mon/74/README.md)
- 실습 환경: 65일차 n8n 2.42.6 + OpenAI API(`gpt-4.1-mini`, `gpt-5.4-nano`) + 74일차 데이터 API(지시문 저장소 · 릴리스 · 점검 기록 · 배포 정책) + **CLI 코딩 에이전트**
- **실습 준비: [00-DAY74-PREP.md](00-DAY74-PREP.md) (수업 전날까지 1~2절 완료)**
- 수업 형식: 1 · 3 · 5교시 강의, 2 · 4 · 6교시 실습 5개씩 (예제 3 · 과제 2), 7교시 바이브 코딩, 8교시 발표 · 4단계 정리
## 오늘의 시간표
| 교시 | 시간 | 내용 | 자료 |
| --- | --- | --- | --- |
| 1 | 09:00~09:50 | 개요 · 1단계 강의 · 무엇이 바뀌나 | [개요](00-DAY74-OVERVIEW.md) 1~14장, [1단계](01-STAGE1-WHAT-CHANGES.md) |
| 2 | 10:00~10:50 | Lab 01 실습 5개 (diff · version · 흔들림 · 지시문 저장소 · 저장소가 멈추면) | [Lab 01](../../Last3Mon/74/LAB-01-Q-WHAT-CHANGES.md) |
| 3 | 11:00~11:50 | 2단계 강의 · 릴리스와 배포 전 게이트 | 개요 15~21장, [2단계](02-STAGE2-RELEASE-GATE.md) |
| 4 | 12:00~12:50 | Lab 02 실습 5개 (릴리스 읽기 · 게이트 · 기준선 · 강한 테스트 · 망가진 릴리스) | [Lab 02](../../Last3Mon/74/LAB-02-Q-RELEASE-GATE.md) |
| - | 13:00~14:00 | 점심 | |
| 5 | 14:00~14:50 | 3단계 강의 · 배포 · 되돌리기 · 카나리 | 개요 22~28장, [3단계](03-STAGE3-DEPLOY-AND-ROLLBACK.md) |
| 6 | 15:00~15:50 | Lab 03 실습 5개 (배포 · 카나리 · 나쁜 카나리 · 점검 · 점검 기록) | [Lab 03](../../Last3Mon/74/LAB-03-Q-DEPLOY-AND-ROLLBACK.md) |
| 7 | 16:00~16:50 | 바이브 코딩 `tver`: 요청서 → TODO → SPEC → IMP-PLAN → 만들기 → 설치 | [VIBE-REQ-TVER](../../Last3Mon/74/VIBE-REQ-TVER.md), [진행](05-VIBE-CODING.md) |
| 8 | 17:00~17:50 | 발표 · **4단계 정리** (의미 · 효과 · 회사에서 쓰는 법) · 75일차 예고 | [4단계 정리](04-STAGE4-WRAPUP.md), [진행](05-VIBE-CODING.md) |
```mermaid
flowchart LR
P["개요"] --> S1["1단계<br/>diff · 흔들림 · version<br/>지시문 저장소 · 기본 지시문"] --> S2["2단계<br/>릴리스 · 고정 · 게이트<br/>기준선 · 망가진 릴리스"] --> S3["3단계<br/>배포 · 되돌리기 · 카나리<br/>점검 · 정책"] --> V["7교시<br/>tver"] --> W["8교시<br/>4단계 정리"]
```
## 자료 목록
| 파일 | 내용 |
| --- | --- |
| `00-DAY74-PREP.md` | 실습 준비: n8n · 74일차 판 데이터 API 켜기, v1 · v2 게시, `flow.py doctor` (수업 전) |
| `00-DAY74-OVERVIEW.md` | 개요 30장 (PPT 대신 Markdown, 가로줄로 장 구분) |
| `01-STAGE1-WHAT-CHANGES.md` | 왜 버전 관리인가, 바뀌는 것 셋(모델 지원 종료 실제 사례), 차이 읽기, 흔들림과 바뀜, 답마다 버전, 지시문 저장소, 저장소가 멈추면, 시맨틱 버전 |
| `02-STAGE2-RELEASE-GATE.md` | 릴리스, 설정을 밖에서, 후보 고정, 품질 게이트, 게이트는 테스트만큼만, 기준선, 작은 모델 판단, 장애와 설정 오류 |
| `03-STAGE3-DEPLOY-AND-ROLLBACK.md` | 배포와 릴리스, 되돌리기 · DORA, 배포 전략, 카나리 · 고정 배정, 카나리 분석, 점검을 한 곳에서, 판단 기록 · 코드로 된 정책, 운영 체계 |
| `04-STAGE4-WRAPUP.md` | 8교시 35분: 오늘의 의미 · 효과 숫자 · 업계 이름 · 산업별 응용 · 운영 지표 · 면접 · 최종 프로젝트 체크리스트 |
| `05-VIBE-CODING.md` | 7 · 8교시 바이브 코딩 진행 (tver), 발표, 강사 정리, 강사 바이브 코딩, 팁, 예고 |