Files
aio-01d4-stu/Last3Mon/72/LAB-02-A-OPTIMIZE-FLOW.md
T

144 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lab 02 · 규칙 먼저, 캐시, LLM 한 번
> **강사용 정답본** · 학생 저장소에 올리지 않습니다. 학생용 문제는 [LAB-02-Q-OPTIMIZE-FLOW.md](LAB-02-Q-OPTIMIZE-FLOW.md).
> 요청문은 예시이며 CLI 에이전트로 보내 재지는 않았습니다. 결과 숫자는 정답 워크플로우를 2026-10-18에 1회씩 실행한 값입니다 (강사 Mac, gpt-4.1-mini, bench는 n=5).
> 10/24에 실습을 5개로 늘리며 정답을 학생 순서대로 다시 돌렸습니다 (45단계). 명령 · `-v` 출력 전부와 codex 프롬프트: [answers/LAB-TEST-RESULT.md](answers/LAB-TEST-RESULT.md). 10/24 점수: 예제 1 `lab02` 7/9 (캐시 케이스 · 접수 답에서 검토 LLM이 "처리 중"을 바꿈), 나머지는 모두 기대대로.
## 예제 1 · 요청문 예
```text
my-work/helpdesk.json 의 순서를 바꿔 줘: Has question? → Start clock → Search FAQ → Get ticket → After data → Rule classify(Code) → Known?(If).
Rule classify: 질문에 T-1234 같은 번호(/T-?\d{4}/i)가 있으면 ticket, FAQ 결과가 1개 이상이면 faq, 아니면 unknown. {category, by:'rule'}.
Known? true → Answer, false → Classify → Parse category → After classify → Answer. Answer 는 $json.category 로 분류를 읽는다.
Build result: 분류는 Parse category 가 실행됐으면 그것, 아니면 Rule classify. steps 에 rule 또는 classify.
나머지(답, 검토, timings)는 그대로. 기대 결과 cases/lab02.json 앞 네 케이스.
```
**빈칸 답** (Q 문서 ▶ 빈칸 채우기 `____①`~`____④`)
| 빈칸 | 답 | 왜 |
| --- | --- | --- |
| ① | `/T-?\d{4}/i` | 하이픈 있어도 없어도, 대소문자 무시, 숫자 네 자리 (`T-1002` · `t1002`) |
| ② | 1개 이상 | `/faq`의 `results`가 비어 있지 않으면 FAQ로 답할 수 있음 |
| ③ | `Parse category` | LLM 분류 길에서 분류 결과를 만드는 마지막 노드 |
| ④ | `rule` | 규칙 길을 지났다는 `steps` 이름 (AGENTS.md 3절) |
정답: [reference/lab02-example1-rules.json](reference/lab02-example1-rules.json)
| 수업 준비 때 결과 | |
| --- | --- |
| `cases/lab02.json` | 7/9 (예제 2의 "같은 질문" 캐시 케이스와, 답 문장이 기대 표현과 달랐던 "없는 접수 번호" 실패. 10/23~24에는 "없는 접수 번호"가 통과해 8/9) |
| `cases/lab01.json` | 4/4 |
| 규칙 질문 시간 | 접수 1.9~2.3초 (예제 2 timings 상태에서 2.4~2.8초) |
- 처음 쓴 "없는 접수 번호" 기대(`찾지 못`)가 틀렸습니다: 검토 LLM이 "확인되지 않습니다"로 바꿔 말함. 기대를 "없다는 여러 표현 + 상태를 지어내지 않음(`not_contains` 처리 중 등)"으로 고쳤습니다 (71일차 "모른다의 여러 모양"과 같음).
## 예제 2 · 요청문 예
```text
my-work/helpdesk.json 에 캐시를 더해 줘 (AGENTS.md 2절 데이터 API, 3절 캐시 규칙).
Start clock 다음: Cache key(Code: question 을 trim·소문자·연속 공백 하나, use_cache = body.no_cache !== true)
→ Cache lookup(GET http://data-api:8800/cache/<encodeURIComponent(key)>, neverError) → Cached?(value 있고 use_cache)
true → Cached result(route·category·answer·sources + llm_calls 0, cached true, steps ['cache'], timings.total_ms) → Send cached
false → 지금 흐름(Search FAQ ...).
Build result 뒤: Cache it?(category faq 이고 use_cache) true → Store in cache(POST /cache/<key> {value:{route,category,answer,sources}, ttl 600}) → Send result.
접수(ticket)는 저장하지 않는다. 기대 결과 cases/lab02.json 전체, lab01 도 통과.
```
**빈칸 답** (Q 문서 ▶ 빈칸 채우기 `____①`~`____④`)
| 빈칸 | 답 | 왜 |
| --- | --- | --- |
| ① | trim · 소문자 · 연속 공백 하나 | 띄어쓰기 · 대소문자만 다른 질문을 같은 키로 (AGENTS.md 3절) |
| ② | `body.no_cache !== true` | 본문 `no_cache`가 true가 아닐 때만 캐시 사용 |
| ③ | `neverError` | 캐시에 없을 때의 404에서 멈추지 않게 (AGENTS.md 5절) |
| ④ | 접수(ticket) | 상태가 바뀌는 답은 캐시하지 않음 (AGENTS.md 3절 굵은 줄) |
정답: [reference/lab02-example2-cache.json](reference/lab02-example2-cache.json)
| 수업 준비 때 결과 | |
| --- | --- |
| `cases/lab02.json` | 9/9 · 같은 질문(띄어쓰기·대소문자 다름) 0.1초, `llm_calls: 0` |
| `cases/lab01.json` · `lab03.json` | 4/4 · 2/4 (3단계 전) |
| 캐시 없이 (`faq-vpn-nocache`) bench | p50 2.01초 · p95 2.23초 |
- 처음에는 "규칙으로 모르는 질문"(회의실 화면 공유)이 LLM 분류로 `faq`가 되어 **캐시에 들어갔고**, 다음 테스트에서 0.1초로 나와 `llm_calls` 기대가 깨졌습니다. 그 케이스에 `no_cache: true`를 넣었습니다 → 수업 이야기 거리: "캐시는 테스트도 바꾼다".
## 예제 3 · 결과 (에이전트 없이, 10/24)
예제 2 정답(`reference/lab02-example2-cache.json`), 캐시를 비운 뒤.
| 재기 | 첫 호출 | p50 | max |
| --- | --- | --- | --- |
| 캐시 씀 (`faq-vpn` × 5) | 2.07초 (LLM 2번) | **0.05초** | 2.07초 |
| 캐시 안 씀 (`faq-vpn-nocache` × 5) | 1.99초 | 1.99초 | 2.24초 |
- `/cache-stats` 뒤: `entries 1 · hits 9 · misses 1`. `no_cache` 요청도 캐시를 **조회는** 하므로 hits로 셉니다 (쓰지는 않음). 학생이 "적중 9번?"이라고 하면 이 이야기를 합니다.
- 적중률 어림 (0.05초 · 2.0초): 10% → 1.81초, 30% → 1.42초, 60% → 0.83초.
## 과제 1 · 요청문 예
```text
my-work/helpdesk.json 에서 Review(검토 LLM)와 Review model 을 지우고, 그 자리에 Review check(Code)를 둬:
Answer 의 output 을 trim, 비었으면 empty, 한글이 없으면 not_korean, 400자 넘으면 자르고 too_long 을 review_problems 에.
Answer 지시문에 "합니다체, 3문장 이내"가 이미 있는지 확인. steps 에는 review 대신 check, llm_calls 는 검토를 세지 않는다.
나머지는 그대로. cases/lab02-challenge.json, lab02.json, lab01.json 모두 통과.
```
**빈칸 답** (Q 문서 ▶ 빈칸 채우기 `____①`~`____④`)
| 빈칸 | 답 | 왜 |
| --- | --- | --- |
| ① | `400` | 답 길이 상한 (교안 2단계 6절 검사 표) |
| ② | 합니다체, 3문장 이내 | 검토 LLM이 하던 말투 · 길이 다듬기를 지시문으로 |
| ③ | `check` | 검토 대신 코드 검사를 지났다는 단계 이름 (다른 짧은 이름도 됨) |
| ④ | 검토를 세지 않는다 | 지운 Review는 빼고 셈: 규칙 길 1, LLM 분류 길 2 |
정답: [answers/72-lab02-challenge-fast.json](answers/72-lab02-challenge-fast.json)
| 수업 준비 때 결과 | |
| --- | --- |
| `cases/lab02-challenge.json` | 5/5 (VPN 1.2초 · 비밀번호 1.1초 · 접수 1.3초) · 캐시 단계 상태 1/5 |
| `cases/lab02.json` · `lab01.json` | 9/9 · 4/4 |
| 캐시 없이 bench | p50 2.01 → **1.15초** · p95 2.23 → **1.16초** |
- 처음 쓴 `lab01` · `lab02` 기대에 `llm_calls: 3`, `steps`에 `review` 같은 **구조**가 들어 있어, 과제로 정당하게 빨라진 워크플로우가 실패했습니다. 품질 테스트는 **답의 내용**만 보게 고쳤습니다 (구조 기대는 그 단계의 테스트에만) → 이야기 거리: "최적화하면 깨지는 테스트는 무엇을 재고 있었나".
10/24 다시 돌린 결과: 고치기 전 `lab02-challenge` 1/5, bench p50 2.22 · p95 2.45초 → 고친 뒤 15/15 (`--repeat 3`, 0.9~1.6초), `lab02` 9/9, `lab01` 4/4, bench p50 **1.12** · p95 1.32초.
## 과제 2 · 요청문 예
```text
my-work/helpdesk.json 에 FAQ 번호 캐시를 더해 줘 (AGENTS.md 3절 캐시 키 표).
Rule classify 다음: FAQ cache key(Code: category 가 faq 이고 use_cache 이고 FAQ 결과가 있으면 faq_key = 'faq-' + 첫 FAQ id, 아니면 'none')
→ FAQ cache lookup(GET /cache/<faq_key>, neverError) → FAQ cached?(value 있고 faq_key 가 'none' 아님)
true → FAQ cached result(llm_calls 0, cached true, cache_by 'faq', steps ['faq', 'rule', 'cache']) → Send
false → Back to rule(Code: Rule classify 의 결과를 그대로) → Known?
Store in cache 뒤에 Store by FAQ(POST /cache/<faq_key>, 같은 value, ttl 600) → Result again.
질문 캐시의 Cached result 에는 cache_by 'question', Build result 에는 cache_by null.
기대 결과 cases/lab02-challenge2.json, lab02-challenge.json, lab02.json 도 통과.
```
**빈칸 답** (Q 문서 ▶ 빈칸 채우기 `____①`~`____④`)
| 빈칸 | 답 | 왜 |
| --- | --- | --- |
| ① | `faq-` | 캐시 키 표 둘째 줄: `faq-<FAQ id>` |
| ② | `faq` | FAQ 번호 키로 찾았을 때 (AGENTS.md 3절 Lab 02 과제 2) |
| ③ | `600` | 질문 캐시와 같은 ttl (초) |
| ④ | `question` | ②와 짝. 질문 키로 찾았을 때 |
정답: [answers/72-lab02-challenge2-faqcache.json](answers/72-lab02-challenge2-faqcache.json) (과제 1 정답에 노드 7개: FAQ cache key · FAQ cache lookup · FAQ cached? · FAQ cached result · Send FAQ cached · Back to rule · Store by FAQ)
| 10/24 결과 (`cases/lab02-challenge2.json`) | 고치기 전 (과제 1 정답) | 고친 뒤 |
| --- | --- | --- |
| 처음 묻는 VPN | ✔ 1.1초 | ✔ 2.5초 (push 직후) |
| 다른 말 VPN | ✘ `cached: false`, LLM 1번, 1.2초 | ✔ `cache_by: faq`, LLM 0번, **0.1초** |
| 같은 말 | ✘ `cache_by` 칸 없음 (캐시는 맞음, 0.1초) | ✔ `cache_by: question`, 0.0초 |
| `no_cache` | ✔ | ✔ 1.2초 |
| 프린터 처음 · 접수 현황 | ✔ | ✔ |
| 합 | 4/6 | 6/6, `lab02-challenge` 5/5, `lab02` 9/9 |
- `Back to rule`이 없으면 `Known?`이 캐시 응답을 읽어 분류가 사라집니다. 학생이 가장 많이 막힐 곳으로 예상합니다 (수업 준비 때 예상).
- 풀이 때 "잃는 것": FAQ-003에 묶인 질문은 모두 처음 질문에 맞춘 답을 받습니다.