74일차 정답 공개 (LAB-A · VIBE-A · answers · tver), Lab 03 예제 3 이해하기 보강

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mcchaeandClaude Opus 5.5 committed 2026-10-28 20:43:38 +09:00
1 parent 2ae33432a9
commit d663d045e3
86 files changed
+16144

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
$ uv run tools/flow.py release helpdesk --reset
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate promote 6/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate rollback 0/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
$ uv run tools/flow.py push reference/helpdesk-v1.json reference/helpdesk-v2.json
✔ pushed '74 IT helpdesk v1 (provided)' (id w0ed73a832099852)
POST http://localhost:5678/webhook/rel-v1
✔ pushed '74 IT helpdesk v2 (provided)' (id w8e65b72e125f790)
POST http://localhost:5678/webhook/rel-v2
$ uv run tools/flow.py doctor
! n8n install folder /Users/mcchae/day65-n8n
→ day 65 SETUP-N8N 5단계 (get.n8n.io 설치). 다른 곳에 설치했다면 Last3Mon/65/n8n/.env 에 N8N_HOME=<폴더>
✔ 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
+24
View File
@@ -0,0 +1,24 @@
$ uv run tools/flow.py diff reference/helpdesk-v1.json reference/helpdesk-v2.json
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)
$ uv run tools/flow.py diff reference/helpdesk-v1.json reference/helpdesk-v2.json --full
74 IT helpdesk v1 (provided) → 74 IT helpdesk v2 (provided)
+ node 'Review check' (code)
~ 'Answer' parameters.options.systemMessage: changed (307 → 419 chars)
before: You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say "찾지 못했습니다" and that a staff member will help. Use 합니다체.
after: You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say "찾지 못했습니다" and that a staff member will help. Use 합니다체.
When the answer comes from a FAQ entry, end with its id in brackets, for example [FAQ-003]. Keep answers short.
~ '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)
@@ -0,0 +1,31 @@
$ uv run tools/flow.py call rel-v1 requests/faq-vpn.json -v
HTTP 200 · 2.8s
→ request POST /webhook/rel-v1
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
]
}
$ uv run tools/flow.py call rel-v2 requests/faq-vpn.json -v
HTTP 200 · 1.0s
→ request POST /webhook/rel-v2
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
+72
View File
@@ -0,0 +1,72 @@
{
"cases": "lab01.json",
"webhook": "my-helpdesk",
"passed": 3,
"total": 3,
"results": [
{
"case": "버전이 결과에",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.5,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
},
{
"case": "접수 현황도 버전과 함께",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
},
{
"case": "빈 질문",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 400,
"request": {
"question": "",
"user": "이준호"
},
"response": {
"error": "question is required"
}
}
]
}
+55
View File
@@ -0,0 +1,55 @@
$ uv run tools/flow.py push reference/lab01-example2-version.json
! unpublished '74 IT helpdesk + release API (reference)' (w2caac7c1a6981b8): it used the same webhook path
✔ pushed '74 IT helpdesk + version (reference)' (id w473a152b356a298)
POST http://localhost:5678/webhook/my-helpdesk
$ uv run tools/flow.py test cases/lab01.json -v
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 버전이 결과에 · 2.5s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ 접수 현황도 버전과 함께 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ 빈 질문 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 3/3
report: runs/lab01-20261027-183702.json
@@ -0,0 +1,314 @@
{
"cases": "lab01-compare.json",
"webhook": "rel-v1",
"passed": 9,
"total": 15,
"results": [
{
"case": "VPN · 해결 낱말",
"webhook": "rel-v1",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 해결 낱말",
"webhook": "rel-v1",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 해결 낱말",
"webhook": "rel-v1",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주십시오.",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 출처 번호",
"webhook": "rel-v1",
"run": 1,
"passed": false,
"problems": [
"answer has none of ['[FAQ-003]']"
],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 출처 번호",
"webhook": "rel-v1",
"run": 2,
"passed": false,
"problems": [
"answer has none of ['[FAQ-003]']"
],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 출처 번호",
"webhook": "rel-v1",
"run": 3,
"passed": false,
"problems": [
"answer has none of ['[FAQ-003]']"
],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 내역은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
]
}
},
{
"case": "프린터 · 해결 낱말",
"webhook": "rel-v1",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 해결 낱말",
"webhook": "rel-v1",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 해결 낱말",
"webhook": "rel-v1",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 출처 번호",
"webhook": "rel-v1",
"run": 1,
"passed": false,
"problems": [
"answer has none of ['[FAQ-']"
],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 출처 번호",
"webhook": "rel-v1",
"run": 2,
"passed": false,
"problems": [
"answer has none of ['[FAQ-']"
],
"seconds": 1.2,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 출처 번호",
"webhook": "rel-v1",
"run": 3,
"passed": false,
"problems": [
"answer has none of ['[FAQ-']"
],
"seconds": 1.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "접수 현황",
"webhook": "rel-v1",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": []
}
},
{
"case": "접수 현황",
"webhook": "rel-v1",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": []
}
},
{
"case": "접수 현황",
"webhook": "rel-v1",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": []
}
}
]
}
@@ -0,0 +1,19 @@
$ uv run tools/flow.py test cases/lab01-compare.json --webhook rel-v1 --repeat 3
POST http://localhost:5678/webhook/rel-v1 · 5 case(s) × 3
✔ VPN · 해결 낱말 #1 · 1.2s
✔ VPN · 해결 낱말 #2 · 1.2s
✔ VPN · 해결 낱말 #3 · 1.1s
✘ VPN · 출처 번호 #1 · 1.1s · answer has none of ['[FAQ-003]']
✘ VPN · 출처 번호 #2 · 1.2s · answer has none of ['[FAQ-003]']
✘ VPN · 출처 번호 #3 · 1.3s · answer has none of ['[FAQ-003]']
✔ 프린터 · 해결 낱말 #1 · 1.0s
✔ 프린터 · 해결 낱말 #2 · 1.1s
✔ 프린터 · 해결 낱말 #3 · 1.1s
✘ 프린터 · 출처 번호 #1 · 1.1s · answer has none of ['[FAQ-']
✘ 프린터 · 출처 번호 #2 · 1.2s · answer has none of ['[FAQ-']
✘ 프린터 · 출처 번호 #3 · 1.0s · answer has none of ['[FAQ-']
✔ 접수 현황 #1 · 0.9s
✔ 접수 현황 #2 · 1.0s
✔ 접수 현황 #3 · 1.1s
Passed 9/15
report: runs/lab01-compare-20261027-183719.json
@@ -0,0 +1,302 @@
{
"cases": "lab01-compare.json",
"webhook": "rel-v2",
"passed": 15,
"total": 15,
"results": [
{
"case": "VPN · 해결 낱말",
"webhook": "rel-v2",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 해결 낱말",
"webhook": "rel-v2",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 해결 낱말",
"webhook": "rel-v2",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 출처 번호",
"webhook": "rel-v2",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 출처 번호",
"webhook": "rel-v2",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
},
{
"case": "VPN · 출처 번호",
"webhook": "rel-v2",
"run": 3,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
},
{
"case": "프린터 · 해결 낱말",
"webhook": "rel-v2",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 해결 낱말",
"webhook": "rel-v2",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 해결 낱말",
"webhook": "rel-v2",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 출처 번호",
"webhook": "rel-v2",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인한 뒤, 인쇄 대기열을 비우고 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 출처 번호",
"webhook": "rel-v2",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인한 뒤, 인쇄 대기열을 비우고 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "프린터 · 출처 번호",
"webhook": "rel-v2",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
]
}
},
{
"case": "접수 현황",
"webhook": "rel-v2",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002의 현재 상태는 “처리 중”입니다. 메모에 따르면 인증서 발급 승인 대기 중이며 10월 20일 완료 예정입니다.",
"sources": []
}
},
{
"case": "접수 현황",
"webhook": "rel-v2",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002의 현재 상태는 “처리 중”입니다. 메모에 따르면 인증서 발급 승인 대기 중이며 10월 20일 완료 예정입니다.",
"sources": []
}
},
{
"case": "접수 현황",
"webhook": "rel-v2",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002의 현재 상태는 “처리 중”입니다. 인증서 발급 승인 대기 중이며 “10월 20일 완료 예정”이라고 기록되어 있습니다.",
"sources": []
}
}
]
}
@@ -0,0 +1,19 @@
$ uv run tools/flow.py test cases/lab01-compare.json --webhook rel-v2 --repeat 3
POST http://localhost:5678/webhook/rel-v2 · 5 case(s) × 3
✔ VPN · 해결 낱말 #1 · 1.0s
✔ VPN · 해결 낱말 #2 · 1.0s
✔ VPN · 해결 낱말 #3 · 1.0s
✔ VPN · 출처 번호 #1 · 0.9s
✔ VPN · 출처 번호 #2 · 1.1s
✔ VPN · 출처 번호 #3 · 0.9s
✔ 프린터 · 해결 낱말 #1 · 1.4s
✔ 프린터 · 해결 낱말 #2 · 1.1s
✔ 프린터 · 해결 낱말 #3 · 1.2s
✔ 프린터 · 출처 번호 #1 · 1.4s
✔ 프린터 · 출처 번호 #2 · 1.1s
✔ 프린터 · 출처 번호 #3 · 1.2s
✔ 접수 현황 #1 · 1.1s
✔ 접수 현황 #2 · 1.0s
✔ 접수 현황 #3 · 1.0s
Passed 15/15
report: runs/lab01-compare-20261027-183736.json
@@ -0,0 +1,19 @@
$ uv run tools/flow.py bench rel-v1 requests/faq-vpn.json -n 5
POST http://localhost:5678/webhook/rel-v1 × 5
✔ #1 · HTTP 200 · 1.26s · route=자동 답변
✔ #2 · HTTP 200 · 1.11s · route=자동 답변
✔ #3 · HTTP 200 · 1.23s · route=자동 답변
✔ #4 · HTTP 200 · 1.34s · route=자동 답변
✔ #5 · HTTP 200 · 1.18s · route=자동 답변
p50 1.23s · p95 1.34s · max 1.34s · failures 0/5
report: runs/bench-rel-v1-20261027-183742.json
$ uv run tools/flow.py bench rel-v2 requests/faq-vpn.json -n 5
POST http://localhost:5678/webhook/rel-v2 × 5
✔ #1 · HTTP 200 · 1.20s · route=자동 답변
✔ #2 · HTTP 200 · 0.97s · route=자동 답변
✔ #3 · HTTP 200 · 0.98s · route=자동 답변
✔ #4 · HTTP 200 · 1.02s · route=자동 답변
✔ #5 · HTTP 200 · 0.98s · route=자동 답변
p50 0.98s · p95 1.20s · max 1.20s · failures 0/5
report: runs/bench-rel-v2-20261027-183747.json
@@ -0,0 +1,83 @@
{
"cases": "lab01-challenge.json",
"webhook": "my-helpdesk",
"passed": 2,
"total": 3,
"results": [
{
"case": "지시문은 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"version.prompt_source = null (expected registry)"
],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
},
{
"case": "p1은 출처 번호를 붙이지 않음",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
},
{
"case": "접수 현황",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
}
]
}
@@ -0,0 +1,59 @@
$ uv run tools/flow.py test cases/lab01-challenge.json -v
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✘ 지시문은 저장소에서 · 1.2s · version.prompt_source = null (expected registry)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ p1은 출처 번호를 붙이지 않음 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ 접수 현황 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
Passed 2/3
report: runs/lab01-challenge-20261027-183751.json
@@ -0,0 +1,84 @@
{
"cases": "lab01-challenge.json",
"webhook": "my-helpdesk",
"passed": 3,
"total": 3,
"results": [
{
"case": "지시문은 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.7,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "p1은 출처 번호를 붙이지 않음",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "접수 현황",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,67 @@
$ uv run tools/flow.py push answers/74-lab01-challenge-registry.json
! unpublished '74 IT helpdesk + version (reference)' (w473a152b356a298): it used the same webhook path
✔ pushed '74 IT helpdesk + prompt registry (answer)' (id w10c91b5c7439d37)
POST http://localhost:5678/webhook/my-helpdesk
$ uv run tools/flow.py test cases/lab01-challenge.json -v
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 지시문은 저장소에서 · 2.7s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ p1은 출처 번호를 붙이지 않음 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 접수 현황 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 3/3
report: runs/lab01-challenge-20261027-183810.json
@@ -0,0 +1,74 @@
{
"cases": "lab01.json",
"webhook": "my-helpdesk",
"passed": 3,
"total": 3,
"results": [
{
"case": "버전이 결과에",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "접수 현황도 버전과 함께",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.9,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "빈 질문",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 400,
"request": {
"question": "",
"user": "이준호"
},
"response": {
"error": "question is required"
}
}
]
}
@@ -0,0 +1,52 @@
$ uv run tools/flow.py test cases/lab01.json -v
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 버전이 결과에 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 접수 현황도 버전과 함께 · 0.9s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 빈 질문 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 3/3
report: runs/lab01-20261027-183812.json
@@ -0,0 +1,110 @@
{
"cases": "lab01-challenge2.json",
"webhook": "my-helpdesk",
"passed": 2,
"total": 4,
"results": [
{
"case": "평소 · 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "저장소 고장 · 기본 지시문으로 답",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 500 (expected 200)",
"route = null (expected 자동 답변)",
"version.prompt_source = null (expected fallback)",
"answer has none of ['인증서']"
],
"seconds": 0.1,
"status": 500,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"message": "Error in workflow"
}
},
{
"case": "저장소 느림 · 오래 기다리지 않음",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"took 9.1s (expected at most 6s)",
"version.prompt_source = registry (expected fallback)"
],
"seconds": 9.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "되돌린 뒤 · 다시 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,137 @@
$ uv run tools/flow.py test cases/lab01-challenge2.json -v
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✘ 저장소 고장 · 기본 지시문으로 답 · 0.1s · status 500 (expected 200); route = null (expected 자동 답변); version.prompt_source = null (expected fallback); answer has none of ['인증서']
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 500
{
"message": "Error in workflow"
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "slow",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "slow",
"slow_seconds": 8.0
}
✘ 저장소 느림 · 오래 기다리지 않음 · 9.1s · took 9.1s (expected at most 6s); version.prompt_source = registry (expected fallback)
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 2/4
report: runs/lab01-challenge2-20261027-183824.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
@@ -0,0 +1,112 @@
{
"cases": "lab01-challenge2.json",
"webhook": "my-helpdesk",
"passed": 4,
"total": 4,
"results": [
{
"case": "평소 · 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.6,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "저장소 고장 · 기본 지시문으로 답",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "fallback"
}
}
},
{
"case": "저장소 느림 · 오래 기다리지 않음",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 3.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "fallback"
}
}
},
{
"case": "되돌린 뒤 · 다시 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
}
]
}
+152
View File
@@ -0,0 +1,152 @@
$ uv run tools/flow.py push answers/74-lab01-challenge2-fallback.json
! unpublished '74 IT helpdesk + prompt registry (answer)' (w10c91b5c7439d37): it used the same webhook path
✔ pushed '74 IT helpdesk + prompt fallback (answer)' (id w5215c7f75644ed0)
POST http://localhost:5678/webhook/my-helpdesk
$ uv run tools/flow.py test cases/lab01-challenge2.json -v
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 2.6s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 기본 지시문으로 답 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "slow",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "slow",
"slow_seconds": 8.0
}
✔ 저장소 느림 · 오래 기다리지 않음 · 3.0s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 4/4
report: runs/lab01-challenge2-20261027-183845.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
@@ -0,0 +1,84 @@
{
"cases": "lab01-challenge.json",
"webhook": "my-helpdesk",
"passed": 3,
"total": 3,
"results": [
{
"case": "지시문은 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "p1은 출처 번호를 붙이지 않음",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "접수 현황",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,62 @@
$ uv run tools/flow.py test cases/lab01-challenge.json -v
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 지시문은 저장소에서 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ p1은 출처 번호를 붙이지 않음 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 접수 현황 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 3/3
report: runs/lab01-challenge-20261027-183849.json
+144
View File
@@ -0,0 +1,144 @@
{
"cases": "lab02.json",
"webhook": "my-helpdesk",
"passed": 1,
"total": 5,
"results": [
{
"case": "운영 중인 릴리스 (active)",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"version.channel = null (expected stable)"
],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "후보 시험 · 1.1.0 고정",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"version.release = 1.0.0 (expected 1.1.0)",
"version.channel = null (expected pinned)",
"version.prompt_version = p1 (expected p2)",
"answer has none of ['[FAQ-003]']"
],
"seconds": 1.6,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "후보 시험 · 2.0.0 (작은 모델)",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"version.release = 1.0.0 (expected 2.0.0)",
"version.model = gpt-4.1-mini (expected gpt-5.4-nano)"
],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "없는 릴리스",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 200 (expected 400)",
"known has none of ['1.0.0']"
],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
},
{
"case": "빈 질문",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 400,
"request": {
"question": "",
"user": "이준호"
},
"response": {
"error": "question is required"
}
}
]
}
+142
View File
@@ -0,0 +1,142 @@
$ uv run tools/flow.py release helpdesk
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate promote 6/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate rollback 0/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
$ uv run tools/flow.py test cases/lab02.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✘ 운영 중인 릴리스 (active) · 1.2s · version.channel = null (expected stable)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✘ 후보 시험 · 1.1.0 고정 · 1.6s · version.release = 1.0.0 (expected 1.1.0); version.channel = null (expected pinned); version.prompt_version = p1 (expected p2); answer has none of ['[FAQ-003]']
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✘ 후보 시험 · 2.0.0 (작은 모델) · 1.3s · version.release = 1.0.0 (expected 2.0.0); version.model = gpt-4.1-mini (expected gpt-5.4-nano)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✘ 없는 릴리스 · 1.2s · status 200 (expected 400); known has none of ['1.0.0']
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 빈 질문 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 1/5
report: runs/lab02-20261027-183854.json
+132
View File
@@ -0,0 +1,132 @@
{
"cases": "lab02.json",
"webhook": "my-helpdesk",
"passed": 5,
"total": 5,
"results": [
{
"case": "운영 중인 릴리스 (active)",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "후보 시험 · 1.1.0 고정",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "후보 시험 · 2.0.0 (작은 모델)",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. 접수 건은 찾지 못했습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "없는 릴리스",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 400,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
},
"response": {
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
},
{
"case": "빈 질문",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.2,
"status": 400,
"request": {
"question": "",
"user": "이준호"
},
"response": {
"error": "question is required"
}
}
]
}
+140
View File
@@ -0,0 +1,140 @@
$ uv run tools/flow.py push reference/lab02-example1-release.json
! unpublished '74 IT helpdesk + prompt fallback (answer)' (w5215c7f75644ed0): it used the same webhook path
✔ pushed '74 IT helpdesk + release API (reference)' (id w2caac7c1a6981b8)
POST http://localhost:5678/webhook/my-helpdesk
$ uv run tools/flow.py test cases/lab02.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 운영 중인 릴리스 (active) · 2.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 후보 시험 · 1.1.0 고정 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ 후보 시험 · 2.0.0 (작은 모델) · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. 접수 건은 찾지 못했습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ 없는 릴리스 · 0.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 400
{
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
✔ 빈 질문 · 0.2s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 5/5
report: runs/lab02-20261027-183911.json
@@ -0,0 +1,116 @@
{
"cases": "lab01-challenge2.json",
"webhook": "my-helpdesk",
"passed": 4,
"total": 4,
"results": [
{
"case": "평소 · 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "저장소 고장 · 기본 지시문으로 답",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "fallback"
}
}
},
{
"case": "저장소 느림 · 오래 기다리지 않음",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 3.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "fallback"
}
}
},
{
"case": "되돌린 뒤 · 다시 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,21 @@
$ uv run tools/flow.py test cases/lab01-challenge.json
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 지시문은 저장소에서 · 1.2s
✔ p1은 출처 번호를 붙이지 않음 · 1.3s
✔ 접수 현황 · 1.2s
Passed 3/3
report: runs/lab01-challenge-20261027-183915.json
$ uv run tools/flow.py test cases/lab01-challenge2.json
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 1.4s
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 기본 지시문으로 답 · 1.1s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
✔ 저장소 느림 · 오래 기다리지 않음 · 3.1s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.3s
Passed 4/4
report: runs/lab01-challenge2-20261027-183922.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
+172
View File
@@ -0,0 +1,172 @@
{
"cases": "golden.json",
"webhook": "my-helpdesk",
"passed": 6,
"total": 6,
"results": [
{
"case": "VPN",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. 접수 건은 찾지 못했습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "비밀번호",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "사내 포털 로그인 화면의 ‘비밀번호 찾기’에서 휴대폰 인증 후 새 비밀번호를 설정합니다. [FAQ-001] \n접수 건은 찾지 못했습니다.",
"sources": [
"FAQ-001"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 2.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인한 뒤, 인쇄 대기열을 비우고 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "메일함",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보해 주시기 바랍니다. [FAQ-005] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 중",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002의 현재 상태는 **처리 중**입니다. 메모에 따르면 **인증서 발급 승인 대기 중이며 10월 20일 완료 예정**입니다.",
"sources": [],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1001은 **처리 완료** 상태이며, **새 배터리로 교체했습니다**. 추가로 확인이 필요하시면 담당자에게 문의해 주세요.",
"sources": [],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
}
]
}
+75
View File
@@ -0,0 +1,75 @@
$ uv run tools/flow.py gate cases/golden.json --set release=1.0.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.2s
✔ 비밀번호 · 1.2s
✔ 프린터 · 1.1s
✔ 메일함 · 1.3s
✔ 접수 처리 중 · 1.0s
✔ 접수 처리 완료 · 1.6s
Passed 6/6
report: runs/golden-20261027-183929.json
gate [release=1.0.0]: pass 6/6 (100%) · p95 1.6s → PASS
✔ gate record → /gates/helpdesk: 1.0.0 promote
$ uv run tools/flow.py gate cases/golden.json --set release=1.1.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.1s
✔ 비밀번호 · 1.0s
✔ 프린터 · 1.1s
✔ 메일함 · 1.1s
✔ 접수 처리 중 · 1.1s
✔ 접수 처리 완료 · 1.1s
Passed 6/6
report: runs/golden-20261027-183936.json
gate [release=1.1.0]: pass 6/6 (100%) · p95 1.1s → PASS
✔ gate record → /gates/helpdesk: 1.1.0 promote
$ uv run tools/flow.py gate cases/golden.json --set release=1.2.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 0.8s
✔ 비밀번호 · 0.9s
✔ 프린터 · 0.8s
✔ 메일함 · 1.0s
✔ 접수 처리 중 · 1.2s
✔ 접수 처리 완료 · 1.5s
Passed 6/6
report: runs/golden-20261027-183942.json
gate [release=1.2.0]: pass 6/6 (100%) · p95 1.5s → PASS
✔ gate record → /gates/helpdesk: 1.2.0 promote
$ uv run tools/flow.py gate cases/golden.json --set release=2.0.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.1s
✔ 비밀번호 · 1.1s
✔ 프린터 · 2.0s
✔ 메일함 · 1.2s
✔ 접수 처리 중 · 1.0s
✔ 접수 처리 완료 · 1.2s
Passed 6/6
report: runs/golden-20261027-183950.json
gate [release=2.0.0]: pass 6/6 (100%) · p95 2.0s → PASS
✔ gate record → /gates/helpdesk: 2.0.0 promote
$ uv run tools/flow.py call my-helpdesk '{"question": "프린터 출력이 안 돼요", "user": "최유진", "release": "1.2.0"}' -v
HTTP 200 · 0.9s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.2.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
+500
View File
@@ -0,0 +1,500 @@
{
"cases": "golden.json",
"webhook": "my-helpdesk",
"passed": 18,
"total": 18,
"results": [
{
"case": "VPN",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "VPN",
"webhook": "my-helpdesk",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "VPN",
"webhook": "my-helpdesk",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "비밀번호",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "비밀번호 재설정은 사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정하시면 됩니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-001"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "비밀번호",
"webhook": "my-helpdesk",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "비밀번호 재설정은 사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정하시면 됩니다. 현재 접수된 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-001"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "비밀번호",
"webhook": "my-helpdesk",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "비밀번호 재설정은 사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정하시면 됩니다. 현재 접수된 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-001"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "프린터",
"webhook": "my-helpdesk",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "프린터",
"webhook": "my-helpdesk",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "메일함",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "메일함",
"webhook": "my-helpdesk",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "메일함",
"webhook": "my-helpdesk",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 중",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 중",
"webhook": "my-helpdesk",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 중",
"webhook": "my-helpdesk",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.5,
"status": 200,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "티켓 T-1001은 처리 완료되었으며, 새 배터리로 교체되었습니다. 추가 문의 사항이 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 2,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "티켓 T-1001은 처리 완료되었으며, 새 배터리로 교체되었습니다. 추가 문의 사항이 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 3,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "티켓 T-1001은 처리 완료되었으며, 새 배터리로 교체되었습니다. 추가 문의 사항이 있으시면 연락 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
}
]
}
+44
View File
@@ -0,0 +1,44 @@
$ uv run tools/flow.py bench my-helpdesk requests/faq-vpn.json -n 5 --set release=1.0.0
POST http://localhost:5678/webhook/my-helpdesk × 5
✔ #1 · HTTP 200 · 1.18s · route=자동 답변, release=1.0.0
✔ #2 · HTTP 200 · 1.42s · route=자동 답변, release=1.0.0
✔ #3 · HTTP 200 · 1.17s · route=자동 답변, release=1.0.0
✔ #4 · HTTP 200 · 1.23s · route=자동 답변, release=1.0.0
✔ #5 · HTTP 200 · 1.23s · route=자동 답변, release=1.0.0
p50 1.23s · p95 1.42s · max 1.42s · failures 0/5
report: runs/bench-my-helpdesk-20261027-183957.json
$ uv run tools/flow.py bench my-helpdesk requests/faq-vpn.json -n 5 --set release=2.0.0
POST http://localhost:5678/webhook/my-helpdesk × 5
✔ #1 · HTTP 200 · 1.10s · route=자동 답변, release=2.0.0
✔ #2 · HTTP 200 · 1.24s · route=자동 답변, release=2.0.0
✔ #3 · HTTP 200 · 1.05s · route=자동 답변, release=2.0.0
✔ #4 · HTTP 200 · 1.12s · route=자동 답변, release=2.0.0
✔ #5 · HTTP 200 · 1.12s · route=자동 답변, release=2.0.0
p50 1.12s · p95 1.24s · max 1.24s · failures 0/5
report: runs/bench-my-helpdesk-20261027-184003.json
$ uv run tools/flow.py gate cases/golden.json --set release=1.0.0 --repeat 3 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 3
✔ VPN #1 · 1.1s
✔ VPN #2 · 1.1s
✔ VPN #3 · 1.2s
✔ 비밀번호 #1 · 1.1s
✔ 비밀번호 #2 · 1.3s
✔ 비밀번호 #3 · 1.3s
✔ 프린터 #1 · 1.1s
✔ 프린터 #2 · 1.0s
✔ 프린터 #3 · 1.3s
✔ 메일함 #1 · 1.1s
✔ 메일함 #2 · 1.1s
✔ 메일함 #3 · 1.1s
✔ 접수 처리 중 #1 · 1.1s
✔ 접수 처리 중 #2 · 1.0s
✔ 접수 처리 중 #3 · 1.0s
✔ 접수 처리 완료 #1 · 1.5s
✔ 접수 처리 완료 #2 · 1.1s
✔ 접수 처리 완료 #3 · 1.1s
Passed 18/18
report: runs/golden-20261027-184024.json
gate [release=1.0.0]: pass 18/18 (100%) · p95 1.5s → PASS
✔ gate record → /gates/helpdesk: 1.0.0 promote
+254
View File
@@ -0,0 +1,254 @@
{
"cases": "74-lab02-challenge-golden.json",
"webhook": "my-helpdesk",
"passed": 9,
"total": 9,
"results": [
{
"case": "VPN · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003] \n접수 건은 찾지 못했습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "VPN · 순서도 (업데이트)",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "비밀번호 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "사내 포털 로그인 화면의 ‘비밀번호 찾기’에서 휴대폰 인증 후 새 비밀번호를 설정합니다. [FAQ-001] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-001"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "프린터 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인한 뒤, 인쇄 대기열을 비우고 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "메일함 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보하시면 됩니다. [FAQ-005] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 중 · 상태와 예정일",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002의 현재 상태는 **처리 중**입니다. 메모에 따르면 **인증서 발급 승인 대기 중이며 10월 20일 완료 예정**입니다.",
"sources": [],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 중 · 예정일",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002의 현재 상태는 **처리 중**입니다. 메모에 따르면 **인증서 발급 승인 대기 중이며 10월 20일 완료 예정**입니다.",
"sources": [],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1001은 **처리 완료** 상태이며, **새 배터리로 교체했습니다**. 추가로 확인이 필요하시면 담당자에게 문의하시기 바랍니다.",
"sources": [],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "포털로 떠넘기지 않기",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,63 @@
$ uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=1.0.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.2s
✔ VPN · 순서도 (업데이트) · 1.3s
✔ 비밀번호 · 해결 방법 · 1.4s
✔ 프린터 · 해결 방법 · 1.1s
✔ 메일함 · 해결 방법 · 1.2s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.0s
✔ 접수 처리 완료 · 1.1s
✔ 포털로 떠넘기지 않기 · 1.1s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184034.json
gate [release=1.0.0]: pass 9/9 (100%) · p95 1.4s → PASS
✔ gate record → /gates/helpdesk: 1.0.0 promote
$ uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=1.1.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.0s
✔ VPN · 순서도 (업데이트) · 1.7s
✔ 비밀번호 · 해결 방법 · 1.1s
✔ 프린터 · 해결 방법 · 1.3s
✔ 메일함 · 해결 방법 · 1.0s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.0s
✔ 접수 처리 완료 · 1.0s
✔ 포털로 떠넘기지 않기 · 1.2s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184045.json
gate [release=1.1.0]: pass 9/9 (100%) · p95 1.7s → PASS
✔ gate record → /gates/helpdesk: 1.1.0 promote
$ uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=1.2.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✘ VPN · 해결 방법 · 0.9s · answer has none of ['인증서']
✘ VPN · 순서도 (업데이트) · 0.7s · answer has none of ['업데이트', '최신']
✘ 비밀번호 · 해결 방법 · 0.9s · answer has none of ['비밀번호 찾기', '휴대폰']
✘ 프린터 · 해결 방법 · 0.9s · answer has none of ['대기열', '전원']
✘ 메일함 · 해결 방법 · 0.9s · answer has none of ['보관함', '삭제']
✘ 접수 처리 중 · 상태와 예정일 · 1.0s · answer has none of ['처리 중']
✘ 접수 처리 중 · 예정일 · 0.9s · answer has none of ['10월 20일', '20일']
✘ 접수 처리 완료 · 1.3s · answer has none of ['처리 완료', '완료']
✔ 포털로 떠넘기지 않기 · 0.7s
Passed 1/9
report: runs/74-lab02-challenge-golden-20261027-184053.json
gate [release=1.2.0]: pass 1/9 (11%) · p95 1.3s → FAIL · pass rate 11% < 100%
✔ gate record → /gates/helpdesk: 1.2.0 rollback
$ uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=2.0.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.1s
✔ VPN · 순서도 (업데이트) · 1.3s
✔ 비밀번호 · 해결 방법 · 1.3s
✔ 프린터 · 해결 방법 · 1.3s
✔ 메일함 · 해결 방법 · 1.3s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.1s
✔ 접수 처리 완료 · 1.2s
✔ 포털로 떠넘기지 않기 · 1.1s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184104.json
gate [release=2.0.0]: pass 9/9 (100%) · p95 1.3s → PASS
✔ gate record → /gates/helpdesk: 2.0.0 promote
@@ -0,0 +1,254 @@
{
"cases": "74-lab02-challenge-golden.json",
"webhook": "my-helpdesk",
"passed": 9,
"total": 9,
"results": [
{
"case": "VPN · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "VPN · 순서도 (업데이트)",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "비밀번호 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.5,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "비밀번호 재설정은 사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정하시면 됩니다. 현재 접수된 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-001"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "프린터 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "메일함 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "접수 처리 중 · 상태와 예정일",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "접수 처리 중 · 예정일",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "티켓 T-1001은 처리 완료되었습니다. 새 배터리로 교체했습니다. 추가 도움이 필요하시면 연락 바랍니다.",
"sources": [],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "포털로 떠넘기지 않기",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.1.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
}
]
}
@@ -0,0 +1,261 @@
$ uv run tools/flow.py test cases/lab02-challenge2.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✘ 망가진 릴리스 2.1.0 · 400 release broken · 1.3s · status 200 (expected 400); error = null (expected release broken); prompt_version = null (expected p9)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
✔ 평소 · 1.1.0은 저장소의 p2 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 1.1.0도 기본 지시문으로 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 없는 릴리스는 그대로 400 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 400
{
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 운영(active 1.0.0)은 그대로 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 4/5
report: runs/lab02-challenge2-20261027-184109.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
$ uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=2.1.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.2s
✔ VPN · 순서도 (업데이트) · 1.2s
✔ 비밀번호 · 해결 방법 · 1.5s
✔ 프린터 · 해결 방법 · 1.1s
✔ 메일함 · 해결 방법 · 1.3s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.0s
✔ 접수 처리 완료 · 1.0s
✔ 포털로 떠넘기지 않기 · 1.0s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184120.json
gate [release=2.1.0]: pass 9/9 (100%) · p95 1.5s → PASS
✔ gate record → /gates/helpdesk: 2.1.0 promote
@@ -0,0 +1,206 @@
{
"cases": "74-lab02-challenge-golden.json",
"webhook": "my-helpdesk",
"passed": 0,
"total": 9,
"results": [
{
"case": "VPN · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"route = null (expected 자동 답변)",
"answer has none of ['인증서']"
],
"seconds": 0.1,
"status": 400,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "VPN · 순서도 (업데이트)",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"answer has none of ['업데이트', '최신']"
],
"seconds": 0.1,
"status": 400,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "비밀번호 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"answer has none of ['비밀번호 찾기', '휴대폰']"
],
"seconds": 0.1,
"status": 400,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "프린터 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"answer has none of ['대기열', '전원']"
],
"seconds": 0.0,
"status": 400,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "메일함 · 해결 방법",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"answer has none of ['보관함', '삭제']"
],
"seconds": 0.1,
"status": 400,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "접수 처리 중 · 상태와 예정일",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"answer has none of ['처리 중']"
],
"seconds": 0.1,
"status": 400,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "접수 처리 중 · 예정일",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"answer has none of ['10월 20일', '20일']"
],
"seconds": 0.1,
"status": 400,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)",
"answer has none of ['처리 완료', '완료']"
],
"seconds": 0.0,
"status": 400,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "포털로 떠넘기지 않기",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"status 400 (expected 200)"
],
"seconds": 0.0,
"status": 400,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
}
]
}
+257
View File
@@ -0,0 +1,257 @@
$ uv run tools/flow.py push answers/74-lab02-challenge2-broken.json
! unpublished '74 IT helpdesk + release API (reference)' (w2caac7c1a6981b8): it used the same webhook path
✔ pushed '74 IT helpdesk + broken release check (answer)' (id w86d00ad9781296e)
POST http://localhost:5678/webhook/my-helpdesk
$ uv run tools/flow.py test cases/lab02-challenge2.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 망가진 릴리스 2.1.0 · 400 release broken · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
}
← response HTTP 400
{
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
✔ 평소 · 1.1.0은 저장소의 p2 · 2.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 1.1.0도 기본 지시문으로 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 없는 릴리스는 그대로 400 · 0.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 400
{
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 운영(active 1.0.0)은 그대로 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 5/5
report: runs/lab02-challenge2-20261027-184139.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
$ uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=2.1.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✘ VPN · 해결 방법 · 0.1s · status 400 (expected 200); route = null (expected 자동 답변); answer has none of ['인증서']
✘ VPN · 순서도 (업데이트) · 0.1s · status 400 (expected 200); answer has none of ['업데이트', '최신']
✘ 비밀번호 · 해결 방법 · 0.1s · status 400 (expected 200); answer has none of ['비밀번호 찾기', '휴대폰']
✘ 프린터 · 해결 방법 · 0.0s · status 400 (expected 200); answer has none of ['대기열', '전원']
✘ 메일함 · 해결 방법 · 0.1s · status 400 (expected 200); answer has none of ['보관함', '삭제']
✘ 접수 처리 중 · 상태와 예정일 · 0.1s · status 400 (expected 200); answer has none of ['처리 중']
✘ 접수 처리 중 · 예정일 · 0.1s · status 400 (expected 200); answer has none of ['10월 20일', '20일']
✘ 접수 처리 완료 · 0.0s · status 400 (expected 200); answer has none of ['처리 완료', '완료']
✘ 포털로 떠넘기지 않기 · 0.0s · status 400 (expected 200)
Passed 0/9
report: runs/74-lab02-challenge-golden-20261027-184140.json
gate [release=2.1.0]: pass 0/9 (0%) · p95 0.0s → FAIL · pass rate 0% < 100%
✔ gate record → /gates/helpdesk: 2.1.0 rollback
@@ -0,0 +1,116 @@
{
"cases": "lab01-challenge2.json",
"webhook": "my-helpdesk",
"passed": 4,
"total": 4,
"results": [
{
"case": "평소 · 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "저장소 고장 · 기본 지시문으로 답",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "fallback"
}
}
},
{
"case": "저장소 느림 · 오래 기다리지 않음",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 3.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "fallback"
}
}
},
{
"case": "되돌린 뒤 · 다시 저장소에서",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,24 @@
$ uv run tools/flow.py test cases/lab02.json
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 운영 중인 릴리스 (active) · 1.3s
✔ 후보 시험 · 1.1.0 고정 · 1.1s
✔ 후보 시험 · 2.0.0 (작은 모델) · 1.2s
✔ 없는 릴리스 · 0.1s
✔ 빈 질문 · 0.1s
Passed 5/5
report: runs/lab02-20261027-184144.json
$ uv run tools/flow.py test cases/lab01-challenge2.json
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 1.2s
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 기본 지시문으로 답 · 1.2s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
✔ 저장소 느림 · 오래 기다리지 않음 · 3.0s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.0s
Passed 4/4
report: runs/lab01-challenge2-20261027-184150.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
+62
View File
@@ -0,0 +1,62 @@
{
"cases": "lab03.json",
"webhook": "my-helpdesk",
"passed": 2,
"total": 2,
"results": [
{
"case": "배포 · 1.1.0을 active로",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "되돌리기 · 1.0.0",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
+260
View File
@@ -0,0 +1,260 @@
$ uv run tools/flow.py release helpdesk 1.1.0
✔ helpdesk: active 1.1.0 · no canary
1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
* 1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
$ uv run tools/flow.py call my-helpdesk requests/faq-vpn.json -v
HTTP 200 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
$ uv run tools/flow.py release helpdesk 1.0.0
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
$ uv run tools/flow.py call my-helpdesk requests/faq-vpn.json -v
HTTP 200 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
$ uv run tools/flow.py test cases/lab03.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 2 case(s) × 1
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.1.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.1.0"
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.1.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ 배포 · 1.1.0을 active로 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.0.0"
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ 되돌리기 · 1.0.0 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 2/2
report: runs/lab03-20261027-184155.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
@@ -0,0 +1,180 @@
{
"cases": "lab03-canary.json",
"webhook": "my-helpdesk",
"passed": 3,
"total": 6,
"results": [
{
"case": "최유진 (bucket 0) → 카나리",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"version.release = 1.0.0 (expected 1.1.0)",
"version.channel = stable (expected canary)"
],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "정우성 (bucket 42) → 카나리",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"version.release = 1.0.0 (expected 1.1.0)",
"version.channel = stable (expected canary)"
],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "정우성"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "이준호 (bucket 56) → 운영",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "김민지 (bucket 80) → 운영",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "고정이 카나리보다 먼저",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "같은 사람은 늘 같은 쪽",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"version.release = 1.0.0 (expected 1.1.0)",
"version.channel = stable (expected canary)"
],
"seconds": 1.2,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,259 @@
$ uv run tools/flow.py release helpdesk --canary 1.1.0 --percent 50
✔ helpdesk: active 1.0.0 · canary 1.1.0 50%
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
c 1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
canary users: 최유진(0), 정우성(42)
$ uv run tools/flow.py test cases/lab03-canary.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": {"release": "1.1.0", "percent": 50}, "require_gate": false, "releases"
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "1.1.0",
"percent": 50
}
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": {
"release": "1.1.0",
"percent": 50
},
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✘ 최유진 (bucket 0) → 카나리 · 1.1s · version.release = 1.0.0 (expected 1.1.0); version.channel = stable (expected canary)
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✘ 정우성 (bucket 42) → 카나리 · 1.3s · version.release = 1.0.0 (expected 1.1.0); version.channel = stable (expected canary)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 이준호 (bucket 56) → 운영 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 (bucket 80) → 운영 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 고정이 카나리보다 먼저 · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✘ 같은 사람은 늘 같은 쪽 · 1.2s · version.release = 1.0.0 (expected 1.1.0); version.channel = stable (expected canary)
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 3/6
report: runs/lab03-canary-20261027-184202.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
+135
View File
@@ -0,0 +1,135 @@
{
"cases": "lab02-challenge2.json",
"webhook": "my-helpdesk",
"passed": 5,
"total": 5,
"results": [
{
"case": "망가진 릴리스 2.1.0 · 400 release broken",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.1,
"status": 400,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
},
"response": {
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
},
{
"case": "평소 · 1.1.0은 저장소의 p2",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "저장소 고장 · 1.1.0도 기본 지시문으로",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주십시오.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
},
{
"case": "저장소 고장 · 없는 릴리스는 그대로 400",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 400,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
},
"response": {
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
},
{
"case": "운영(active 1.0.0)은 그대로",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
+291
View File
@@ -0,0 +1,291 @@
$ uv run tools/flow.py push reference/lab03-example2-canary.json
! unpublished '74 IT helpdesk + broken release check (answer)' (w86d00ad9781296e): it used the same webhook path
✔ pushed '74 IT helpdesk + canary (reference)' (id w2e1e5fc96604a25)
POST http://localhost:5678/webhook/my-helpdesk
$ uv run tools/flow.py test cases/lab03-canary.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": {"release": "1.1.0", "percent": 50}, "require_gate": false, "releases"
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "1.1.0",
"percent": 50
}
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": {
"release": "1.1.0",
"percent": 50
},
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ 최유진 (bucket 0) → 카나리 · 2.8s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-004]",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✔ 정우성 (bucket 42) → 카나리 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 문의해 주세요. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✔ 이준호 (bucket 56) → 운영 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 (bucket 80) → 운영 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 고정이 카나리보다 먼저 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ 같은 사람은 늘 같은 쪽 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-004]",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
Passed 6/6
report: runs/lab03-canary-20261027-184226.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
$ uv run tools/flow.py test cases/lab02.json
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 운영 중인 릴리스 (active) · 1.6s
✔ 후보 시험 · 1.1.0 고정 · 1.3s
✔ 후보 시험 · 2.0.0 (작은 모델) · 1.4s
✔ 없는 릴리스 · 0.1s
✔ 빈 질문 · 0.1s
Passed 5/5
report: runs/lab02-20261027-184231.json
$ uv run tools/flow.py test cases/lab02-challenge2.json
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 망가진 릴리스 2.1.0 · 400 release broken · 0.1s
✔ 평소 · 1.1.0은 저장소의 p2 · 1.2s
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 1.1.0도 기본 지시문으로 · 1.2s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 없는 릴리스는 그대로 400 · 0.0s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ 운영(active 1.0.0)은 그대로 · 1.1s
Passed 5/5
report: runs/lab02-challenge2-20261027-184235.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
$ uv run tools/flow.py release helpdesk --reset
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
+174
View File
@@ -0,0 +1,174 @@
{
"cases": "lab03-watch.json",
"webhook": "my-helpdesk",
"passed": 4,
"total": 6,
"results": [
{
"case": "최유진 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"answer has none of ['대기열']"
],
"seconds": 0.9,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
},
{
"case": "정우성 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": false,
"problems": [
"answer has none of ['대기열']"
],
"seconds": 0.9,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "정우성"
},
"response": {
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
},
{
"case": "한지훈 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "한지훈"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "이준호 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "박서연 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "김민지 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "김민지"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
+221
View File
@@ -0,0 +1,221 @@
$ uv run tools/flow.py test cases/lab03-watch.json -v
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": {"release": "1.2.0", "percent": 50}, "require_gate": false, "releases"
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "1.2.0",
"percent": 50
}
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": {
"release": "1.2.0",
"percent": 50
},
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✘ 최유진 · 프린터 · 0.9s · answer has none of ['대기열']
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✘ 정우성 · 프린터 · 0.9s · answer has none of ['대기열']
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✔ 한지훈 · 프린터 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "한지훈"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 이준호 · 프린터 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 박서연 · 프린터 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 · 프린터 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 4/6
report: runs/lab03-watch-20261027-184241.json
$ uv run tools/flow.py release helpdesk
✔ helpdesk: active 1.0.0 · canary 1.2.0 50%
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
c 1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
canary users: 최유진(0), 정우성(42)
@@ -0,0 +1,170 @@
{
"cases": "lab03-watch.json",
"webhook": "my-helpdesk",
"passed": 6,
"total": 6,
"results": [
{
"case": "최유진 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "정우성 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "정우성"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "한지훈 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.0,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "한지훈"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "이준호 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "박서연 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.2,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "박서연"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "김민지 · 프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "김민지"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,146 @@
$ uv run tools/flow.py release helpdesk --no-canary
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
$ uv run tools/flow.py test cases/lab03-watch.json --no-before -v
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ 최유진 · 프린터 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 정우성 · 프린터 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 한지훈 · 프린터 · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "한지훈"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 이준호 · 프린터 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 박서연 · 프린터 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 · 프린터 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 6/6
report: runs/lab03-watch-20261027-184249.json
$ uv run tools/flow.py release helpdesk --reset
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
@@ -0,0 +1,68 @@
{
"cases": "lab03-challenge.json",
"webhook": "my-release-check",
"passed": 3,
"total": 3,
"results": [
{
"case": "1.1.0 · 내보내도 됨",
"webhook": "my-release-check",
"run": 1,
"passed": true,
"problems": [],
"seconds": 3.1,
"status": 200,
"request": {
"release": "1.1.0"
},
"response": {
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 2,
"verdict": "promote"
}
},
{
"case": "1.2.0 · 되돌림",
"webhook": "my-release-check",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.6,
"status": 200,
"request": {
"release": "1.2.0"
},
"response": {
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 1.6,
"verdict": "rollback"
}
},
{
"case": "릴리스 없음",
"webhook": "my-release-check",
"run": 1,
"passed": true,
"problems": [],
"seconds": 0.0,
"status": 400,
"request": {},
"response": {
"error": "release is required"
}
}
]
}
@@ -0,0 +1,67 @@
$ uv run tools/flow.py push answers/74-lab03-challenge-release-check.json
! unpublished '74 Release check + gate record (answer)' (w9589252e9498695): it used the same webhook path
✔ pushed '74 Release check (answer)' (id w50ae3687e1cc859)
POST http://localhost:5678/webhook/my-release-check
$ uv run tools/flow.py test cases/lab03-challenge.json -v
POST http://localhost:5678/webhook/my-release-check · 3 case(s) × 1
✔ 1.1.0 · 내보내도 됨 · 3.1s
→ request POST /webhook/my-release-check
{
"release": "1.1.0"
}
← response HTTP 200
{
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 2,
"verdict": "promote"
}
✔ 1.2.0 · 되돌림 · 1.6s
→ request POST /webhook/my-release-check
{
"release": "1.2.0"
}
← response HTTP 200
{
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 1.6,
"verdict": "rollback"
}
✔ 릴리스 없음 · 0.0s
→ request POST /webhook/my-release-check
{}
← response HTTP 400
{
"error": "release is required"
}
Passed 3/3
report: runs/lab03-challenge-20261027-184308.json
$ uv run tools/flow.py call my-release-check '{"release": "2.0.0"}' -v
HTTP 200 · 1.9s
→ request POST /webhook/my-release-check
{
"release": "2.0.0"
}
← response HTTP 200
{
"release": "2.0.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 1.8,
"verdict": "promote"
}
@@ -0,0 +1,201 @@
$ uv run tools/flow.py test cases/lab03-challenge2.json -v
✔ before: POST http://localhost:8800/gates/helpdesk/clear · HTTP 200 · 0.0s · {"cleared": 19}
→ request POST http://localhost:8800/gates/helpdesk/clear
{}
← response HTTP 200
{
"cleared": 19
}
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk/policy · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": true, "releases": {"1.0.0": {"prompt_version": "
→ request POST http://localhost:8800/releases/helpdesk/policy
{
"require_gate": true
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": true,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-release-check · 5 case(s) × 1
✘ 1.2.0 점검 · 기록 · 24.2s · recorded = null (expected true)
→ request POST /webhook/my-release-check
{
"release": "1.2.0"
}
← response HTTP 200
{
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 24.1,
"verdict": "rollback"
}
✘ 1.1.0 점검 · 기록 · 7.9s · recorded = null (expected true)
→ request POST /webhook/my-release-check
{
"release": "1.1.0"
}
← response HTTP 200
{
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 7.9,
"verdict": "promote"
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "1.2.0", "last_gate": null}
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.2.0"
}
← response HTTP 409
{
"error": "not gated",
"release": "1.2.0",
"last_gate": null
}
✔ 점검에 떨어진 1.2.0은 배포 거부 (409) · 운영은 1.0.0 · 7.9s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✘ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "1.1.0", "last_gate": null}
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.1.0"
}
← response HTTP 409
{
"error": "not gated",
"release": "1.1.0",
"last_gate": null
}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✘ the 'before' step failed (http://localhost:8800/releases/helpdesk). Is it pushed / is the data API the day 74 version?
@@ -0,0 +1,138 @@
{
"cases": "lab03-challenge2.json",
"webhook": "my-release-check",
"passed": 5,
"total": 5,
"results": [
{
"case": "1.2.0 점검 · 기록",
"webhook": "my-release-check",
"run": 1,
"passed": true,
"problems": [],
"seconds": 10.6,
"status": 200,
"request": {
"release": "1.2.0"
},
"response": {
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 9.5,
"verdict": "rollback",
"recorded": true
}
},
{
"case": "1.1.0 점검 · 기록",
"webhook": "my-release-check",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.9,
"status": 200,
"request": {
"release": "1.1.0"
},
"response": {
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 1.7,
"verdict": "promote",
"recorded": true
}
},
{
"case": "점검에 떨어진 1.2.0은 배포 거부 (409) · 운영은 1.0.0",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 7.7,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주십시오.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "점검을 지난 1.1.0은 배포",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하시면 문의해 주세요. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
},
{
"case": "점검 안 한 2.0.0은 카나리도 거부 (409)",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진"
},
"response": {
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-004]",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
}
]
}
+317
View File
@@ -0,0 +1,317 @@
$ uv run tools/flow.py push answers/74-lab03-challenge2-gated.json
! unpublished '74 Release check (answer)' (w50ae3687e1cc859): it used the same webhook path
✔ pushed '74 Release check + gate record (answer)' (id w9589252e9498695)
POST http://localhost:5678/webhook/my-release-check
$ uv run tools/flow.py test cases/lab03-challenge2.json -v
✔ before: POST http://localhost:8800/gates/helpdesk/clear · HTTP 200 · 0.0s · {"cleared": 0}
→ request POST http://localhost:8800/gates/helpdesk/clear
{}
← response HTTP 200
{
"cleared": 0
}
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk/policy · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": true, "releases": {"1.0.0": {"prompt_version": "
→ request POST http://localhost:8800/releases/helpdesk/policy
{
"require_gate": true
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": true,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-release-check · 5 case(s) × 1
✔ 1.2.0 점검 · 기록 · 10.6s
→ request POST /webhook/my-release-check
{
"release": "1.2.0"
}
← response HTTP 200
{
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 9.5,
"verdict": "rollback",
"recorded": true
}
✔ 1.1.0 점검 · 기록 · 1.9s
→ request POST /webhook/my-release-check
{
"release": "1.1.0"
}
← response HTTP 200
{
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 1.7,
"verdict": "promote",
"recorded": true
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "1.2.0", "last_gate": {"t": 1793094252.829, "release": "1.2.0", "verdict": "rollback",
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.2.0"
}
← response HTTP 409
{
"error": "not gated",
"release": "1.2.0",
"last_gate": {
"t": 1793094252.829,
"release": "1.2.0",
"verdict": "rollback",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 9.5,
"source": "my-release-check"
}
}
✔ 점검에 떨어진 1.2.0은 배포 거부 (409) · 운영은 1.0.0 · 7.7s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주십시오.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.1.0", "canary": null, "require_gate": true, "releases": {"1.0.0": {"prompt_version": "
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.1.0"
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.1.0",
"canary": null,
"require_gate": true,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ 점검을 지난 1.1.0은 배포 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하시면 문의해 주세요. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "2.0.0", "last_gate": null}
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "2.0.0",
"percent": 50
}
}
← response HTTP 409
{
"error": "not gated",
"release": "2.0.0",
"last_gate": null
}
✔ 점검 안 한 2.0.0은 카나리도 거부 (409) · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-004]",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 5/5
report: runs/lab03-challenge2-20261027-184425.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
$ uv run tools/flow.py release helpdesk
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용)
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
+172
View File
@@ -0,0 +1,172 @@
{
"cases": "golden.json",
"webhook": "my-helpdesk",
"passed": 6,
"total": 6,
"results": [
{
"case": "VPN",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "비밀번호",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"user": "한지훈",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "사내 포털 로그인 화면의 ‘비밀번호 찾기’에서 휴대폰 인증 후 새 비밀번호를 설정하시면 됩니다. [FAQ-001] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-001"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "프린터",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.3,
"status": 200,
"request": {
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "프린터 출력이 안 될 때는 프린터 전원과 네트워크 연결을 확인한 뒤, 인쇄 대기열을 비우고 다시 출력해 보시기 바랍니다. [FAQ-004] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "메일함",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.4,
"status": 200,
"request": {
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보해 주시면 됩니다. [FAQ-005] \n접수 건은 찾지 못했습니다. 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 중",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1002의 현재 상태는 **처리 중**입니다. 메모에 따르면 **인증서 발급 승인 대기 중이며 10월 20일 완료 예정**입니다.",
"sources": [],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
},
{
"case": "접수 처리 완료",
"webhook": "my-helpdesk",
"run": 1,
"passed": true,
"problems": [],
"seconds": 1.1,
"status": 200,
"request": {
"question": "T-1001 어떻게 됐나요?",
"user": "이준호",
"release": "2.0.0"
},
"response": {
"route": "자동 답변",
"answer": "T-1001은 **처리 완료** 상태이며, **새 배터리로 교체했습니다**. 추가로 확인이 필요하시면 담당자에게 문의해 주세요.",
"sources": [],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
}
]
}
@@ -0,0 +1,53 @@
$ uv run tools/flow.py release helpdesk --require-gate on
✔ helpdesk: active 1.0.0 · no canary · deploy needs a passing gate
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용)
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
$ uv run tools/flow.py release helpdesk 2.1.0
✘ refused: 2.1.0 has no passing gate record (never gated)
→ run the gate (flow.py gate ... --set release=<version>) or my-release-check first
$ uv run tools/flow.py gate cases/golden.json --set release=2.0.0 --max-p95 3
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.1s
✔ 비밀번호 · 1.1s
✔ 프린터 · 1.3s
✔ 메일함 · 1.4s
✔ 접수 처리 중 · 1.1s
✔ 접수 처리 완료 · 1.1s
Passed 6/6
report: runs/golden-20261027-184432.json
gate [release=2.0.0]: pass 6/6 (100%) · p95 1.4s → PASS
✔ gate record → /gates/helpdesk: 2.0.0 promote
$ uv run tools/flow.py release helpdesk 2.0.0
✔ helpdesk: active 2.0.0 · no canary · deploy needs a passing gate
1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
* 2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 6/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
$ uv run tools/flow.py release helpdesk --reset
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 6/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
$ uv run tools/flow.py doctor
! n8n install folder /Users/mcchae/day65-n8n
→ day 65 SETUP-N8N 5단계 (get.n8n.io 설치). 다른 곳에 설치했다면 Last3Mon/65/n8n/.env 에 N8N_HOME=<폴더>
✔ 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
@@ -0,0 +1,357 @@
{
"name": "74 IT helpdesk + prompt registry (answer)",
"nodes": [
{
"name": "Question webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-helpdesk",
"responseMode": "responseNode",
"options": {}
},
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
"position": [
0,
300
],
"id": "d898f230-a253-55da-9ec5-4288291c08dd"
},
{
"name": "Has question?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.body.question }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
220,
300
],
"id": "185f04ea-0ee8-514b-b3fd-11b89b1c0c2e"
},
{
"name": "Reject empty",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "{ \"error\": \"question is required\" }",
"options": {
"responseCode": 400
}
},
"position": [
440,
560
],
"id": "8a3b377f-f3fa-56e8-a9c4-98a3e014ce61"
},
{
"name": "Release info",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"parameters": {
"assignments": {
"assignments": [
{
"id": "r1",
"name": "release",
"value": "1.0.0",
"type": "string"
},
{
"id": "r2",
"name": "prompt_version",
"value": "p1",
"type": "string"
},
{
"id": "r3",
"name": "model",
"value": "gpt-4.1-mini",
"type": "string"
}
]
},
"options": {}
},
"position": [
440,
300
],
"id": "b33700b3-849d-5b4f-8d8c-42723207125b"
},
{
"name": "Get prompt",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "=http://data-api:8800/prompts/helpdesk/{{ $('Release info').first().json.prompt_version }}",
"options": {}
},
"position": [
660,
300
],
"id": "a39d24f1-c645-50c9-940e-59e381744fc1"
},
{
"name": "Search FAQ",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/faq",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "={{ $('Question webhook').first().json.body.question }}"
}
]
}
},
"position": [
880,
300
],
"id": "c7fa8ee8-ba2b-5266-9d1a-ffa5dd8743b5"
},
{
"name": "Get ticket",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "requester",
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
}
]
}
},
"position": [
1100,
300
],
"id": "b4fd19f5-03d4-5b31-a654-4b0ac4c32fac"
},
{
"name": "Answer",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"parameters": {
"promptType": "define",
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
"options": {
"systemMessage": "={{ $('Get prompt').first().json.text }}",
"maxIterations": 1
}
},
"position": [
1320,
300
],
"id": "1a80728e-83e0-58d7-9477-42d4099502c6"
},
{
"name": "Answer model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"parameters": {
"model": {
"__rl": true,
"mode": "id",
"value": "={{ $('Release info').first().json.model }}"
},
"options": {
"temperature": 0
}
},
"credentials": {
"openAiApi": {
"id": "openAiCourse0001",
"name": "OpenAI (.env)"
}
},
"position": [
1320,
520
],
"id": "4c69869f-3300-5834-8124-e64dfcc8135e"
},
{
"name": "Build result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const results = $('Search FAQ').first().json.results ?? [];\nconst result = { route: '자동 답변', answer: String($json.output ?? '').trim(), sources: results.map((r) => r.id) };\nconst i = $('Release info').first().json;\nresult.version = { release: i.release, prompt_version: i.prompt_version, model: i.model, prompt_source: 'registry' };\nreturn [{ json: result }];"
},
"position": [
1540,
300
],
"id": "7c284309-570a-5987-b139-e1be350994fd"
},
{
"name": "Send result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
1760,
300
],
"id": "32a9aaf4-2a59-5388-8a24-0e9f28c346e4"
}
],
"connections": {
"Question webhook": {
"main": [
[
{
"node": "Has question?",
"type": "main",
"index": 0
}
]
]
},
"Has question?": {
"main": [
[
{
"node": "Release info",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject empty",
"type": "main",
"index": 0
}
]
]
},
"Release info": {
"main": [
[
{
"node": "Get prompt",
"type": "main",
"index": 0
}
]
]
},
"Get prompt": {
"main": [
[
{
"node": "Search FAQ",
"type": "main",
"index": 0
}
]
]
},
"Search FAQ": {
"main": [
[
{
"node": "Get ticket",
"type": "main",
"index": 0
}
]
]
},
"Get ticket": {
"main": [
[
{
"node": "Answer",
"type": "main",
"index": 0
}
]
]
},
"Answer model": {
"ai_languageModel": [
[
{
"node": "Answer",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Answer": {
"main": [
[
{
"node": "Build result",
"type": "main",
"index": 0
}
]
]
},
"Build result": {
"main": [
[
{
"node": "Send result",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,435 @@
{
"name": "74 IT helpdesk + prompt fallback (answer)",
"nodes": [
{
"name": "Question webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-helpdesk",
"responseMode": "responseNode",
"options": {}
},
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
"position": [
0,
300
],
"id": "956f6ead-f979-5d78-bedf-13743406c6b5"
},
{
"name": "Has question?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.body.question }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
220,
300
],
"id": "b946f570-248c-5436-a6d6-06ab2b15db29"
},
{
"name": "Reject empty",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "{ \"error\": \"question is required\" }",
"options": {
"responseCode": 400
}
},
"position": [
440,
560
],
"id": "e9818b28-ea3d-517c-ab00-02f86427c3c5"
},
{
"name": "Release info",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"parameters": {
"assignments": {
"assignments": [
{
"id": "r1",
"name": "release",
"value": "1.0.0",
"type": "string"
},
{
"id": "r2",
"name": "prompt_version",
"value": "p1",
"type": "string"
},
{
"id": "r3",
"name": "model",
"value": "gpt-4.1-mini",
"type": "string"
}
]
},
"options": {}
},
"position": [
440,
300
],
"id": "0ffcc09f-8eff-56b4-801e-9c8e2a40930c"
},
{
"name": "Get prompt",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "=http://data-api:8800/prompts/helpdesk/{{ $('Release info').first().json.prompt_version }}",
"options": {
"timeout": 2000
}
},
"position": [
660,
300
],
"id": "578695ac-1a56-57c4-863b-08677838f02c",
"onError": "continueErrorOutput"
},
{
"name": "Search FAQ",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/faq",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "={{ $('Question webhook').first().json.body.question }}"
}
]
}
},
"position": [
1100,
300
],
"id": "322d506e-a6a2-52bc-b153-170da84009c2"
},
{
"name": "Get ticket",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "requester",
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
}
]
}
},
"position": [
1320,
300
],
"id": "3caf9b96-2920-54de-a905-3eabacb6f6e9"
},
{
"name": "Answer",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"parameters": {
"promptType": "define",
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
"options": {
"systemMessage": "={{ $('Registry prompt').isExecuted ? $('Registry prompt').first().json.text : $('Fallback prompt').first().json.text }}",
"maxIterations": 1
}
},
"position": [
1540,
300
],
"id": "500a6055-13cd-5000-9255-4f854efc5bb5"
},
{
"name": "Answer model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"parameters": {
"model": {
"__rl": true,
"mode": "id",
"value": "={{ $('Release info').first().json.model }}"
},
"options": {
"temperature": 0
}
},
"credentials": {
"openAiApi": {
"id": "openAiCourse0001",
"name": "OpenAI (.env)"
}
},
"position": [
1540,
520
],
"id": "efcecd80-eb64-54b9-ba80-23ce47e0d416"
},
{
"name": "Build result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const results = $('Search FAQ').first().json.results ?? [];\nconst result = { route: '자동 답변', answer: String($json.output ?? '').trim(), sources: results.map((r) => r.id) };\nconst i = $('Release info').first().json;\nresult.version = { release: i.release, prompt_version: i.prompt_version, model: i.model, prompt_source: ($('Registry prompt').isExecuted ? 'registry' : 'fallback') };\nreturn [{ json: result }];"
},
"position": [
1760,
300
],
"id": "999ca8b6-3571-5e6d-8c0d-d681a8cf40ee"
},
{
"name": "Send result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
1980,
300
],
"id": "f9bbb5e8-0e9c-5c3b-9a23-204b66884821"
},
{
"name": "Registry prompt",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
880,
300
],
"id": "8161625d-2c7f-5acc-9377-3545a0ced05c",
"parameters": {
"assignments": {
"assignments": [
{
"id": "f0",
"name": "text",
"value": "={{ $json.text }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"name": "Fallback prompt",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
880,
540
],
"id": "d9000207-de8d-561e-865c-216430daeaa5",
"parameters": {
"assignments": {
"assignments": [
{
"id": "f0",
"name": "text",
"value": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help. Use 합니다체.",
"type": "string"
}
]
},
"options": {}
}
}
],
"connections": {
"Question webhook": {
"main": [
[
{
"node": "Has question?",
"type": "main",
"index": 0
}
]
]
},
"Has question?": {
"main": [
[
{
"node": "Release info",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject empty",
"type": "main",
"index": 0
}
]
]
},
"Release info": {
"main": [
[
{
"node": "Get prompt",
"type": "main",
"index": 0
}
]
]
},
"Get prompt": {
"main": [
[
{
"node": "Registry prompt",
"type": "main",
"index": 0
}
],
[
{
"node": "Fallback prompt",
"type": "main",
"index": 0
}
]
]
},
"Search FAQ": {
"main": [
[
{
"node": "Get ticket",
"type": "main",
"index": 0
}
]
]
},
"Get ticket": {
"main": [
[
{
"node": "Answer",
"type": "main",
"index": 0
}
]
]
},
"Answer model": {
"ai_languageModel": [
[
{
"node": "Answer",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Answer": {
"main": [
[
{
"node": "Build result",
"type": "main",
"index": 0
}
]
]
},
"Build result": {
"main": [
[
{
"node": "Send result",
"type": "main",
"index": 0
}
]
]
},
"Registry prompt": {
"main": [
[
{
"node": "Search FAQ",
"type": "main",
"index": 0
}
]
]
},
"Fallback prompt": {
"main": [
[
{
"node": "Search FAQ",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,114 @@
{
"webhook": "my-helpdesk",
"cases": [
{
"name": "VPN · 해결 방법",
"request": "requests/faq-vpn.json",
"expect": {
"equals": {
"route": "자동 답변"
},
"contains": {
"answer": [
"인증서"
]
}
}
},
{
"name": "VPN · 순서도 (업데이트)",
"request": "requests/faq-vpn.json",
"expect": {
"contains": {
"answer": [
"업데이트",
"최신"
]
}
}
},
{
"name": "비밀번호 · 해결 방법",
"request": "requests/faq-password.json",
"expect": {
"contains": {
"answer": [
"비밀번호 찾기",
"휴대폰"
]
}
}
},
{
"name": "프린터 · 해결 방법",
"request": "requests/faq-printer.json",
"expect": {
"contains": {
"answer": [
"대기열",
"전원"
]
}
}
},
{
"name": "메일함 · 해결 방법",
"request": "requests/faq-mail.json",
"expect": {
"contains": {
"answer": [
"보관함",
"삭제"
]
}
}
},
{
"name": "접수 처리 중 · 상태와 예정일",
"request": "requests/ticket-own.json",
"expect": {
"contains": {
"answer": [
"처리 중"
]
}
}
},
{
"name": "접수 처리 중 · 예정일",
"request": "requests/ticket-own.json",
"expect": {
"contains": {
"answer": [
"10월 20일",
"20일"
]
}
}
},
{
"name": "접수 처리 완료",
"request": "requests/ticket-done.json",
"expect": {
"contains": {
"answer": [
"처리 완료",
"완료"
]
}
}
},
{
"name": "포털로 떠넘기지 않기",
"request": "requests/faq-printer.json",
"expect": {
"not_contains": {
"answer": [
"IT 포털을 확인",
"포털을 참고"
]
}
}
}
]
}
@@ -0,0 +1,571 @@
{
"name": "74 IT helpdesk + broken release check (answer)",
"nodes": [
{
"name": "Question webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-helpdesk",
"responseMode": "responseNode",
"options": {}
},
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
"position": [
0,
300
],
"id": "a45a7cee-22e6-5b71-a0fd-3229330b4bbc"
},
{
"name": "Has question?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.body.question }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
220,
300
],
"id": "299d8e82-eeab-5f99-9a46-137788c8ebb9"
},
{
"name": "Reject empty",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "{ \"error\": \"question is required\" }",
"options": {
"responseCode": 400
}
},
"position": [
440,
560
],
"id": "03fcdd93-2dbb-572f-b5fb-1c9ff133f3ea"
},
{
"name": "Get release",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/releases/helpdesk",
"options": {}
},
"position": [
440,
300
],
"id": "03108783-0952-544a-a765-fa3f085af473"
},
{
"name": "Pick release",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const body = $('Question webhook').first().json.body;\nconst r = $json;\nconst bucket = [...String(body.user ?? '')].reduce((sum, ch) => sum + ch.codePointAt(0), 0) % 100;\nlet release = r.active, channel = 'stable';\nif (body.release) { release = String(body.release); channel = 'pinned'; }\n\nconst info = r.releases?.[release];\nif (!info) return [{ json: { ok: false, release, known: Object.keys(r.releases ?? {}) } }];\nreturn [{ json: { ok: true, release, channel, bucket, prompt_version: info.prompt_version, model: info.model } }];"
},
"position": [
660,
300
],
"id": "c8b634b5-7537-561f-95ea-c96ef4bca3f5"
},
{
"name": "Known release?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.ok }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
880,
300
],
"id": "1ed7677b-7a5f-51d2-a53e-ba8492a36fd4"
},
{
"name": "Reject release",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ error: 'unknown release', release: $json.release, known: $json.known }) }}",
"options": {
"responseCode": 400
}
},
"position": [
1100,
560
],
"id": "64c8dfc5-03fb-5c28-b8b9-1982526a211a"
},
{
"name": "Get prompt",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "=http://data-api:8800/prompts/helpdesk/{{ $('Pick release').first().json.prompt_version }}",
"options": {
"timeout": 2000,
"response": {
"response": {
"neverError": true,
"fullResponse": true
}
}
}
},
"position": [
1100,
300
],
"id": "77e6bc79-43c9-530b-8b64-905fa6e2d7b8",
"onError": "continueErrorOutput"
},
{
"name": "Search FAQ",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/faq",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "={{ $('Question webhook').first().json.body.question }}"
}
]
}
},
"position": [
1760,
300
],
"id": "255ba11b-b6c8-5d20-81d3-0ef7031b9f53"
},
{
"name": "Get ticket",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "requester",
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
}
]
}
},
"position": [
1980,
300
],
"id": "b1e5fa0b-afae-5885-a4cf-e0703d11057d"
},
{
"name": "Answer",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"parameters": {
"promptType": "define",
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
"options": {
"systemMessage": "={{ $('Registry prompt').isExecuted ? $('Registry prompt').first().json.text : $('Fallback prompt').first().json.text }}",
"maxIterations": 1
}
},
"position": [
2200,
300
],
"id": "3ef8a476-ca75-5139-8560-67ef9c9f2e50"
},
{
"name": "Answer model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"parameters": {
"model": {
"__rl": true,
"mode": "id",
"value": "={{ $('Pick release').first().json.model }}"
},
"options": {
"temperature": 0
}
},
"credentials": {
"openAiApi": {
"id": "openAiCourse0001",
"name": "OpenAI (.env)"
}
},
"position": [
2200,
520
],
"id": "1dfcbaad-7d59-5856-be0c-5d2321a43364"
},
{
"name": "Build result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const results = $('Search FAQ').first().json.results ?? [];\nconst result = { route: '자동 답변', answer: String($json.output ?? '').trim(), sources: results.map((r) => r.id) };\nconst i = $('Pick release').first().json;\nresult.version = { release: i.release, prompt_version: i.prompt_version, model: i.model, channel: i.channel, prompt_source: ($('Registry prompt').isExecuted && $('Registry prompt').first().json.state === 'registry') ? 'registry' : 'fallback' };\nreturn [{ json: result }];"
},
"position": [
2420,
300
],
"id": "96b32475-5056-5d64-9d49-a4422d523aa4"
},
{
"name": "Send result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
2640,
300
],
"id": "45eb0e50-740f-5969-9ddf-af23290e4484"
},
{
"name": "Registry prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1320,
300
],
"id": "516ccc9d-19d1-5b72-b514-55df1f1c6426",
"parameters": {
"jsCode": "// 200: the registry prompt. 404: the release names a prompt that does not exist (a broken release).\n// anything else (500 ...): an outage, so use the built-in prompt like the error output does.\nconst status = $json.statusCode;\nif (status === 200 && $json.body?.text) return [{ json: { state: 'registry', text: $json.body.text } }];\nif (status === 404) return [{ json: { state: 'broken' } }];\nreturn [{ json: { state: 'fallback', text: \"You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \\\"찾지 못했습니다\\\" and that a staff member will help. Use 합니다체.\" } }];"
}
},
{
"name": "Fallback prompt",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1320,
540
],
"id": "21a69db0-9a21-5f97-94a5-55679b268089",
"parameters": {
"assignments": {
"assignments": [
{
"id": "f0",
"name": "text",
"value": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help. Use 합니다체.",
"type": "string"
}
]
},
"options": {}
}
},
{
"name": "Prompt broken?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1540,
300
],
"id": "9d349380-cde8-5736-ae9c-301eac84eb07",
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.state }}",
"rightValue": "broken",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"name": "Reject broken",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1760,
780
],
"id": "d1579bb1-e209-584f-a9f4-89d779cdad40",
"parameters": {
"respondWith": "json",
"options": {
"responseCode": 400
},
"responseBody": "={{ JSON.stringify({ error: 'release broken', release: $('Pick release').first().json.release, prompt_version: $('Pick release').first().json.prompt_version }) }}"
}
}
],
"connections": {
"Question webhook": {
"main": [
[
{
"node": "Has question?",
"type": "main",
"index": 0
}
]
]
},
"Has question?": {
"main": [
[
{
"node": "Get release",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject empty",
"type": "main",
"index": 0
}
]
]
},
"Get release": {
"main": [
[
{
"node": "Pick release",
"type": "main",
"index": 0
}
]
]
},
"Pick release": {
"main": [
[
{
"node": "Known release?",
"type": "main",
"index": 0
}
]
]
},
"Known release?": {
"main": [
[
{
"node": "Get prompt",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject release",
"type": "main",
"index": 0
}
]
]
},
"Get prompt": {
"main": [
[
{
"node": "Registry prompt",
"type": "main",
"index": 0
}
],
[
{
"node": "Fallback prompt",
"type": "main",
"index": 0
}
]
]
},
"Search FAQ": {
"main": [
[
{
"node": "Get ticket",
"type": "main",
"index": 0
}
]
]
},
"Get ticket": {
"main": [
[
{
"node": "Answer",
"type": "main",
"index": 0
}
]
]
},
"Answer model": {
"ai_languageModel": [
[
{
"node": "Answer",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Answer": {
"main": [
[
{
"node": "Build result",
"type": "main",
"index": 0
}
]
]
},
"Build result": {
"main": [
[
{
"node": "Send result",
"type": "main",
"index": 0
}
]
]
},
"Registry prompt": {
"main": [
[
{
"node": "Prompt broken?",
"type": "main",
"index": 0
}
]
]
},
"Fallback prompt": {
"main": [
[
{
"node": "Search FAQ",
"type": "main",
"index": 0
}
]
]
},
"Prompt broken?": {
"main": [
[
{
"node": "Reject broken",
"type": "main",
"index": 0
}
],
[
{
"node": "Search FAQ",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,206 @@
{
"name": "74 Release check (answer)",
"nodes": [
{
"name": "Check webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-release-check",
"responseMode": "responseNode",
"options": {}
},
"webhookId": "32c84fbc-d108-5979-a1bf-a11fb0028d96",
"position": [
0,
300
],
"id": "cf6ae2a5-a340-50d9-8826-86dcde970c8a"
},
{
"name": "Has release?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.body.release }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
220,
300
],
"id": "07285064-a3b8-5605-a6ee-580e64e38fbc"
},
{
"name": "Reject empty",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "{ \"error\": \"release is required\" }",
"options": {
"responseCode": 400
}
},
"position": [
440,
560
],
"id": "34e77f24-bb35-5cf2-80e3-457681e2bd0e"
},
{
"name": "Golden questions",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const release = String($('Check webhook').first().json.body.release);\nconst golden = [\n { question: 'VPN 접속이 자꾸 끊겨요. 어떻게 하나요?', user: '이준호', expect: ['인증서'] },\n { question: '비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?', user: '한지훈', expect: ['비밀번호 찾기', '휴대폰'] },\n { question: '프린터 출력이 안 돼요', user: '최유진', expect: ['대기열', '전원'] },\n { question: '메일함 용량이 꽉 찼대요', user: '김민지', expect: ['보관함', '삭제'] },\n { question: 'T-1002 처리 상태 알려 주세요', user: '박서연', expect: ['처리 중'] },\n { question: 'T-1001 어떻게 됐나요?', user: '이준호', expect: ['처리 완료', '완료'] },\n];\nreturn golden.map((g) => ({ json: { ...g, release, t0: Date.now() } }));"
},
"position": [
440,
300
],
"id": "9f9a1078-aab9-55d5-ac81-4292f7d22e73"
},
{
"name": "Ask helpdesk",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://localhost:5678/webhook/my-helpdesk",
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 30000
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ question: $json.question, user: $json.user, release: $json.release }) }}"
},
"position": [
660,
300
],
"id": "30940a89-e2b9-5153-bb09-9eca37792e8a"
},
{
"name": "Judge",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const golden = $('Golden questions').all().map((item) => item.json);\nconst answers = $input.all().map((item) => item.json);\nconst rows = golden.map((g, i) => {\n const a = answers[i] ?? {};\n const text = String(a.answer ?? '');\n const ok = !a.error && g.expect.some((word) => text.includes(word)) && (a.version?.release === g.release);\n return { question: g.question, ok, release_seen: a.version?.release ?? null };\n});\nconst passed = rows.filter((row) => row.ok).length;\nconst failed = rows.filter((row) => !row.ok).map((row) => row.question);\nconst seconds = Math.round((Date.now() - golden[0].t0) / 100) / 10;\nreturn [{ json: { release: golden[0].release, passed, total: rows.length, failed, seconds,\n verdict: passed === rows.length ? 'promote' : 'rollback' } }];"
},
"position": [
880,
300
],
"id": "5cbfc582-550a-5e91-aab4-bf3df3e17fcf"
},
{
"name": "Send verdict",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
1100,
300
],
"id": "f93d3e49-5d3d-5a46-afb6-c694f03e65ee"
}
],
"connections": {
"Check webhook": {
"main": [
[
{
"node": "Has release?",
"type": "main",
"index": 0
}
]
]
},
"Has release?": {
"main": [
[
{
"node": "Golden questions",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject empty",
"type": "main",
"index": 0
}
]
]
},
"Golden questions": {
"main": [
[
{
"node": "Ask helpdesk",
"type": "main",
"index": 0
}
]
]
},
"Ask helpdesk": {
"main": [
[
{
"node": "Judge",
"type": "main",
"index": 0
}
]
]
},
"Judge": {
"main": [
[
{
"node": "Send verdict",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
@@ -0,0 +1,243 @@
{
"name": "74 Release check + gate record (answer)",
"nodes": [
{
"name": "Check webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-release-check",
"responseMode": "responseNode",
"options": {}
},
"webhookId": "32c84fbc-d108-5979-a1bf-a11fb0028d96",
"position": [
0,
300
],
"id": "d961a9dc-450e-5d96-8451-dbc5fca3ba66"
},
{
"name": "Has release?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.body.release }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
220,
300
],
"id": "2ba94bc7-bef4-51e1-b534-669e49072171"
},
{
"name": "Reject empty",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "{ \"error\": \"release is required\" }",
"options": {
"responseCode": 400
}
},
"position": [
440,
560
],
"id": "87efe7b5-90b5-5674-8e8b-5e28a9cca01c"
},
{
"name": "Golden questions",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const release = String($('Check webhook').first().json.body.release);\nconst golden = [\n { question: 'VPN 접속이 자꾸 끊겨요. 어떻게 하나요?', user: '이준호', expect: ['인증서'] },\n { question: '비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?', user: '한지훈', expect: ['비밀번호 찾기', '휴대폰'] },\n { question: '프린터 출력이 안 돼요', user: '최유진', expect: ['대기열', '전원'] },\n { question: '메일함 용량이 꽉 찼대요', user: '김민지', expect: ['보관함', '삭제'] },\n { question: 'T-1002 처리 상태 알려 주세요', user: '박서연', expect: ['처리 중'] },\n { question: 'T-1001 어떻게 됐나요?', user: '이준호', expect: ['처리 완료', '완료'] },\n];\nreturn golden.map((g) => ({ json: { ...g, release, t0: Date.now() } }));"
},
"position": [
440,
300
],
"id": "9b1783b9-375e-509d-8990-992a70b79062"
},
{
"name": "Ask helpdesk",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://localhost:5678/webhook/my-helpdesk",
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 30000
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ question: $json.question, user: $json.user, release: $json.release }) }}"
},
"position": [
660,
300
],
"id": "b2944162-4641-5f43-9468-89d4d3cab67d"
},
{
"name": "Judge",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const golden = $('Golden questions').all().map((item) => item.json);\nconst answers = $input.all().map((item) => item.json);\nconst rows = golden.map((g, i) => {\n const a = answers[i] ?? {};\n const text = String(a.answer ?? '');\n const ok = !a.error && g.expect.some((word) => text.includes(word)) && (a.version?.release === g.release);\n return { question: g.question, ok, release_seen: a.version?.release ?? null };\n});\nconst passed = rows.filter((row) => row.ok).length;\nconst failed = rows.filter((row) => !row.ok).map((row) => row.question);\nconst seconds = Math.round((Date.now() - golden[0].t0) / 100) / 10;\nreturn [{ json: { release: golden[0].release, passed, total: rows.length, failed, seconds,\n verdict: passed === rows.length ? 'promote' : 'rollback' } }];"
},
"position": [
880,
300
],
"id": "00c58707-3b5f-5880-921e-b664bb6c6001"
},
{
"name": "Send verdict",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ ...$('Judge').first().json, recorded: $json.stored === true }) }}",
"options": {}
},
"position": [
1320,
300
],
"id": "548d6719-91e4-5920-ba38-2abb01730d92"
},
{
"name": "Record gate",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1100,
300
],
"id": "37a5dc22-d6e9-58c7-ac41-fdafcd25d9d0",
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "http://data-api:8800/gates/helpdesk",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ release: $json.release, verdict: $json.verdict, passed: $json.passed, total: $json.total, failed: $json.failed, seconds: $json.seconds, source: 'my-release-check' }) }}",
"options": {
"timeout": 3000,
"response": {
"response": {
"neverError": true
}
}
}
}
}
],
"connections": {
"Check webhook": {
"main": [
[
{
"node": "Has release?",
"type": "main",
"index": 0
}
]
]
},
"Has release?": {
"main": [
[
{
"node": "Golden questions",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject empty",
"type": "main",
"index": 0
}
]
]
},
"Golden questions": {
"main": [
[
{
"node": "Ask helpdesk",
"type": "main",
"index": 0
}
]
]
},
"Ask helpdesk": {
"main": [
[
{
"node": "Judge",
"type": "main",
"index": 0
}
]
]
},
"Judge": {
"main": [
[
{
"node": "Record gate",
"type": "main",
"index": 0
}
]
]
},
"Record gate": {
"main": [
[
{
"node": "Send verdict",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
File diff suppressed because it is too large. Load diff
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Quick check of the day 74 data API (instructor, before class): releases, policy, gates, prompt fault switch.
set -u
B=localhost:8800
show() { echo "\$ $1"; shift; "$@"; echo; }
show "GET /releases/helpdesk" curl -s $B/releases/helpdesk
show "policy on" curl -s -XPOST $B/releases/helpdesk/policy -d '{"require_gate":true}'
show "deploy 1.1.0 without a gate record (409)" curl -s -XPOST $B/releases/helpdesk -d '{"active":"1.1.0"}'
show "gate record 1.1.0 promote" curl -s -XPOST $B/gates/helpdesk -d '{"release":"1.1.0","verdict":"promote","passed":6,"total":6,"source":"check"}'
show "deploy 1.1.0 again (200)" curl -s -XPOST $B/releases/helpdesk -d '{"active":"1.1.0"}'
show "fault prompts error" curl -s -XPOST $B/admin/fault -d '{"prompts":"error"}'
show "GET /prompts/helpdesk/p1 (500)" curl -s $B/prompts/helpdesk/p1
show "fault prompts ok" curl -s -XPOST $B/admin/fault -d '{"prompts":"ok"}'
show "GET /prompts/helpdesk/p9 (404)" curl -s $B/prompts/helpdesk/p9
show "reset" curl -s -XPOST $B/releases/helpdesk/reset
show "gates clear" curl -s -XPOST $B/gates/helpdesk/clear
show "GET /audit (day 73 endpoint still there)" curl -s $B/audit