77일차 Lab 03: replay-after·replay-normal이 지난 기록만 지우고 시작(logs-reset에 tag), 다시 돌려도 lab03·lab03-cost 그대로

This commit is contained in:
mcchae committed 2026-11-02 20:51:12 +09:00
1 parent 4ec7ae123b
commit acf3a93041
6 files changed
+28 -3

No files matched your search

+1 -1
View File
@@ -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`(오늘 실습) | | `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=` | 같음 | | `GET /logs?from=&to=&tag=&source=&limit=` | 같음 |
| `POST /logs` 본문 `{"items": [{...}]}` | 실행 기록 더하기 (`started_at`이 없으면 지금) | | `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 더하기 · 되돌리기 (**사람이**) | | `POST /faq/add` 본문 `{"id", "title", "keywords": [...], "answer"}` · `POST /faq-reset` | FAQ 더하기 · 되돌리기 (**사람이**) |
| `GET /faq-extra` | 오늘 더한 FAQ `{"count", "faq"}` | | `GET /faq-extra` | 오늘 더한 FAQ `{"count", "faq"}` |
| `POST /alerts` · `POST /alerts/list` · `POST /alerts/latest` | 알림 남기기 · 거르기 `{"workflow", "reason", "within": 초}` · 가장 최근 (76일차와 같음) | | `POST /alerts` · `POST /alerts/list` · `POST /alerts/latest` | 알림 남기기 · 거르기 `{"workflow", "reason", "within": 초}` · 가장 최근 (76일차와 같음) |
@@ -177,6 +177,7 @@ uv run tools/flow.py ops
```text ```text
{"added": "FAQ-007", "size": 7} {"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} ✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "slow", …, "slow_seconds": 1.2}
✔ after · 회의실 와이파이가 안 · 4.5s ✔ after · 회의실 와이파이가 안 · 4.5s
✔ after · 사내 Wi-Fi 비밀번 · 2.4s ✔ 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 답 | | 한 건 시간 | 1.1~2.1초 | **2.3~4.5초** | FAQ 1.2초를 기다린 뒤 LLM 답 |
- **두 고침이 다 필요했습니다.** timeout만 늘렸다면 와이파이 질문 5개는 FAQ에 없어 `담당자 연결`이 됐을 것이고("답하지 못한 주제 없음" ✘), FAQ만 더했다면 1초에 끊겨 10개 모두 오류였을 것입니다. 하나는 워크플로우(에이전트), 하나는 콘텐츠(나)입니다. - **두 고침이 다 필요했습니다.** 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`까지)를 확인합니다. - **`replay-after` 10/10은 `replay-before`와 뜻이 다릅니다.** 이번 테스트는 200만이 아니라 `route: 자동 답변` · `status: success`(와이파이는 답에 `CORP-WIFI`까지)를 확인합니다.
- **`10 calls · $0.000000`:** 고친 뒤 10건은 LLM을 한 번씩 불렀습니다. 제공 헬프데스크는 기록에 비용을 0으로 남기므로 `$0.000000`입니다 (두 주 기록에는 비용이 있음). - **`10 calls · $0.000000`:** 고친 뒤 10건은 LLM을 한 번씩 불렀습니다. 제공 헬프데스크는 기록에 비용을 0으로 남기므로 `$0.000000`입니다 (두 주 기록에는 비용이 있음).
- **대신 느려졌습니다** (2.3~4.5초). 예제 2의 SLO와 예제 3에서 이 대가를 잽니다. - **대신 느려졌습니다** (2.3~4.5초). 예제 2의 SLO와 예제 3에서 이 대가를 잽니다.
@@ -428,6 +430,7 @@ uv run tools/flow.py test cases/replay-normal.json -v
출력 (결과 줄만): 출력 (결과 줄만):
```text ```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", …} ✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", …}
✔ normal · 회의실 와이파이가 안 · 2.3s ✔ normal · 회의실 와이파이가 안 · 2.3s
✔ normal · 사내 Wi-Fi 비밀번 · 1.1s ✔ 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`에서 와이파이만 실패 | FAQ를 더했는지 (`curl -s 'localhost:8800/faq?q=와이파이'`, `ops`의 `added FAQ`) |
| `replay-after`에서 모두 실패 | timeout을 늘렸는지, push했는지 (`fault faq slow`는 테스트가 켬) | | `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`을 먼저 돌렸는지 | | `lab03-cost`의 `normal`이 0건 | `replay-normal`을 먼저 돌렸는지 |
| SLO가 늘 `ok` | 비교가 `>`인지, `limits` 기본값 · 본문 `slo` 덮어쓰기 | | SLO가 늘 `ok` | 비교가 `>`인지, `limits` 기본값 · 본문 `slo` 덮어쓰기 |
| 주간 보고가 timeout | 부르는 웹훅 셋(`my-log-report` · `my-log-summary`)이 모두 push돼 있는지, HTTP timeout 30~60초 | | 주간 보고가 timeout | 부르는 웹훅 셋(`my-log-report` · `my-log-summary`)이 모두 push돼 있는지, HTTP timeout 30~60초 |
+4
View File
@@ -6863,6 +6863,8 @@ report: runs/lab03-20261031-075118.json
### 18 · Lab 03 예제 1 · 고치기 (timeout · FAQ 더하기) → `replay-after` 10/10 · `lab03` 4/4 ### 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`): codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `reference/lab03-example1-helpdesk-fixed.json`):
```text ```text
@@ -8299,6 +8301,8 @@ report: runs/lab02-challenge-20261031-075213.json
### 21 · Lab 03 예제 3 · 고친 것의 대가 → `replay-normal` 10/10 · `lab03-cost` 4/4 ### 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 ```bash
+6
View File
@@ -1,6 +1,12 @@
{ {
"webhook": "my-helpdesk", "webhook": "my-helpdesk",
"before": [ "before": [
{
"url": "http://localhost:8800/logs-reset",
"body": {
"tag": "after"
}
},
{ {
"url": "http://localhost:8800/admin/fault", "url": "http://localhost:8800/admin/fault",
"body": { "body": {
+6
View File
@@ -1,6 +1,12 @@
{ {
"webhook": "my-helpdesk", "webhook": "my-helpdesk",
"before": [ "before": [
{
"url": "http://localhost:8800/logs-reset",
"body": {
"tag": "normal"
}
},
{ {
"url": "http://localhost:8800/admin/fault", "url": "http://localhost:8800/admin/fault",
"body": { "body": {
+6 -1
View File
@@ -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 -> {"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/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 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 GET /faq-extra -> {"count", "faq": [...]} the FAQ entries added today
POST /faq/add body {"id", "title", "keywords": [...], "answer"} -> {"added", "size"}; POST /faq-reset 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}) LIVE_LOGS.append({"started_at": now, "source": "live", **item})
return self.send_json(200, {"stored": len(items), "live": len(LIVE_LOGS)}) return self.send_json(200, {"stored": len(items), "live": len(LIVE_LOGS)})
if url.path == "/logs-reset": if url.path == "/logs-reset":
tag = body.get("tag") if isinstance(body, dict) else None
with LOCK: with LOCK:
count = len(LIVE_LOGS) count = len(LIVE_LOGS)
if tag:
LIVE_LOGS[:] = [log for log in LIVE_LOGS if log.get("tag") != tag]
else:
LIVE_LOGS.clear() LIVE_LOGS.clear()
count -= len(LIVE_LOGS)
return self.send_json(200, {"cleared": count}) return self.send_json(200, {"cleared": count})
if url.path == "/faq/add": if url.path == "/faq/add":
if not body.get("id") or not body.get("answer") or not isinstance(body.get("keywords"), list): if not body.get("id") or not body.get("answer") or not isinstance(body.get("keywords"), list):