From acf3a930419e378a1676c92e064e5f627060399e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B1=84=EB=AC=B8=EC=B0=BD=40MBA?= Date: Mon, 2 Nov 2026 20:51:12 +0900 Subject: [PATCH] =?UTF-8?q?77=EC=9D=BC=EC=B0=A8=20Lab=2003:=20replay-after?= =?UTF-8?q?=C2=B7replay-normal=EC=9D=B4=20=EC=A7=80=EB=82=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EB=A7=8C=20=EC=A7=80=EC=9A=B0=EA=B3=A0=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91(logs-reset=EC=97=90=20tag),=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EB=8F=8C=EB=A0=A4=EB=8F=84=20lab03=C2=B7lab03-cost=20=EA=B7=B8?= =?UTF-8?q?=EB=8C=80=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Last3Mon/77/AGENTS.md | 2 +- Last3Mon/77/LAB-03-Q-IMPROVE-AND-REMEASURE.md | 6 +++++- Last3Mon/77/answers/LAB-TEST-RESULT.md | 4 ++++ Last3Mon/77/cases/replay-after.json | 6 ++++++ Last3Mon/77/cases/replay-normal.json | 6 ++++++ Last3Mon/77/data-api/server.py | 9 +++++++-- 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Last3Mon/77/AGENTS.md b/Last3Mon/77/AGENTS.md index 46b3475..90a516f 100644 --- a/Last3Mon/77/AGENTS.md +++ b/Last3Mon/77/AGENTS.md @@ -35,7 +35,7 @@ | `POST /logs/query` 본문 `{"from": "2026-10-26", "to": "2026-10-30", "tag": "", "source": "all"}` | `{"count", "logs": [...]}` — 날짜는 **포함**(`from ≤ 날짜 ≤ to`), 비우면 전체. `source`: `all` · `seed`(두 주 기록) · `live`(오늘 실습) | | `GET /logs?from=&to=&tag=&source=&limit=` | 같음 | | `POST /logs` 본문 `{"items": [{...}]}` | 실행 기록 더하기 (`started_at`이 없으면 지금) | -| `POST /logs-reset` | 오늘 실습 기록(`live`)만 지움 (두 주 기록은 그대로) | +| `POST /logs-reset` | 오늘 실습 기록(`live`)만 지움 (두 주 기록은 그대로). 본문 `{"tag": "after"}`면 그 `tag`의 기록만 | | `POST /faq/add` 본문 `{"id", "title", "keywords": [...], "answer"}` · `POST /faq-reset` | FAQ 더하기 · 되돌리기 (**사람이**) | | `GET /faq-extra` | 오늘 더한 FAQ `{"count", "faq"}` | | `POST /alerts` · `POST /alerts/list` · `POST /alerts/latest` | 알림 남기기 · 거르기 `{"workflow", "reason", "within": 초}` · 가장 최근 (76일차와 같음) | diff --git a/Last3Mon/77/LAB-03-Q-IMPROVE-AND-REMEASURE.md b/Last3Mon/77/LAB-03-Q-IMPROVE-AND-REMEASURE.md index 38b491b..172d3ed 100644 --- a/Last3Mon/77/LAB-03-Q-IMPROVE-AND-REMEASURE.md +++ b/Last3Mon/77/LAB-03-Q-IMPROVE-AND-REMEASURE.md @@ -177,6 +177,7 @@ uv run tools/flow.py ops ```text {"added": "FAQ-007", "size": 7} +✔ before: POST http://localhost:8800/logs-reset · HTTP 200 · 0.0s · {"cleared": 0} ✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "slow", …, "slow_seconds": 1.2} ✔ after · 회의실 와이파이가 안 · 4.5s ✔ after · 사내 Wi-Fi 비밀번 · 2.4s @@ -205,6 +206,7 @@ added FAQ: 1 (FAQ-007) | 한 건 시간 | 1.1~2.1초 | **2.3~4.5초** | FAQ 1.2초를 기다린 뒤 LLM 답 | - **두 고침이 다 필요했습니다.** timeout만 늘렸다면 와이파이 질문 5개는 FAQ에 없어 `담당자 연결`이 됐을 것이고("답하지 못한 주제 없음" ✘), FAQ만 더했다면 1초에 끊겨 10개 모두 오류였을 것입니다. 하나는 워크플로우(에이전트), 하나는 콘텐츠(나)입니다. +- **첫 `before` 줄 `logs-reset` · `{"tag": "after"}`:** 테스트가 지난번 `after` 기록**만** 지우고 시작합니다 (`before` 기록 10줄은 그대로). 그래서 고치기 전에 `replay-after`를 한 번 돌려 실패했거나 여러 번 돌려도, 마지막 한 번의 10줄만 남아 `lab03`이 그 10줄로 셉니다. 처음 돌리면 `{"cleared": 0}`, 다시 돌리면 `{"cleared": 10}`. 예제 3의 `replay-normal`도 같은 방식(`tag: normal`)입니다. - **`replay-after` 10/10은 `replay-before`와 뜻이 다릅니다.** 이번 테스트는 200만이 아니라 `route: 자동 답변` · `status: success`(와이파이는 답에 `CORP-WIFI`까지)를 확인합니다. - **`10 calls · $0.000000`:** 고친 뒤 10건은 LLM을 한 번씩 불렀습니다. 제공 헬프데스크는 기록에 비용을 0으로 남기므로 `$0.000000`입니다 (두 주 기록에는 비용이 있음). - **대신 느려졌습니다** (2.3~4.5초). 예제 2의 SLO와 예제 3에서 이 대가를 잽니다. @@ -428,6 +430,7 @@ uv run tools/flow.py test cases/replay-normal.json -v 출력 (결과 줄만): ```text +✔ before: POST http://localhost:8800/logs-reset · HTTP 200 · 0.0s · {"cleared": 0} ✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", …} ✔ normal · 회의실 와이파이가 안 · 2.3s ✔ normal · 사내 Wi-Fi 비밀번 · 1.1s @@ -813,7 +816,8 @@ alerts: 1 | --- | --- | | `replay-after`에서 와이파이만 실패 | FAQ를 더했는지 (`curl -s 'localhost:8800/faq?q=와이파이'`, `ops`의 `added FAQ`) | | `replay-after`에서 모두 실패 | timeout을 늘렸는지, push했는지 (`fault faq slow`는 테스트가 켬) | -| `lab03` 숫자가 10이 아님 | 실습 기록이 섞임: `replay-before`를 다시 (처음에 `/logs-reset` · `/faq-reset`을 부름) → FAQ 더하기 → `replay-after` | +| `lab03`의 "고치기 전" 숫자가 10이 아님 | 실습 기록이 섞임: `replay-before`를 다시 (처음에 `/logs-reset` · `/faq-reset`을 부름) → FAQ 더하기 → `replay-after` | +| 고치기 전에 `replay-after`를 돌려 실패했다 | 고친 뒤 `replay-after`를 다시 돌리면 됨 — 테스트가 지난 `after` 기록만 지우고 시작 (`{"cleared": 10}`) | | `lab03-cost`의 `normal`이 0건 | `replay-normal`을 먼저 돌렸는지 | | SLO가 늘 `ok` | 비교가 `>`인지, `limits` 기본값 · 본문 `slo` 덮어쓰기 | | 주간 보고가 timeout | 부르는 웹훅 셋(`my-log-report` · `my-log-summary`)이 모두 push돼 있는지, HTTP timeout 30~60초 | diff --git a/Last3Mon/77/answers/LAB-TEST-RESULT.md b/Last3Mon/77/answers/LAB-TEST-RESULT.md index 7c82f8f..6a8710d 100644 --- a/Last3Mon/77/answers/LAB-TEST-RESULT.md +++ b/Last3Mon/77/answers/LAB-TEST-RESULT.md @@ -6863,6 +6863,8 @@ report: runs/lab03-20261031-075118.json ### 18 · Lab 03 예제 1 · 고치기 (timeout · FAQ 더하기) → `replay-after` 10/10 · `lab03` 4/4 +> 11/02 고침: 이 기록(10/31) 뒤에 `cases/replay-after.json` · `replay-normal.json`의 `before` 맨 앞에 `logs-reset` (`{"tag": "after"}` · `{"tag": "normal"}`)을 더했습니다. 지금 돌리면 결과 줄 맨 위에 `✔ before: POST http://localhost:8800/logs-reset · … · {"cleared": 0}`이 한 줄 더 나옵니다 (다시 돌리면 `{"cleared": 10}`). 나머지 결과는 같습니다. + codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `reference/lab03-example1-helpdesk-fixed.json`): ```text @@ -8299,6 +8301,8 @@ report: runs/lab02-challenge-20261031-075213.json ### 21 · Lab 03 예제 3 · 고친 것의 대가 → `replay-normal` 10/10 · `lab03-cost` 4/4 +> 11/02 고침: 이 기록(10/31) 뒤에 `cases/replay-after.json` · `replay-normal.json`의 `before` 맨 앞에 `logs-reset` (`{"tag": "after"}` · `{"tag": "normal"}`)을 더했습니다. 지금 돌리면 결과 줄 맨 위에 `✔ before: POST http://localhost:8800/logs-reset · … · {"cleared": 0}`이 한 줄 더 나옵니다 (다시 돌리면 `{"cleared": 10}`). 나머지 결과는 같습니다. + 실행한 명령: ```bash diff --git a/Last3Mon/77/cases/replay-after.json b/Last3Mon/77/cases/replay-after.json index ecd65fa..73f29a9 100644 --- a/Last3Mon/77/cases/replay-after.json +++ b/Last3Mon/77/cases/replay-after.json @@ -1,6 +1,12 @@ { "webhook": "my-helpdesk", "before": [ + { + "url": "http://localhost:8800/logs-reset", + "body": { + "tag": "after" + } + }, { "url": "http://localhost:8800/admin/fault", "body": { diff --git a/Last3Mon/77/cases/replay-normal.json b/Last3Mon/77/cases/replay-normal.json index 69d9ab0..2674143 100644 --- a/Last3Mon/77/cases/replay-normal.json +++ b/Last3Mon/77/cases/replay-normal.json @@ -1,6 +1,12 @@ { "webhook": "my-helpdesk", "before": [ + { + "url": "http://localhost:8800/logs-reset", + "body": { + "tag": "normal" + } + }, { "url": "http://localhost:8800/admin/fault", "body": { diff --git a/Last3Mon/77/data-api/server.py b/Last3Mon/77/data-api/server.py index 813d6be..e135c74 100644 --- a/Last3Mon/77/data-api/server.py +++ b/Last3Mon/77/data-api/server.py @@ -76,7 +76,7 @@ GET /logs?from=2026-10-19&to=2026-10-30&tag=&source=all|seed|live&limit= -> {"count", "logs": [...]} dates inclusive (from <= started_at[:10] <= to), empty = all POST /logs/query same filters in the body {"from", "to", "tag", "source"} POST /logs body {"items": [{... one run ...}]} -> {"stored", "live"} (started_at = now when missing) -POST /logs-reset remove live logs (the seeded two weeks stay) -> {"cleared"} +POST /logs-reset remove live logs (the seeded two weeks stay); body {"tag"} removes only that tag -> {"cleared"} GET /faq-extra -> {"count", "faq": [...]} the FAQ entries added today POST /faq/add body {"id", "title", "keywords": [...], "answer"} -> {"added", "size"}; POST /faq-reset """ @@ -392,9 +392,14 @@ class Handler(BaseHTTPRequestHandler): LIVE_LOGS.append({"started_at": now, "source": "live", **item}) return self.send_json(200, {"stored": len(items), "live": len(LIVE_LOGS)}) if url.path == "/logs-reset": + tag = body.get("tag") if isinstance(body, dict) else None with LOCK: count = len(LIVE_LOGS) - LIVE_LOGS.clear() + if tag: + LIVE_LOGS[:] = [log for log in LIVE_LOGS if log.get("tag") != tag] + else: + LIVE_LOGS.clear() + count -= len(LIVE_LOGS) return self.send_json(200, {"cleared": count}) if url.path == "/faq/add": if not body.get("id") or not body.get("answer") or not isinstance(body.get("keywords"), list):