feat(backend): AI 코스 생성에 Anthropic 클라이언트와 프롬프트 v1, 조건 해석 연결 #14

Merged
ttipo merged 1 commits from feat/course-ai-anthropic into main 2026-11-11 18:00:56 +09:00
Owner

무엇을 · 왜

AI 코스 생성에 실제 모델(Anthropic, 기본 Claude Haiku 5.5)을 붙인다. 구현 순서의 5단계(설계 §14)다.

4단계까지 서버는 가짜 AI 로 코스를 만들었다. 이제 문장에서 조건을 읽고 후보 중 코스를 고르는 일을 모델이 한다. 순서·시간·사실 검증은 지금처럼 코드(planner)가 한다. 모델과 effort 는 6단계 평가로 정한다.

Anthropic 클라이언트 — ai/anthropic-course-ai.client.ts

  • SDK @anthropic-ai/sdk 1.5.0 을 컨테이너 안에서 설치했다
    • 컨테이너 npm 11 이 선택 peer 항목(chokidar 3개)을 잠금 파일에서 지웠다
    • 이전 잠금 파일에서 되살렸고 npx npm@10 ci --dry-run 이 통과한다
  • 응답은 구조화 출력(output_config.format, JSON 스키마)으로 받고, 코드가 형식을 다시 본다
  • 추론은 adaptive, 깊이는 AI_COURSE_EFFORT · AI_CONDITION_EFFORT 다(기본 low)
  • 시스템 프롬프트는 요청마다 같게 두고 프롬프트 캐시에 올린다
  • 제한 시간은 AI_TIMEOUT_MS, SDK 재시도는 1회다. 취소하면 signal 로 호출을 끊는다
  • 키가 없거나 응답을 받은 뒤 실패하면 AI_002 다. 실패한 경우는 거절 · 잘림 · JSON 이 아님 · 형식이 다름이다
    • 응답 뒤 실패한 호출도 비용이 나갔으므로 사용량을 생성 기록에 남긴다

프롬프트 v1

프롬프트 파일 내용
코스 초안 course-v1 ai/course-prompt.ts 후보를 번호 | 이름 | 분류 | 실내외 | 방향·거리 | 그날 영업 | (반려) | 출처 한 줄로 준다. 요청 문장은 <request> 칸에만 넣고, 좌표는 보내지 않는다. 다시 부를 때는 위반 목록을 붙인다
조건 해석 conditions-v1 ai/condition-prompt.ts 문장에서 값만 뽑는다. "다음 주 토요일"을 셈하지 않고 고르도록 이번 주부터 3주 달력을 붙인다

조건 해석 — 값은 AI, 판정은 코드

  • condition-extraction.ts 의 applyExtraction 이 기본값을 채우고 issues 를 정한다. 판정하는 것은 지난 날짜, 30일 초과, 2시간 미만, 자정 넘김, 여러 날, 읽을 수 없음이다
  • 문제가 있는 값도 그대로 돌려준다. 앱이 그 항목을 짚어 고치게 하기 위해서다
  • 장소는 별칭표(PLACE_ALIASES: 팔공산 · 83타워 · 서문시장 · 김광석길) → 장소 DB 이름 검색 순서로 찾는다
  • 새 issue unknown_place — 말한 장소를 찾지 못해 기준 위치를 둔 경우다
  • 조건 해석 비용도 하루 비용 상한에 들어간다. 서버 메모리에 더하며, 서버가 한 대라 충분하다. 상한에 닿으면 AI 를 부르지 않고 기본값만 채운다

설정

키 기본값 설명
AI_PROVIDER anthropic fake 면 이전처럼 가짜 AI(개발용). 운영에서 fake 면 기동하지 않는다
ANTHROPIC_API_KEY — 운영에서는 없으면 기동하지 않는다. 개발에서 없으면 AI 호출이 AI_002 다
AI_COURSE_MODEL · AI_CONDITION_MODEL claude-haiku-5-5 가격표(shared/utils/ai-pricing.ts)에 있는 모델만 받는다 — 하루 비용 상한이 그 가격으로 계산된다
AI_COURSE_EFFORT · AI_CONDITION_EFFORT low low · medium · high

첫 실제 호출에서 고친 것

ai:check 로 한 번 불러 본 결과로 프롬프트 두 곳을 고쳤다.

  • "비 오는 토요일"처럼 날씨만 묘사한 문장으로는 indoorIfRain 을 채우지 않는다
  • 예보가 없다는 알림을 쓰지 않는다. 날씨는 앱이 보여 준다

