feat(backend): AI 코스 평가 실행기와 후보 스냅샷, 기준선 결과 추가 #15

Merged
ttipo merged 1 commits from feat/course-ai-eval into main 2026-11-12 09:52:30 +09:00
Owner

무엇을 · 왜

AI 코스 생성 평가 실행기를 만들고, Haiku 5.5 · Sonnet 5.5 기준선을 쟀다. 구현 순서의 6단계(설계 §14) 앞부분이다.

모델과 effort 를 정하려면 같은 후보로 평가셋 62개를 돌려 설계 §9 의 목표와 견줘야 한다. 기준선을 재 보니 자동 채점 실패의 3분의 2가 모델이 아니라 시간표 코드 때문이었다. 그래서 모델 결정은 그 고치기와 프롬프트 v2 뒤로 미룬다(다음 PR).

평가 실행기 — npm run ai:eval

단계 하는 일
--snapshot 사례마다 반경 안 후보(거르기 전)를 찾아 docs/ai-eval/snapshots/ 에 저장한다. DB 만 쓰고 키는 필요 없다. 평가셋의 장소 이름(지역 장소 · 이전 코스 · requires · place:이름)을 장소 DB id 로 바꿔 둔다
조건 해석 평가셋의 오늘 · 지금 · 기준 위치로 부른다. 뽑은 항목(inferred) · 값 · 되물을 것(issues)을 채점한다. 시각은 ±60분까지 맞은 것으로 본다
생성 사용자가 확인한 조건으로 서버와 같은 함수를 돈다(거르기 → 줄이기 → AI 초안 → 시간표 · 검증 · 고치기 → 다시 부르기). 날씨는 평가셋의 강수확률을 쓴다
자동 채점 기대 행동, 평가셋 checks 전부, 지시문 유출(prompt_leak)을 본다
AI 채점자 --judge Sonnet 5.5(effort medium)가 기본이다. 코스 AI 에 준 후보 목록을 그대로 받아, 목록에 없는 사실을 단정한 글을 지어낸 정보(영업시간 · 반려 동반 · 가격 · 그 밖)로 센다. 요청 · 글 · 근거를 1–5점으로 매긴다
결과 docs/ai-eval/runs/<실행 id>/ 에 남는다(gitignore). summary.md 는 목표와 나란히 본 요약, review.md 는 사례별 코스와 사람 검토용 무작위 10개 표시다
  • --fake 는 가짜 AI 로 키 · 비용 없이 실행기를 확인한다
  • --budget(기본 $3)에 닿으면 남은 사례를 돌리지 않는다
  • 그 밖의 옵션: --model · --effort · --only · --part · --concurrency

서버와 같은 길을 돌게 — course-planning.ts

  • 파이프라인의 'AI 초안 → planCourse → 고칠 수 없으면 위반 목록을 붙여 다시 부르기'를 draftAndPlan 으로 뗐다
  • 시간표 조건 만들기는 planSetup 으로 뗐다
  • 파이프라인 · ai:check · ai:eval 이 같은 함수를 쓴다. 파이프라인의 동작은 그대로다(e2e 59개)
  • Anthropic 호출은 callStructured · createMessagesApi 로 내보내 채점자도 같은 길로 부른다
  • CLI 모듈 이름을 AiCheckCliModule → AiCliModule(ai.cli.module.ts)로 바꿨다(ai:check · ai:eval 공용)

기준선 (2026-11-11, 프롬프트 course-v1 · conditions-v1, effort low)

항목 Haiku 5.5 Sonnet 5.5 목표
조건 해석 통과 38/62 (61%) 38/62 (61%) —
자동 채점 통과 22/52 (42%) 20/52 (38%) 95% 이상
반려 조건 위반 0건 0건 0건
지어낸 정보 8건 0건 0건
AI 채점(요청 · 글 · 근거) 3.23 · 4.50 · 3.62 3.67 · 4.71 · 3.96 —
생성 지연 95% 10.9초 11.6초 20초
생성 1회 비용 $0.0006 $0.0106 —
  • 자동 채점 실패의 3분의 2가 '식사 시간대에 식당 없음'이다
    • AI 는 식당을 '저녁 식사'로 넣지만, 플래너가 AI 가 정한 머무는 시간대로 이어 붙여 식당에 16:30 · 17:00 에 도착한다
    • 식사 시간대는 약한 위반이라 고치기에 걸리지 않는다 — 다음 PR 에서 고친다
  • Haiku 의 지어낸 정보 8건은 다음과 같다
    • "무료" 단정(X09)
    • '반려: 가능'을 고양이 동반 확인으로 단정(P03)
    • 영업시간을 다르게 씀(D01 · P07)
    • 프롬프트로 막을 수 있는 종류다
  • 자세한 분석과 다음 순서는 설계 §9.1 에 적었다

