From 160967b0059a074814faa6a5dddb5700c1d2adca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B1=84=EB=AC=B8=EC=B0=BD=40MBA?= Date: Tue, 20 Oct 2026 22:59:34 +0900 Subject: [PATCH] =?UTF-8?q?69=EC=9D=BC=EC=B0=A8=20=EC=9E=90=EB=A3=8C=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0:=20flow.py=20test=20-v,=20=EA=B0=9C=EC=9A=94?= =?UTF-8?q?=20=EA=B7=B8=EB=A6=BC=C2=B7=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=98=88=EC=8B=9C,=20Lab=2003=20=EC=98=88=EC=A0=9C=201=20?= =?UTF-8?q?=EC=A7=80=EA=B8=88=20=EA=B2=B0=EA=B3=BC=20(=EC=A0=95=EB=8B=B5?= =?UTF-8?q?=EB=B3=B8=20=EC=A0=9C=EC=99=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- Last3Mon/69/AGENTS.md | 4 +- Last3Mon/69/LAB-01-Q-ROLE-CARDS.md | 4 +- Last3Mon/69/LAB-02-Q-COLLABORATION.md | 6 +- Last3Mon/69/LAB-03-Q-TRACE-AND-COMPARE.md | 44 +++++++++---- Last3Mon/69/LECTURE-69.md | 14 ++-- Last3Mon/69/README.md | 2 +- Last3Mon/69/tools/flow.py | 14 +++- Lecture/69/00-DAY69-OVERVIEW.md | 77 ++++++++++++++++++++-- Lecture/69/02-STAGE2-COLLABORATION.md | 2 +- Lecture/69/03-STAGE3-VERIFY-AND-COMPARE.md | 2 +- 10 files changed, 133 insertions(+), 36 deletions(-) diff --git a/Last3Mon/69/AGENTS.md b/Last3Mon/69/AGENTS.md index 65f9de5..be18746 100644 --- a/Last3Mon/69/AGENTS.md +++ b/Last3Mon/69/AGENTS.md @@ -10,7 +10,7 @@ 4. 끝나면 한국어로 짧게 보고한다: 바꾼 노드, check 결과, 그리고 사람이 실행할 명령 두 줄. ```bash uv run tools/flow.py push my-work/<이름>.json - uv run tools/flow.py test cases/<요청에 나온 테스트 파일>.json + uv run tools/flow.py test cases/<요청에 나온 테스트 파일>.json -v ``` 5. **push와 test는 사람이 실행해 결과를 확인한다.** 사람이 "네가 push·test까지 해"라고 하면 직접 실행해도 된다 (push는 약 10초, n8n이 잠깐 재시작됨). 6. 사람이 실패 보고서(`runs/*.json`)를 알려 주면 실패한 케이스의 요청·응답을 읽고 원인을 설명한 뒤 고친다. **같은 곳을 3번 고쳐도 실패하면 멈추고** 다른 방법을 제안한다. @@ -165,7 +165,7 @@ ``` - 필드 이름은 점으로 내려간다: `issues.0.code` = `issues` 첫 항목의 `code`. - `equals`: 값이 같아야 함 · `contains`: 목록 중 **하나라도** 들어 있어야 함 (배열·객체는 JSON 글자로 바꿔 찾음) · `not_contains`: 하나도 없어야 함 · `status`: HTTP 코드(기본 200) -- `uv run tools/flow.py test <파일> --repeat 3`: LLM 답은 매번 조금씩 다르므로 3번 이상 돌려 본다. +- `uv run tools/flow.py test <파일> -v --repeat 3`: LLM 답은 매번 조금씩 다르므로 3번 이상 돌려 본다. `-v`(`--verbose`)는 케이스마다 보낸 요청 JSON과 받은 응답 JSON을 화면에 보여 준다 (보고서 `runs/*.json`에도 같은 것이 남음). - **테스트를 통과시키려고 테스트 파일의 기대 결과를 바꾸지 않는다.** 기대 결과가 틀렸다고 생각되면 사람에게 묻는다. ## 9. 하지 말 것 diff --git a/Last3Mon/69/LAB-01-Q-ROLE-CARDS.md b/Last3Mon/69/LAB-01-Q-ROLE-CARDS.md index d6e04a8..71eda64 100644 --- a/Last3Mon/69/LAB-01-Q-ROLE-CARDS.md +++ b/Last3Mon/69/LAB-01-Q-ROLE-CARDS.md @@ -149,7 +149,7 @@ HTTP 400 ```bash uv run tools/flow.py push my-work/writer.json uv run tools/flow.py call my-writer requests/notice-mail.json # ③과 같은 모양인가 -uv run tools/flow.py test cases/lab01.json --repeat 3 # 수업 준비 때 5/5 +uv run tools/flow.py test cases/lab01.json -v --repeat 3 # 수업 준비 때 5/5 uv run tools/flow.py call my-writer requests/notice-phone.json # 관찰 ``` @@ -276,7 +276,7 @@ HTTP 400 ```bash uv run tools/flow.py push my-work/reviewer.json uv run tools/flow.py call my-reviewer requests/review-personal-info.json # ③과 같은 모양인가 -uv run tools/flow.py test cases/lab01-challenge.json --repeat 3 # 수업 준비 때 6/6 +uv run tools/flow.py test cases/lab01-challenge.json -v --repeat 3 # 수업 준비 때 6/6 ```
▶ 1단계 · 규칙과 LLM을 어디서 나누나 diff --git a/Last3Mon/69/LAB-02-Q-COLLABORATION.md b/Last3Mon/69/LAB-02-Q-COLLABORATION.md index 3868348..43e3f4f 100644 --- a/Last3Mon/69/LAB-02-Q-COLLABORATION.md +++ b/Last3Mon/69/LAB-02-Q-COLLABORATION.md @@ -153,7 +153,7 @@ HTTP 400 ```bash uv run tools/flow.py push my-work/notice.json uv run tools/flow.py call my-notice requests/notice-phone.json # ③과 같은 모양인가 -uv run tools/flow.py test cases/lab02.json # 수업 준비 때 4/4 (처음엔 검토자 거짓 경보로 3/4) +uv run tools/flow.py test cases/lab02.json -v # 수업 준비 때 4/4 (처음엔 검토자 거짓 경보로 3/4) ``` **관찰**: `notice-phone`에서 고쳐 쓴 공지를 처음 초안과 비교해 봅니다. 전화번호만 빠졌나요, 다른 것도 바뀌었나요? n8n **Executions**에서 `my-notice` 한 번에 `my-writer`, `my-reviewer` 실행이 몇 개씩 생겼나요? @@ -246,8 +246,8 @@ HTTP 200 · 5.8s ```bash uv run tools/flow.py push my-work/notice.json uv run tools/flow.py call my-notice requests/notice-mail.json # ③과 같은 모양인가 -uv run tools/flow.py test cases/lab02-challenge.json --repeat 3 # 수업 준비 때 4/4 -uv run tools/flow.py test cases/lab02.json # 4/4 그대로 +uv run tools/flow.py test cases/lab02-challenge.json -v --repeat 3 # 수업 준비 때 4/4 +uv run tools/flow.py test cases/lab02.json -v # 4/4 그대로 ```
▶ 1단계 · Fork-Join diff --git a/Last3Mon/69/LAB-03-Q-TRACE-AND-COMPARE.md b/Last3Mon/69/LAB-03-Q-TRACE-AND-COMPARE.md index f1f42fe..89e5ca1 100644 --- a/Last3Mon/69/LAB-03-Q-TRACE-AND-COMPARE.md +++ b/Last3Mon/69/LAB-03-Q-TRACE-AND-COMPARE.md @@ -24,7 +24,7 @@ flowchart LR - 명령은 모두 `~/aio-01d4-stu/Last3Mon/69`에서 실행합니다. - `uv run tools/flow.py call <웹훅 경로> <요청>`은 `http://localhost:5678/webhook/<경로>`로 POST를 보내고, 첫 줄에 `HTTP 코드 · 걸린 시간`, 그 아래 응답 JSON을 보여 줍니다. `<요청>`은 `requests/` 파일이거나 `'{"facts": "..."}'` 같은 JSON 글자입니다. - 웹훅 경로 ([AGENTS.md](AGENTS.md) 3절): 공지 팀 `my-notice` (`my-work/notice.json`), 작성자 `my-writer`, 검토자 `my-reviewer`. -- 아래 "지금 결과"와 "나와야 할 결과"는 수업 준비 때 Lab 02 예제 정답(`reference/lab02-example2-pipeline.json`)으로 실제로 돌린 값입니다. Lab 02 과제까지 했다면 응답에 `title` · `chat`도 붙어 있습니다. LLM 답 문장과 시간은 조금씩 다를 수 있습니다. +- 아래 "지금 결과"와 "나와야 할 결과"는 수업 준비 때(10/18, 수업 전날 10/20) Lab 02 예제 정답(`reference/lab02-example2-pipeline.json`)으로 실제로 돌린 값입니다. Lab 02 과제까지 했다면 응답에 `title` · `chat`도 붙어 있습니다. LLM 답 문장과 시간은 조금씩 다를 수 있습니다. --- @@ -71,30 +71,46 @@ HTTP 200 · 2.0s } ``` -공지는 맞습니다. 그런데 **누가 어떤 순서로 일했는지**가 없습니다. 프린터 공지는 수업 준비 때 두 번 돌려 두 번 모두 `담당자 확인`이었는데, 이유가 달랐습니다. +공지는 맞습니다. 그런데 **누가 어떤 순서로 일했는지**가 없습니다. 프린터 공지도 봅니다. ```bash uv run tools/flow.py call my-notice requests/notice-phone.json ``` -| 실행 | 결과 (요약) | 고쳐 쓴 공지의 마지막 줄 | 두 번째 검토 | +지금 `reference/`의 작성자 · 검토자로 돌리면 (수업 전날 2026-10-20 실행) 이렇게 나옵니다. + +```text +HTTP 200 · 5.2s +{ + "route": "수정 후 게시 가능", + "published": true, + "rounds": 2, + "notice": "[프린터 드라이버 일괄 업데이트 안내]\n\n- 10월 30일(목) 18:00~19:00 사내 프린터 드라이버가 일괄 업데이트됩니다.\n- 업데이트 시간 동안 프린터 출력이 불가합니다.\n- 문의는 IT팀 김민지에게 연락 바랍니다.", + "codes": [], + "first_codes": ["personal_info"] +} +``` + +결과는 맞지만, 고쳐 쓰기를 거쳤다는 것은 `rounds: 2`로 짐작할 뿐입니다. 그리고 같은 요청이 **언제나 이렇게 끝나지는 않습니다.** 수업 준비 때(10/18) 작성자 · 검토자를 다듬기 전 판으로 두 번 돌렸더니 두 번 모두 `담당자 확인`이었고, 이유가 달랐습니다. + +| 실행 (10/18) | 결과 (요약) | 고쳐 쓴 공지의 마지막 줄 | 두 번째 검토 | | --- | --- | --- | --- | | 1 | `"route": "담당자 확인"`, `"rounds": 2`, `"first_codes": ["personal_info"]`, `"codes": ["unsupported_claim"]` | `- 문의: IT팀 김민지` | 사실에 있는 이름을 "사실에 없는 연락처"로 잡음 | | 2 | 위와 같음 | `- 문의: IT팀 김민지 (내선 1234)` | 작성자가 **지어낸 내선 번호**를 잡음 | -`first_codes`, `codes`가 있어도 결과만 보면 둘은 똑같습니다. +`first_codes`, `codes`가 있어도 결과만 보면 둘은 똑같습니다. 내가 만든 작성자 · 검토자로도 이런 일이 생길 수 있습니다. ### ③ 원인 · 결과를 만드는 노드와 역할을 직접 보기 - n8n에서 `my-notice` 워크플로우를 열어 결과를 만드는 Set 노드 셋(`Approved`, `Approved after revision`, `Needs person`)을 봅니다. **어느 길로 왔는지** 적는 칸이 없습니다. -- 위 두 실행이 왜 달랐는지는 **역할을 하나씩** 불러 봐야 압니다. 고쳐 쓴 공지를 검토자에게 직접 보냅니다. +- 10/18의 두 실행이 왜 달랐는지는 **역할을 하나씩** 불러 봐야 압니다. 고쳐 쓴 공지를 검토자에게 직접 보냅니다. ```bash uv run tools/flow.py call my-reviewer '{"facts": "10월 30일(목) 18:00~19:00 사내 프린터 드라이버 일괄 업데이트. 업데이트 중에는 출력이 안 됨. 문의는 IT팀 김민지(010-1234-5678, minji.kim@example.com).", "draft": "[프린터 드라이버 업데이트 안내]\n- 10월 30일(목) 18:00~19:00 사내 프린터 드라이버 일괄 업데이트 진행\n- 업데이트 시간 동안 프린터 출력 불가\n- 문의: IT팀 김민지"}' ``` ```text -라우터 없이 본 흐름 +역할별로 본 흐름 (10/18의 두 실행) 공지 팀 Write draft → Review draft (personal_info) → Revise draft → Review again → Needs person 실행 1 Revise draft는 번호를 잘 지움 → Review again이 맞는 문장을 의심 → 검토자를 고칠 일 실행 2 Revise draft가 "(내선 1234)"를 새로 만듦 → Review again이 제대로 잡음 → 작성자를 고칠 일 @@ -148,8 +164,8 @@ HTTP 200 · 3.9s ```bash uv run tools/flow.py push my-work/notice.json uv run tools/flow.py call my-notice requests/notice-phone.json # ④와 같은 모양인가 -uv run tools/flow.py test cases/lab03.json # 고치기 전 1/3 → 3/3 -uv run tools/flow.py test cases/lab02.json # 4/4 그대로 +uv run tools/flow.py test cases/lab03.json -v # 고치기 전 1/3 → 3/3 +uv run tools/flow.py test cases/lab02.json -v # 4/4 그대로 ```
▶ 1단계 · 왜 기록을 남기나 @@ -248,11 +264,11 @@ HTTP 200 · 2.5s ```bash uv run tools/flow.py push my-work/notice.json uv run tools/flow.py call my-notice requests/notice-mail.json # ④와 같은 모양인가 -uv run tools/flow.py test cases/lab03-down.json # 고치기 전 0/2 → 2/2 +uv run tools/flow.py test cases/lab03-down.json -v # 고치기 전 0/2 → 2/2 uv run tools/flow.py push my-work/reviewer.json # 검토자 다시 켜기 -uv run tools/flow.py test cases/lab02.json # 4/4 -uv run tools/flow.py test cases/lab03.json --repeat 3 # 예제 1이 그대로인지 +uv run tools/flow.py test cases/lab02.json -v # 4/4 +uv run tools/flow.py test cases/lab03.json -v --repeat 3 # 예제 1이 그대로인지 ```
▶ 1단계 · 멈추되 500은 아니게 @@ -329,9 +345,9 @@ uv run tools/flow.py call my-notice '{"request": "공지 써 주세요", "facts" ### 단일 Agent와 비교 ```bash -uv run tools/flow.py test cases/my-cases.json --repeat 3 # 내 공지 팀 +uv run tools/flow.py test cases/my-cases.json -v --repeat 3 # 내 공지 팀 uv run tools/flow.py push reference/lab03-single-agent.json # AI 하나짜리 (웹훅 my-single) -uv run tools/flow.py test cases/my-cases.json --webhook my-single --repeat 3 # 같은 테스트 +uv run tools/flow.py test cases/my-cases.json -v --webhook my-single --repeat 3 # 같은 테스트 uv run tools/flow.py call my-single requests/notice-phone.json ``` @@ -379,7 +395,7 @@ uv run tools/flow.py call my-reviewer '{"facts": "<그 케이스의 facts>", "dr | 단계 | 요청 | 지금 | 원인 (직접 call로 확인) | 나와야 할 결과 | | --- | --- | --- | --- | --- | -| 예제 1 | 메일 · 프린터 공지 | 200, `steps` 없음. 프린터는 `담당자 확인` (이유 둘) | 결과 노드에 기록 칸이 없음. 고쳐 쓴 공지를 검토자에게 직접 보내 역할을 구별 | 200, `steps` 2개 · 4개, 프린터는 `수정 후 게시 가능` | +| 예제 1 | 메일 · 프린터 공지 | 200, `steps` 없음. 프린터는 `수정 후 게시 가능`, 예전 판에서는 `담당자 확인` (이유 둘) | 결과 노드에 기록 칸이 없음. 고쳐 쓴 공지를 검토자에게 직접 보내 역할을 구별 | 200, `steps` 2개 · 4개, 프린터는 `수정 후 게시 가능` | | 예제 2 | 검토자 꺼짐 | 500, 초안도 사라짐 | 검토자 404에 공지 팀이 멈춤 | 200, `published: false`, `reason`에 "검토자" | | 과제 예 A | 공백만 | 200 `게시 가능` (빈 공지가 나감) | 공백도 "비어 있지 않음" | 400 | | 과제 예 B | 사실 안에 지시문 | 200, 번호 없음 (통과) | 규칙 검사는 지시문을 읽지 않음 | 그대로 유지 | diff --git a/Last3Mon/69/LECTURE-69.md b/Last3Mon/69/LECTURE-69.md index 801c72a..ee81165 100644 --- a/Last3Mon/69/LECTURE-69.md +++ b/Last3Mon/69/LECTURE-69.md @@ -86,8 +86,8 @@ flowchart LR | 순서 | 할 일 | 파일 · 웹훅 | 확인 | | --- | --- | --- | --- | | 예제 1 (10분, 에이전트 없이) | `cp templates/ROLE-DESIGN-TEMPLATE.md my-work/DESIGN.md` 후 1절 역할 카드를 **직접** (특히 "하지 않는 일") | | 표 | -| 예제 2 (15분) | 작성자: 공지 요청 → 공지 초안 (검토는 안 함) | `my-work/writer.json` · `my-writer` | `push my-work/writer.json` → `test cases/lab01.json --repeat 3`, `call my-writer requests/notice-phone.json` | -| 과제 (15분) | 검토자: 날짜·시간 빠짐 · 개인 연락처 · 길이는 **코드로**, 사실에 없는 약속만 AI가 | `my-work/reviewer.json` · `my-reviewer` | `push my-work/reviewer.json` → `test cases/lab01-challenge.json --repeat 3` | +| 예제 2 (15분) | 작성자: 공지 요청 → 공지 초안 (검토는 안 함) | `my-work/writer.json` · `my-writer` | `push my-work/writer.json` → `test cases/lab01.json -v --repeat 3`, `call my-writer requests/notice-phone.json` | +| 과제 (15분) | 검토자: 날짜·시간 빠짐 · 개인 연락처 · 길이는 **코드로**, 사실에 없는 약속만 AI가 | `my-work/reviewer.json` · `my-reviewer` | `push my-work/reviewer.json` → `test cases/lab01-challenge.json -v --repeat 3` | (명령 앞에는 모두 `uv run tools/flow.py`가 붙습니다.) @@ -100,8 +100,8 @@ flowchart LR | 순서 | 할 일 | 파일 · 웹훅 | 확인 | | --- | --- | --- | --- | | 예제 1 (10분) | 작성자 · 검토자의 계약 맞추기 (서로 부를 수 있게) | | `push my-work/writer.json my-work/reviewer.json`, `call my-writer requests/notice-phone.json`, `call my-reviewer requests/review-personal-info.json` | -| 예제 2 (25분) | 공지 팀: 작성자 → 검토자, 통과 못 하면 의견을 주어 **한 번** 고쳐 쓰고 다시 검토 | `my-work/notice.json` · `my-notice` (오늘 끝까지 고쳐 나감) | `push my-work/notice.json` → `test cases/lab02.json`, `call my-notice requests/notice-phone.json` | -| 과제 (35분, 4교시) | 통과한 공지로 메일 제목 한 줄 · 메신저용 짧은 글을 두 정리자가 나눠 만들고 모으기 (Fork-Join) | `my-work/notice.json` 고치기 | `test cases/lab02-challenge.json --repeat 3`, `test cases/lab02.json` | +| 예제 2 (25분) | 공지 팀: 작성자 → 검토자, 통과 못 하면 의견을 주어 **한 번** 고쳐 쓰고 다시 검토 | `my-work/notice.json` · `my-notice` (오늘 끝까지 고쳐 나감) | `push my-work/notice.json` → `test cases/lab02.json -v`, `call my-notice requests/notice-phone.json` | +| 과제 (35분, 4교시) | 통과한 공지로 메일 제목 한 줄 · 메신저용 짧은 글을 두 정리자가 나눠 만들고 모으기 (Fork-Join) | `my-work/notice.json` 고치기 | `test cases/lab02-challenge.json -v --repeat 3`, `test cases/lab02.json -v` | - 볼 것: 고쳐 쓴 공지에서 전화번호만 빠졌나요, 다른 것도 바뀌었나요? Executions에서 두 정리자 노드의 시작·끝 시각: **정말 동시에 돌았나요?** `DESIGN.md`에 적습니다. @@ -111,9 +111,9 @@ flowchart LR | 순서 | 할 일 | 확인 | | --- | --- | --- | -| 예제 1 (20분) | 결과만 보고 협업 과정을 알 수 있게 기록 (`rounds`, `steps`, `first_codes`). `steps`는 LLM이 아니라 워크플로우가 | `push my-work/notice.json` → `test cases/lab03.json` | -| 예제 2 (20분) | 검토자가 꺼지면 **게시하지 않기** (fail-closed) | `off my-reviewer` → `test cases/lab03-down.json`, 고친 뒤 **다시 켜기** `push my-work/reviewer.json` → `test cases/lab02.json`, `test cases/lab03.json --repeat 3` | -| 과제 (40분, 6교시) | 어려운 케이스 `cases/my-cases.json`, 같은 테스트로 AI 하나짜리(단일 Agent)와 숫자로 비교 | `test cases/my-cases.json --repeat 3`, `push reference/lab03-single-agent.json`, `test cases/my-cases.json --webhook my-single --repeat 3` | +| 예제 1 (20분) | 결과만 보고 협업 과정을 알 수 있게 기록 (`rounds`, `steps`, `first_codes`). `steps`는 LLM이 아니라 워크플로우가 | `push my-work/notice.json` → `test cases/lab03.json -v` | +| 예제 2 (20분) | 검토자가 꺼지면 **게시하지 않기** (fail-closed) | `off my-reviewer` → `test cases/lab03-down.json -v`, 고친 뒤 **다시 켜기** `push my-work/reviewer.json` → `test cases/lab02.json -v`, `test cases/lab03.json -v --repeat 3` | +| 과제 (40분, 6교시) | 어려운 케이스 `cases/my-cases.json`, 같은 테스트로 AI 하나짜리(단일 Agent)와 숫자로 비교 | `test cases/my-cases.json -v --repeat 3`, `push reference/lab03-single-agent.json`, `test cases/my-cases.json -v --webhook my-single --repeat 3` | - 먼저 생각해 보기: 68일차에는 Agent가 꺼져도 나머지 답을 **계속** 냈습니다. 오늘은 왜 **멈춰야** 할까요? - 보고서: `cp templates/TEST-REPORT-TEMPLATE.md my-work/TEST-REPORT.md` 후 채우기. diff --git a/Last3Mon/69/README.md b/Last3Mon/69/README.md index f0d1d20..3b48e5f 100644 --- a/Last3Mon/69/README.md +++ b/Last3Mon/69/README.md @@ -76,4 +76,4 @@ cd ~/aio-01d4-stu/Last3Mon/69 && uv run tools/flow.py doctor # 마지막 줄 | `uv run tools/flow.py push my-work/writer.json my-work/reviewer.json` | 여러 파일을 가져오기 → 게시 → n8n 재시작 한 번 | | | `uv run tools/flow.py off my-reviewer` | 그 웹훅을 쓰는 워크플로우 게시 해제 (역할이 꺼진 상황). 다시 켜기는 `push` | | | `uv run tools/flow.py call my-notice requests/notice-phone.json` | 요청 한 번 보내고 응답 보기 | 공지 팀 2~6초 | -| `uv run tools/flow.py test cases/lab02.json` | 테스트 실행, 보고서를 `runs/`에 저장 | 케이스당 1~6초 | +| `uv run tools/flow.py test cases/lab02.json -v` | 테스트 실행, 보고서를 `runs/`에 저장. `-v`는 케이스마다 보낸 요청 JSON과 받은 응답 JSON을 화면에 보여 줌 | 케이스당 0~7초 (빈 요청 0초, 고쳐 쓰기 + 정리자 약 7초) | diff --git a/Last3Mon/69/tools/flow.py b/Last3Mon/69/tools/flow.py index 9642033..4e0d255 100644 --- a/Last3Mon/69/tools/flow.py +++ b/Last3Mon/69/tools/flow.py @@ -9,7 +9,7 @@ uv run tools/flow.py push my-work/writer.json my-work/reviewer.json # import + publish (one n8n restart) uv run tools/flow.py off my-reviewer # unpublish by file or webhook path (a role that is down) uv run tools/flow.py call my-notice requests/notice-mail.json - uv run tools/flow.py test cases/lab02.json [--repeat 3] + uv run tools/flow.py test cases/lab02.json -v [--repeat 3] # -v: show each request and response JSON Run it from the day folder (Last3Mon/69). n8n is the day 65 install (~/day65-n8n, Docker project day65-n8n), started from Last3Mon/65/n8n. Environment overrides: @@ -494,6 +494,14 @@ def judge(expect: dict, status: int, payload: object) -> list[str]: return problems +def show_json(label: str, value: object) -> None: + """Print one JSON value under a label, indented below the case line (test -v).""" + text = json.dumps(value, ensure_ascii=False, indent=2) if not isinstance(value, str) else value + print(f" {label}") + for row in text.splitlines(): + print(f" {row}") + + def cmd_test(args: argparse.Namespace) -> int: path = resolve(args.cases) suite = load_json(path) @@ -513,6 +521,9 @@ def cmd_test(args: argparse.Namespace) -> int: mark = OK if not problems else BAD suffix = f" #{run}" if args.repeat > 1 else "" print(f"{mark} {case.get('name', '?')}{suffix} · {seconds:.1f}s" + ("" if not problems else " · " + "; ".join(problems))) + if args.verbose: + show_json(f"→ request POST /webhook/{case_hook}", body) + show_json(f"← response HTTP {status}", payload) results.append({"case": case.get("name"), "webhook": case_hook, "run": run, "passed": not problems, "problems": problems, "seconds": round(seconds, 1), "status": status, "request": body, "response": payload}) total = len(results) @@ -597,6 +608,7 @@ def main() -> int: item.add_argument("cases") item.add_argument("--repeat", type=int, default=1, help="run every case N times") item.add_argument("--webhook", help="override the webhook path in the case file") + item.add_argument("-v", "--verbose", action="store_true", help="print the request and response JSON of every run") item.set_defaults(run=cmd_test) args = parser.parse_args() return args.run(args) diff --git a/Lecture/69/00-DAY69-OVERVIEW.md b/Lecture/69/00-DAY69-OVERVIEW.md index 5cd196a..f89f9b9 100644 --- a/Lecture/69/00-DAY69-OVERVIEW.md +++ b/Lecture/69/00-DAY69-OVERVIEW.md @@ -75,14 +75,82 @@ flowchart LR ```mermaid flowchart LR - W(["request · facts"]) --> A{{"작성자
my-writer"}} --> R{{"검토자
my-reviewer"}} --> P{"통과?"} + W(["공지 팀 my-notice
request · facts"]) --> A{{"작성자 워크플로우
my-writer · LLM 1"}} --> R{{"검토자 워크플로우
my-reviewer · 규칙 3 + LLM 1"}} --> P{"통과?
(If)"} P -->|예| OK["게시 가능"] - P -->|아니오| A2{{"작성자
고쳐 쓰기"}} --> R2{{"검토자"}} --> P2{"통과?"} + P -->|아니오| A2{{"작성자 워크플로우
고쳐 쓰기"}} --> R2{{"검토자 워크플로우"}} --> P2{"통과?
(If)"} P2 -->|예| OK2["수정 후 게시 가능"] P2 -->|아니오| H["담당자 확인"] - OK & OK2 --> E1["제목"] & E2["메신저 글"] --> J["모으기"] + OK & OK2 --> E1["정리자: 제목
LLM 1"] & E2["정리자: 메신저 글
LLM 1"] --> J["모으기"] ``` +> 육각형 하나 = **역할 하나 = 웹훅 하나인 n8n 워크플로우**입니다. 역할 자체가 LLM은 아니고, 그 안에 AI Agent(LLM) 노드가 들어 있습니다. 공지 팀(`my-notice`)은 LLM 없이 역할들을 **HTTP로 부르고 If로 갈림**을 정합니다 (68일차 라우터의 Switch와 같은 자리). + +### 데이터로 따라가기 · 연락처가 든 프린터 공지 (수업 준비 때 실제 실행 기록) + +그림의 왼쪽부터, 각 상자가 **받은 것**과 **내놓은 것**입니다. 상자 사이로는 이 JSON만 오갑니다. + +**① 요청** → 공지 팀 `my-notice` (`requests/notice-phone.json`) + +```json +{"request": "프린터 드라이버 업데이트 공지를 써 주세요. 문의처 연락처도 꼭 넣어 주세요.", + "facts": "10월 30일(목) 18:00~19:00 사내 프린터 드라이버 일괄 업데이트. 업데이트 중에는 출력이 안 됨. 문의는 IT팀 김민지(010-1234-5678, minji.kim@example.com).", + "author": "김민지"} +``` + +**② 작성자** `my-writer` · 받음: `request`, `facts` → 내놓음: 초안 + +```json +{"draft": "[프린터 드라이버 업데이트 안내]\n\n- 10월 30일(목) 18:00~19:00 사내 프린터 드라이버 일괄 업데이트 진행\n- 업데이트 시간 동안 프린터 출력 불가\n- 문의: IT팀 김민지 (010-1234-5678, minji.kim@example.com)"} +``` + +요청("연락처도 꼭")대로 개인 연락처를 넣었습니다. 작성자는 요청을 따르고, **회사 규칙은 검토자가** 지킵니다. + +**③ 검토자** `my-reviewer` · 받음: `facts`(①의 것), `draft`(②) → 내놓음: 판정 + +```json +{"passed": false, "codes": ["personal_info"], + "issues": [{"code": "personal_info", "detail": "010-1234-5678, minji.kim@example.com"}]} +``` + +전화번호 · 메일 주소는 **규칙(Code)** 이 찾았습니다. `passed: false` → 공지 팀의 If가 "아니오" 길로 보냅니다. + +**④ 작성자 · 고쳐 쓰기** · 받음: `request`, `facts`(①), `previous_draft`(②), `feedback`(③의 `issues`를 "code: detail" 줄로) + +```json +{"feedback": "personal_info: 010-1234-5678, minji.kim@example.com"} +``` + +```json +{"draft": "[프린터 드라이버 업데이트 안내]\n\n- 10월 30일(목) 18:00~19:00 사내 프린터 드라이버 일괄 업데이트 진행\n- 업데이트 시간 동안 프린터 출력 불가\n- 문의: IT팀 김민지"} +``` + +연락처만 빠지고 날짜 · 시간 · 나머지는 그대로입니다. (다른 실행에서는 여기서 "(내선 1234)"를 지어낸 적이 있어, 작성자 지시문에 "고칠 때 새 정보 금지"를 넣었습니다.) + +**⑤ 검토자 · 다시** · 받음: `facts`(**①의 것**, 고쳐 쓴 작성자가 전한 것이 아님), `draft`(④) + +```json +{"passed": true, "codes": [], "issues": []} +``` + +**⑥ 정리자 둘** · 받음: 통과한 공지(④) → 내놓음: 메일 제목, 메신저 글 + +```json +{"title": "10월 30일 프린터 드라이버 업데이트 안내", + "chat": "10/30(목) 18:00~19:00 프린터 드라이버 업데이트로 출력 불가. 문의: IT팀 김민지."} +``` + +**⑦ 공지 팀 응답** (5.9초, LLM 6번: 작성 2 · 검토 2 · 정리 2) + +```json +{"route": "수정 후 게시 가능", "published": true, "rounds": 2, + "notice": "[프린터 드라이버 업데이트 안내]\n\n- 10월 30일(목) 18:00~19:00 … 진행\n- … 출력 불가\n- 문의: IT팀 김민지", + "codes": [], "first_codes": ["personal_info"], + "title": "10월 30일 프린터 드라이버 업데이트 안내", + "chat": "10/30(목) 18:00~19:00 프린터 드라이버 업데이트로 출력 불가. 문의: IT팀 김민지."} +``` + +`first_codes`는 ③에서 무엇이 걸렸는지, `codes`는 ⑤의 결과입니다. 메일 점검 공지처럼 ③에서 바로 `passed: true`면 ④ · ⑤ 없이 `게시 가능`, `rounds: 1`입니다. + > Lab 01(역할 하나씩) → Lab 02(이어 붙이기, 정리자) → Lab 03(기록, 검토자 장애, 비교) 순서로 완성합니다. --- @@ -95,6 +163,7 @@ flowchart LR | 내놓는 것 | `draft` | `passed`, `codes`, `issues` | | **하지 않는 일** | 스스로 통과 판정 | 초안 고쳐 쓰기 | | LLM · 규칙 | LLM | 규칙 3 (날짜·시간, 연락처, 길이) + LLM 1 (없는 약속) | +| n8n 워크플로우 안 | Webhook → If → **AI Agent**(LLM) → Code → 응답 | Webhook → If → **Code**(규칙 3) → **AI Agent**(LLM, 없는 약속) → Code(합치기 · LLM 판단 재확인) → 응답 | > 역할을 나누면 **역할마다 따로 테스트**할 수 있습니다: 검토자에게 일부러 틀린 초안 5종 (`lab01-challenge` 6/6). @@ -135,7 +204,7 @@ flowchart LR | 수업 준비 측정 | 결과 | | --- | --- | | `lab02-challenge` | 4/4 (두 번) | -| 요청 하나 | 정리자 없이 2.0~4.1초 → 정리자 붙이고 4.1~5.9초 | +| 요청 하나 | 정리자 없이 2.0~4.1초 → 정리자 붙이고 4.1~6.5초 | > 설계는 "서로 기다릴 필요 없음". n8n이 실제로 동시에 돌리는지는 Executions의 노드 시각으로 함께 확인합니다. diff --git a/Lecture/69/02-STAGE2-COLLABORATION.md b/Lecture/69/02-STAGE2-COLLABORATION.md index d4f2156..6b5f724 100644 --- a/Lecture/69/02-STAGE2-COLLABORATION.md +++ b/Lecture/69/02-STAGE2-COLLABORATION.md @@ -87,7 +87,7 @@ flowchart LR | 정리자마다 chat model 노드 따로 | model 노드 하나는 Agent 하나에 연결 | | 정리자의 "하지 않는 일": 새 정보, 날짜 바꾸기 | 검토를 통과한 뒤 바뀌면 안 됨 | -수업 준비 때 결과: `lab02-challenge` 4/4 (두 번 실행). 요청 하나가 정리자 없이 2.0~4.1초 → 정리자 붙이고 4.1~5.9초. +수업 준비 때 결과: `lab02-challenge` 4/4 (두 번 실행). 요청 하나가 정리자 없이 2.0~4.1초 → 정리자 붙이고 4.1~6.5초. > Fork-Join은 **설계**에서 "서로 기다릴 필요 없음"입니다. n8n이 두 갈래를 실제로 동시에 돌리는지는 실행 방식에 따라 다릅니다. Lab 02 과제에서 Executions의 노드 시각으로 확인합니다. diff --git a/Lecture/69/03-STAGE3-VERIFY-AND-COMPARE.md b/Lecture/69/03-STAGE3-VERIFY-AND-COMPARE.md index a539964..2e65320 100644 --- a/Lecture/69/03-STAGE3-VERIFY-AND-COMPARE.md +++ b/Lecture/69/03-STAGE3-VERIFY-AND-COMPARE.md @@ -23,7 +23,7 @@ flowchart LR - `steps`는 LLM이 쓰지 않습니다. 어느 길로 왔는지는 **워크플로우가** 알기 때문에, 결과를 만드는 노드에 정해진 값으로 넣습니다. - 역할마다 웹훅이 따로라서 n8n **Executions**에 역할별 실행이 남습니다. 결과의 기록 + 역할별 실행 = 협업의 감사 기록. -수업 준비 때 Lab 02 예제 정답에 `cases/lab03.json`: 1/3 (`steps` 없음, 그중 하나는 고쳐 쓰기에서 "(내선 1234)"를 지어내 `담당자 확인`). 기록을 더하고 검토자를 고친 뒤 3/3. +수업 준비 때 Lab 02 예제 정답에 `cases/lab03.json`: 1/3 (`steps` 없음. 10/18에는 그중 하나가 고쳐 쓰기에서 "(내선 1234)"를 지어내 `담당자 확인`, 작성자 · 검토자를 다듬은 수업 전날 10/20에는 `steps`만 없음). 기록을 더한 뒤 3/3. ## 2. fail-open과 fail-closed