확인 방법

# 단위 테스트 · e2e (실제 호출 없음 — SDK 자리에 가짜를 넣는다)
docker exec wandergo-backend npx jest src/modules/course-generation src/shared
docker exec wandergo-backend npx jest --config ./test/jest-e2e.json

# 실제 호출 확인 — 키를 쓰므로 키가 있는 환경에서. 결과는 저장하지 않고 날씨는 부르지 않는다
docker exec wandergo-backend npm run ai:check -- --text="토요일 오후 수성못 산책하고 카페" --course
  • 단위 테스트를 52개 더했다
    • 가격 계산, 조건 적용 규칙(평가셋 D01 · D02 · P09 · W04 · W06 · X02 · X06 · X07 · X10 · F04 사례)
    • 프롬프트 만들기(좌표가 들어가지 않음 · 시스템 프롬프트에 날짜 없음), Anthropic 클라이언트 요청 형식과 실패 처리
    • 장소 이름 고르기, 설정 검증
  • e2e 를 2개 더했다
    • 조건 해석이 장소 이름을 기준 장소로 찾고, 못 찾은 장소 · 대구 밖 · 짧은 시간을 issues 로 알리는가
    • AI 가 답한 뒤 실패해도 비용이 생성 기록에 남는가
  • 빈 임시 DB 로 e2e 전체(6개 스위트, 59개)를 돌려 통과했다. CI 와 같은 조건이다
  • 실제 호출 결과(개발 DB, Haiku 5.5 · effort low, 2026-11-11). 문장은 "비 오는 토요일 오후, 강아지랑 수성못 데이트"
    • 조건 해석: 수성못 · 11-14(토) · 14:00 · 반려견으로 읽었다. 3.2초 · $0.0005
    • 코스 초안: 후보 48 → 12곳 중 3곳을 골랐다. 실내 동반 가능한 식당이 없다고 알렸다. 13.3초 · $0.0015
    • 시간표 검증은 통과했다(partial)

영향 범위

  • backend
  • frontend
  • flutter
  • docker / 인프라
  • 규칙 문서(.claude/)

공유 계약: 에러 코드와 응답 형식은 바뀌지 않는다. POST /course-conditions 의 issues 에 unknown_place 값이 더해진다. 아직 이 API 를 쓰는 화면이 없어 클라이언트 변경은 없다(앱 연결은 7단계).

개발 환경: ANTHROPIC_API_KEY 가 있으면 개발 서버의 AI API 가 실제로 호출된다. 이전처럼 쓰려면 backend/.env.development 에 AI_PROVIDER=fake 를 넣고 컨테이너를 다시 만든다. 컨테이너 환경변수는 만들 때 읽으므로 up -d --force-recreate backend 가 필요하다.

체크리스트

  • 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과: lint · tsc · build · 백엔드 단위 테스트 441개 · e2e 59개
  • 주석 한국어, 매직 넘버·하드코딩 문자열 없음: 기준값은 course-generation.constants.ts, 가격은 ai-pricing.ts 에 모았다
  • 사용자 노출 실패는 모두 에러 코드와 함께 처리: AI 실패는 AI_002, 조건 해석 실패는 기본값으로 대신한다
  • 에러 코드를 바꿨다면 npm run sync:error-codes 실행 후 생성 파일까지 커밋 — 바꾸지 않았다
  • 엔티티와 마이그레이션 함께 커밋 — 스키마 변경 없음
  • 민감정보 미커밋 — 키는 backend/.env.{env} 에만 있다
  • 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음: 요청 문장·프롬프트는 서버 로그에 남기지 않는다. SDK 오류는 짧은 이유만 남긴다

🤖 Generated with Claude Code