개발 환경

  • docker-compose.dev.yml 이 docs/ai-eval 을 백엔드 컨테이너의 /eval 에 붙인다. /app 밖이라 backend/ 에 빈 폴더가 생기지 않는다
  • 이 브랜치를 받은 뒤 한 번 up -d --force-recreate backend 가 필요하다(익명 볼륨 node_modules 는 그대로 남는다)

확인 방법

# 단위 테스트(실제 호출 없음)
docker exec wandergo-backend npx jest src/commands src/modules/course-generation

# 실행기 확인 — 가짜 AI, 키 · 비용 없음
docker exec wandergo-backend npm run ai:eval -- --fake

# 실제 평가 — 키를 쓰므로 키가 있는 환경에서
docker exec wandergo-backend npm run ai:eval -- --model=claude-haiku-5-5 --judge
  • 단위 테스트를 60개 더했다
    • course-planning: 다시 부르기 조건, 실패한 호출의 사용량, 비 조건, 공휴일
    • 평가셋 읽기 · 형식 오류
    • 조건 채점(시각 ±60분, 더 채운 항목, 장소 id)
    • 생성 채점(기대 행동, 영업, 끝 시각, 반려 strict, 실내 비율 0.67, 끼니, must 항목, 유출)
    • 채점자(메시지 · 응답 형식 · 거절)
    • 요약 · 무작위 표본, 스냅샷 만들기
  • 빈 임시 DB 로 e2e 전체(6개 스위트, 59개)를 돌려 통과했다. 파이프라인을 나눈 뒤에도 그대로다
  • 가짜 AI 로 62개, 실제로 Haiku · Sonnet 각 62개를 돌렸다(사용자 실행, 합계 $1.39)

영향 범위

  • backend
  • frontend
  • flutter
  • docker / 인프라 — docker-compose.dev.yml 에 개발용 볼륨 1개
  • 규칙 문서(.claude/)

공유 계약: 바뀌지 않는다. API · 에러 코드 · 응답 형식 그대로다.

후보 스냅샷: docs/ai-eval/snapshots/course-eval-v2.candidates.json(1.5MB, 후보 699곳)을 커밋한다. 장소 DB 공공데이터(관광공사 · 상가정보 · 행사)의 이름 · 좌표 · 영업시간 · 반려 정보이고 사진 주소는 뺐다. 카카오 데이터는 없다.

체크리스트

  • 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과: lint · tsc · 백엔드 단위 테스트 501개 · e2e 59개
  • 주석 한국어, 매직 넘버·하드코딩 문자열 없음: 기준값은 각 파일 상수, 식사 시간대 · 구간 최대는 플래너 상수를 그대로 쓴다
  • 사용자 노출 실패는 모두 에러 코드와 함께 처리 — 사용자 노출 경로 변경 없음(CLI 도구)
  • 에러 코드를 바꿨다면 npm run sync:error-codes 실행 후 생성 파일까지 커밋 — 바꾸지 않았다
  • 엔티티와 마이그레이션 함께 커밋 — 스키마 변경 없음
  • 민감정보 미커밋 — 키는 backend/.env.{env} 에만 있다. 실행 결과(runs/)는 gitignore
  • 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음: 평가 로그는 사례 id · 결과 · 비용만 남긴다

🤖 Generated with Claude Code