## 무엇을 · 왜 AI 코스 생성에 실제 모델(Anthropic, 기본 Claude Haiku 5.5)을 붙인다. 구현 순서의 5단계(설계 §14)다. 4단계까지 서버는 가짜 AI 로 코스를 만들었다. 이제 문장에서 조건을 읽고 후보 중 코스를 고르는 일을 모델이 한다. 순서·시간·사실 검증은 지금처럼 코드(planner)가 한다. 모델과 effort 는 6단계 평가로 정한다. ### Anthropic 클라이언트 — `ai/anthropic-course-ai.client.ts` - SDK `@anthropic-ai/sdk` 1.5.0 을 컨테이너 안에서 설치했다 - 컨테이너 npm 11 이 선택 peer 항목(chokidar 3개)을 잠금 파일에서 지웠다 - 이전 잠금 파일에서 되살렸고 `npx npm@10 ci --dry-run` 이 통과한다 - 응답은 구조화 출력(`output_config.format`, JSON 스키마)으로 받고, 코드가 형식을 다시 본다 - 추론은 adaptive, 깊이는 `AI_COURSE_EFFORT` · `AI_CONDITION_EFFORT` 다(기본 low) - 시스템 프롬프트는 요청마다 같게 두고 프롬프트 캐시에 올린다 - 제한 시간은 `AI_TIMEOUT_MS`, SDK 재시도는 1회다. 취소하면 signal 로 호출을 끊는다 - 키가 없거나 응답을 받은 뒤 실패하면 `AI_002` 다. 실패한 경우는 거절 · 잘림 · JSON 이 아님 · 형식이 다름이다 - 응답 뒤 실패한 호출도 비용이 나갔으므로 사용량을 생성 기록에 남긴다 ### 프롬프트 v1 | 프롬프트 | 파일 | 내용 | | --- | --- | --- | | 코스 초안 `course-v1` | `ai/course-prompt.ts` | 후보를 `번호 \| 이름 \| 분류 \| 실내외 \| 방향·거리 \| 그날 영업 \| (반려) \| 출처` 한 줄로 준다. 요청 문장은 `<request>` 칸에만 넣고, 좌표는 보내지 않는다. 다시 부를 때는 위반 목록을 붙인다 | | 조건 해석 `conditions-v1` | `ai/condition-prompt.ts` | 문장에서 값만 뽑는다. "다음 주 토요일"을 셈하지 않고 고르도록 이번 주부터 3주 달력을 붙인다 | ### 조건 해석 — 값은 AI, 판정은 코드 - `condition-extraction.ts` 의 `applyExtraction` 이 기본값을 채우고 `issues` 를 정한다. 판정하는 것은 지난 날짜, 30일 초과, 2시간 미만, 자정 넘김, 여러 날, 읽을 수 없음이다 - 문제가 있는 값도 그대로 돌려준다. 앱이 그 항목을 짚어 고치게 하기 위해서다 - 장소는 별칭표(`PLACE_ALIASES`: 팔공산 · 83타워 · 서문시장 · 김광석길) → 장소 DB 이름 검색 순서로 찾는다 - 새 issue `unknown_place` — 말한 장소를 찾지 못해 기준 위치를 둔 경우다 - 조건 해석 비용도 하루 비용 상한에 들어간다. 서버 메모리에 더하며, 서버가 한 대라 충분하다. 상한에 닿으면 AI 를 부르지 않고 기본값만 채운다 ### 설정 | 키 | 기본값 | 설명 | | --- | --- | --- | | `AI_PROVIDER` | `anthropic` | `fake` 면 이전처럼 가짜 AI(개발용). 운영에서 `fake` 면 기동하지 않는다 | | `ANTHROPIC_API_KEY` | — | 운영에서는 없으면 기동하지 않는다. 개발에서 없으면 AI 호출이 `AI_002` 다 | | `AI_COURSE_MODEL` · `AI_CONDITION_MODEL` | `claude-haiku-5-5` | 가격표(`shared/utils/ai-pricing.ts`)에 있는 모델만 받는다 — 하루 비용 상한이 그 가격으로 계산된다 | | `AI_COURSE_EFFORT` · `AI_CONDITION_EFFORT` | `low` | low · medium · high | ### 첫 실제 호출에서 고친 것 `ai:check` 로 한 번 불러 본 결과로 프롬프트 두 곳을 고쳤다. - "비 오는 토요일"처럼 날씨만 묘사한 문장으로는 `indoorIfRain` 을 채우지 않는다 - 예보가 없다는 알림을 쓰지 않는다. 날씨는 앱이 보여 준다 ## 확인 방법 ```bash # 단위 테스트 · e2e (실제 호출 없음 — SDK 자리에 가짜를 넣는다) docker exec wandergo-backend npx jest src/modules/course-generation src/shared docker exec wandergo-backend npx jest --config ./test/jest-e2e.json # 실제 호출 확인 — 키를 쓰므로 키가 있는 환경에서. 결과는 저장하지 않고 날씨는 부르지 않는다 docker exec wandergo-backend npm run ai:check -- --text="토요일 오후 수성못 산책하고 카페" --course ``` - 단위 테스트를 52개 더했다 - 가격 계산, 조건 적용 규칙(평가셋 D01 · D02 · P09 · W04 · W06 · X02 · X06 · X07 · X10 · F04 사례) - 프롬프트 만들기(좌표가 들어가지 않음 · 시스템 프롬프트에 날짜 없음), Anthropic 클라이언트 요청 형식과 실패 처리 - 장소 이름 고르기, 설정 검증 - e2e 를 2개 더했다 - 조건 해석이 장소 이름을 기준 장소로 찾고, 못 찾은 장소 · 대구 밖 · 짧은 시간을 issues 로 알리는가 - AI 가 답한 뒤 실패해도 비용이 생성 기록에 남는가 - 빈 임시 DB 로 e2e 전체(6개 스위트, 59개)를 돌려 통과했다. CI 와 같은 조건이다 - 실제 호출 결과(개발 DB, Haiku 5.5 · effort low, 2026-11-11). 문장은 "비 오는 토요일 오후, 강아지랑 수성못 데이트" - 조건 해석: 수성못 · 11-14(토) · 14:00 · 반려견으로 읽었다. 3.2초 · $0.0005 - 코스 초안: 후보 48 → 12곳 중 3곳을 골랐다. 실내 동반 가능한 식당이 없다고 알렸다. 13.3초 · $0.0015 - 시간표 검증은 통과했다(partial) ## 영향 범위 - [x] backend - [ ] frontend - [ ] flutter - [ ] docker / 인프라 - [ ] 규칙 문서(.claude/) > **공유 계약**: 에러 코드와 응답 형식은 바뀌지 않는다. `POST /course-conditions` 의 `issues` 에 `unknown_place` 값이 더해진다. 아직 이 API 를 쓰는 화면이 없어 클라이언트 변경은 없다(앱 연결은 7단계). > > **개발 환경**: `ANTHROPIC_API_KEY` 가 있으면 개발 서버의 AI API 가 실제로 호출된다. 이전처럼 쓰려면 `backend/.env.development` 에 `AI_PROVIDER=fake` 를 넣고 컨테이너를 다시 만든다. 컨테이너 환경변수는 만들 때 읽으므로 `up -d --force-recreate backend` 가 필요하다. ## 체크리스트 - [x] 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과: lint · tsc · build · 백엔드 단위 테스트 441개 · e2e 59개 - [x] 주석 한국어, 매직 넘버·하드코딩 문자열 없음: 기준값은 `course-generation.constants.ts`, 가격은 `ai-pricing.ts` 에 모았다 - [x] 사용자 노출 실패는 모두 에러 코드와 함께 처리: AI 실패는 `AI_002`, 조건 해석 실패는 기본값으로 대신한다 - [ ] 에러 코드를 바꿨다면 `npm run sync:error-codes` 실행 후 생성 파일까지 커밋 — 바꾸지 않았다 - [ ] 엔티티와 마이그레이션 함께 커밋 — 스키마 변경 없음 - [x] 민감정보 미커밋 — 키는 `backend/.env.{env}` 에만 있다 - [x] 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음: 요청 문장·프롬프트는 서버 로그에 남기지 않는다. SDK 오류는 짧은 이유만 남긴다 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ttipo added 1 commit 2026-11-11 17:16:42 +09:00
feat(backend): AI 코스 생성에 Anthropic 클라이언트와 프롬프트 v1, 조건 해석 연결
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 12m37s
CI / Frontend (lint · type-check · test) (pull_request) Successful in 11m24s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m57s
CI / Backend (lint · test · e2e · build) (push) Successful in 12m33s
CI / Frontend (lint · type-check · test) (push) Successful in 11m12s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m18s
4dc5be72a4
4단계까지 서버는 가짜 AI 로 코스를 만들었다. 실제 모델(기본 Haiku 5.5)을 붙여
문장에서 조건을 읽고 후보 중 코스를 고르게 한다(설계 §14 5단계).

- 구조화 출력(JSON 스키마)으로 받고, 같은 시스템 프롬프트는 캐시에 올린다
- 조건 해석은 AI 가 값만 뽑고 기본값과 되물을 것은 코드가 정한다(unknown_place 추가)
- 조건 해석 비용과 응답 뒤 실패한 호출 비용도 하루 비용 상한에 넣는다
- AI_PROVIDER(fake 로 되돌림) · effort 설정, 모델은 가격표에 있어야 기동한다
- 키로 실제 호출을 확인하는 ai:check 명령

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttipo merged commit 4dc5be72a4 into main 2026-11-11 18:00:56 +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#14