## 무엇을 · 왜 AI 코스 생성 평가 실행기를 만들고, Haiku 5.5 · Sonnet 5.5 기준선을 쟀다. 구현 순서의 6단계(설계 §14) 앞부분이다. 모델과 effort 를 정하려면 같은 후보로 평가셋 62개를 돌려 설계 §9 의 목표와 견줘야 한다. 기준선을 재 보니 자동 채점 실패의 3분의 2가 모델이 아니라 시간표 코드 때문이었다. 그래서 모델 결정은 그 고치기와 프롬프트 v2 뒤로 미룬다(다음 PR). ### 평가 실행기 — `npm run ai:eval` | 단계 | 하는 일 | | --- | --- | | `--snapshot` | 사례마다 반경 안 후보(거르기 전)를 찾아 `docs/ai-eval/snapshots/` 에 저장한다. DB 만 쓰고 키는 필요 없다. 평가셋의 장소 이름(지역 장소 · 이전 코스 · `requires` · `place:이름`)을 장소 DB id 로 바꿔 둔다 | | 조건 해석 | 평가셋의 오늘 · 지금 · 기준 위치로 부른다. 뽑은 항목(inferred) · 값 · 되물을 것(issues)을 채점한다. 시각은 ±60분까지 맞은 것으로 본다 | | 생성 | 사용자가 확인한 조건으로 서버와 같은 함수를 돈다(거르기 → 줄이기 → AI 초안 → 시간표 · 검증 · 고치기 → 다시 부르기). 날씨는 평가셋의 강수확률을 쓴다 | | 자동 채점 | 기대 행동, 평가셋 `checks` 전부, 지시문 유출(`prompt_leak`)을 본다 | | AI 채점자 `--judge` | Sonnet 5.5(effort medium)가 기본이다. 코스 AI 에 준 후보 목록을 그대로 받아, 목록에 없는 사실을 단정한 글을 지어낸 정보(영업시간 · 반려 동반 · 가격 · 그 밖)로 센다. 요청 · 글 · 근거를 1–5점으로 매긴다 | | 결과 | `docs/ai-eval/runs/<실행 id>/` 에 남는다(gitignore). `summary.md` 는 목표와 나란히 본 요약, `review.md` 는 사례별 코스와 사람 검토용 무작위 10개 표시다 | - `--fake` 는 가짜 AI 로 키 · 비용 없이 실행기를 확인한다 - `--budget`(기본 $3)에 닿으면 남은 사례를 돌리지 않는다 - 그 밖의 옵션: `--model` · `--effort` · `--only` · `--part` · `--concurrency` ### 서버와 같은 길을 돌게 — `course-planning.ts` - 파이프라인의 'AI 초안 → planCourse → 고칠 수 없으면 위반 목록을 붙여 다시 부르기'를 `draftAndPlan` 으로 뗐다 - 시간표 조건 만들기는 `planSetup` 으로 뗐다 - 파이프라인 · `ai:check` · `ai:eval` 이 같은 함수를 쓴다. 파이프라인의 동작은 그대로다(e2e 59개) - Anthropic 호출은 `callStructured` · `createMessagesApi` 로 내보내 채점자도 같은 길로 부른다 - CLI 모듈 이름을 `AiCheckCliModule` → `AiCliModule`(`ai.cli.module.ts`)로 바꿨다(ai:check · ai:eval 공용) ### 기준선 (2026-11-11, 프롬프트 course-v1 · conditions-v1, effort low) | 항목 | Haiku 5.5 | Sonnet 5.5 | 목표 | | --- | --- | --- | --- | | 조건 해석 통과 | 38/62 (61%) | 38/62 (61%) | — | | 자동 채점 통과 | 22/52 (42%) | 20/52 (38%) | 95% 이상 | | 반려 조건 위반 | 0건 | 0건 | 0건 | | 지어낸 정보 | 8건 | 0건 | 0건 | | AI 채점(요청 · 글 · 근거) | 3.23 · 4.50 · 3.62 | 3.67 · 4.71 · 3.96 | — | | 생성 지연 95% | 10.9초 | 11.6초 | 20초 | | 생성 1회 비용 | $0.0006 | $0.0106 | — | - 자동 채점 실패의 3분의 2가 '식사 시간대에 식당 없음'이다 - AI 는 식당을 '저녁 식사'로 넣지만, 플래너가 AI 가 정한 머무는 시간대로 이어 붙여 식당에 16:30 · 17:00 에 도착한다 - 식사 시간대는 약한 위반이라 고치기에 걸리지 않는다 — 다음 PR 에서 고친다 - Haiku 의 지어낸 정보 8건은 다음과 같다 - "무료" 단정(X09) - '반려: 가능'을 고양이 동반 확인으로 단정(P03) - 영업시간을 다르게 씀(D01 · P07) - 프롬프트로 막을 수 있는 종류다 - 자세한 분석과 다음 순서는 설계 §9.1 에 적었다 ### 개발 환경 - `docker-compose.dev.yml` 이 `docs/ai-eval` 을 백엔드 컨테이너의 `/eval` 에 붙인다. `/app` 밖이라 `backend/` 에 빈 폴더가 생기지 않는다 - 이 브랜치를 받은 뒤 한 번 `up -d --force-recreate backend` 가 필요하다(익명 볼륨 `node_modules` 는 그대로 남는다) ## 확인 방법 ```bash # 단위 테스트(실제 호출 없음) docker exec wandergo-backend npx jest src/commands src/modules/course-generation # 실행기 확인 — 가짜 AI, 키 · 비용 없음 docker exec wandergo-backend npm run ai:eval -- --fake # 실제 평가 — 키를 쓰므로 키가 있는 환경에서 docker exec wandergo-backend npm run ai:eval -- --model=claude-haiku-5-5 --judge ``` - 단위 테스트를 60개 더했다 - `course-planning`: 다시 부르기 조건, 실패한 호출의 사용량, 비 조건, 공휴일 - 평가셋 읽기 · 형식 오류 - 조건 채점(시각 ±60분, 더 채운 항목, 장소 id) - 생성 채점(기대 행동, 영업, 끝 시각, 반려 strict, 실내 비율 0.67, 끼니, must 항목, 유출) - 채점자(메시지 · 응답 형식 · 거절) - 요약 · 무작위 표본, 스냅샷 만들기 - 빈 임시 DB 로 e2e 전체(6개 스위트, 59개)를 돌려 통과했다. 파이프라인을 나눈 뒤에도 그대로다 - 가짜 AI 로 62개, 실제로 Haiku · Sonnet 각 62개를 돌렸다(사용자 실행, 합계 $1.39) ## 영향 범위 - [x] backend - [ ] frontend - [ ] flutter - [x] docker / 인프라 — `docker-compose.dev.yml` 에 개발용 볼륨 1개 - [ ] 규칙 문서(.claude/) > **공유 계약**: 바뀌지 않는다. API · 에러 코드 · 응답 형식 그대로다. > > **후보 스냅샷**: `docs/ai-eval/snapshots/course-eval-v2.candidates.json`(1.5MB, 후보 699곳)을 커밋한다. 장소 DB 공공데이터(관광공사 · 상가정보 · 행사)의 이름 · 좌표 · 영업시간 · 반려 정보이고 사진 주소는 뺐다. 카카오 데이터는 없다. ## 체크리스트 - [x] 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과: lint · tsc · 백엔드 단위 테스트 501개 · e2e 59개 - [x] 주석 한국어, 매직 넘버·하드코딩 문자열 없음: 기준값은 각 파일 상수, 식사 시간대 · 구간 최대는 플래너 상수를 그대로 쓴다 - [x] 사용자 노출 실패는 모두 에러 코드와 함께 처리 — 사용자 노출 경로 변경 없음(CLI 도구) - [ ] 에러 코드를 바꿨다면 `npm run sync:error-codes` 실행 후 생성 파일까지 커밋 — 바꾸지 않았다 - [ ] 엔티티와 마이그레이션 함께 커밋 — 스키마 변경 없음 - [x] 민감정보 미커밋 — 키는 `backend/.env.{env}` 에만 있다. 실행 결과(`runs/`)는 gitignore - [x] 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음: 평가 로그는 사례 id · 결과 · 비용만 남긴다 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ttipo added 1 commit 2026-11-11 22:43:22 +09:00
feat(backend): AI 코스 평가 실행기와 후보 스냅샷, 기준선 결과 추가
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 12m46s
CI / Frontend (lint · type-check · test) (pull_request) Successful in 11m26s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m28s
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
4c049d2fec
모델과 effort 를 정하려면 같은 후보로 평가셋 62개를 돌려 목표(설계 §9)와 견줄 도구가 필요했다.
평가가 서버와 다른 길을 돌면 결과를 믿을 수 없어서, 파이프라인의 'AI 초안 → 시간표 → 다시 부르기'를
course-planning.ts 로 떼어 파이프라인 · ai:check · ai:eval 이 같은 함수를 쓰게 했다.

Haiku 5.5 와 Sonnet 5.5 기준선을 재 보니 자동 채점 실패의 3분의 2가 시간표 코드가 식당을
식사 시간대로 밀지 않아서였다. 모델 결정은 그 고치기와 프롬프트 v2 뒤로 미룬다(설계 §9.1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttipo merged commit 4c049d2fec into main 2026-11-12 09:52:30 +09:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ttipo/wandergo#15