iykyka/ai-service
Claude 5733ec4399
관계별 페르소나(2.7-B) 구현: 관계 티어 필드 + 온보딩/연락처 UI + 톤 프롬프트 분기
PRD.md §2.1-②·§3.1 P0 갭 우선순위 #2. close/formal 2종 관계 티어를 추가하고,
초안 생성 시 상대별 톤을 다르게 낸다.

- core-backend: TwinSettings.RelationshipTier(전역 기본값, 안전 우선 formal
  기본), Contact.RelationshipTier(1:1 연락처별 오버라이드, nullable).
  persona.go의 resolveRelationshipTier()가 연락처 오버라이드 → 전역 기본값 →
  formal 순으로 해석하고, 그룹 대화는 상대가 여럿이라 항상 전역 기본값만 사용.
  POST /conversations/:id/draft는 기존에 인증을 요구하지 않던 동작을 깨지
  않도록 currentUser(..., false)로 선택적 인증 처리 후 티어를 주입.
- 안전 관련 부수 수정: 그룹 대화에서는 전역 자율성 레벨(L1/L2)과 무관하게
  와카뷰 자동 발송을 무조건 차단(단톡 따라잡기는 L0 고정이 맞음).
- ai-service: RELATIONSHIP_TIER_INSTRUCTIONS + system_prompt_for_tier()로
  Gemini system_instruction에 관계 톤 지침을 주입. 에스컬레이션/정체성 게이팅
  로직은 티어와 무관하게 그대로 유지.
- mobile: 온보딩(말투 샘플 다음 단계)과 자율성 설정 화면에 전역 기본값
  SegmentedButton, 연락처 추가/수정 다이얼로그에 _RelationshipTierPicker로
  상대별 오버라이드 추가.
- 테스트: core-backend persona_test.go 6개(해석 순서·그룹 예외·비인증
  기본값 포함), ai-service 6개(system_prompt_for_tier + draft_reply 경로)
  전부 추가, 기존 스위트 모두 통과(go test, pytest 47/47, flutter analyze/test).
  실제 Flutter 빌드 + Playwright로 온보딩→연락처 오버라이드→목록 표시까지
  전체 라운드트립 시각 검증 완료.
- docs: roadmap.md §2.7-B, deploy-checklist.md N4-C2a~d 완료 처리. 다음
  우선순위는 Track C3(스팸/도배 감지 최소 버전).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YSB5PqF38raTxP5ABgr9m
2026-08-03 04:59:34 +00:00
..
app 관계별 페르소나(2.7-B) 구현: 관계 티어 필드 + 온보딩/연락처 UI + 톤 프롬프트 분기 2026-08-03 04:59:34 +00:00
tests 관계별 페르소나(2.7-B) 구현: 관계 티어 필드 + 온보딩/연락처 UI + 톤 프롬프트 분기 2026-08-03 04:59:34 +00:00
.dockerignore feat(deploy): Docker Compose stack for msn.iykyka.com (N2-B) 2026-07-31 02:26:40 +00:00
Dockerfile feat(deploy): Docker Compose stack for msn.iykyka.com (N2-B) 2026-07-31 02:26:40 +00:00
README.md 제품명 변경(분신→와카뷰) + 소프트 그라디언트/글래스모피즘 UI + 브랜드 로딩 화면 2026-07-31 01:15:42 +00:00
pytest.ini 2.5 QA: ai-service pytest 스위트 승격 + 자율성 플로우(L0~L2) 통합 테스트 2026-07-30 02:37:36 +00:00
requirements-dev.txt 2.5 QA: ai-service pytest 스위트 승격 + 자율성 플로우(L0~L2) 통합 테스트 2026-07-30 02:37:36 +00:00
requirements.txt Promote PoC scripts to the AI service (item 2.2) 2026-07-30 02:09:19 +00:00

README.md

와카뷰 AI service (Python)

Phase 1 AI 서비스 (docs/roadmap.md Phase 1 §2.2). poc/tone-corpus/의 세 스크립트 (generate_draft.py·escalation_filter.py·retrieve_style.py)를 그대로 승격한 내부 API — core-backend/(Go)가 이 서비스를 내부망 HTTP로 호출한다 (tech-design.md §8).

poc/tone-corpus/는 그대로 둔다 — 거긴 코퍼스 실험/블라인드 평가용 PoC 도구로 계속 쓰고, 여기 코드가 실제로 서비스에 쓰이는 "승격된" 버전이다. 두 곳의 로직은 지금 동일하지만, 앞으로 갈라질 수 있다(예: 여기는 프로덕션 안정성 위주로만 바뀌고, PoC 쪽은 계속 실험적으로 바뀌는 식).

실행

pip install -r requirements.txt
export GEMINI_API_KEY=...   # 또는 이 디렉토리에 .env 파일
uvicorn app.main:app --reload --port 8001

테스트

pip install -r requirements-dev.txt
pytest tests/ -v

escalation_filter.py(SELFTEST_CASES 승격 + 추가 케이스)·retrieve_style.py(overlap이 recency를 항상 이긴다는 것, 동점일 때만 recency가 tie-break한다는 것)·generation.py(escalate/no_key/ok 세 경로, Gemini 호출은 mock)·main.py(/health·/escalate/check·/draft 전부)를 tests/의 정식 pytest 스위트로 승격함 (docs/roadmap.md Phase 1 §2.5). 이전엔 ad-hoc TestClient 스크립트로만 확인했던 것들.

API

POST /escalate/check

{ "text": "계좌번호 알려줄래?" }

응답: { "escalate": true, "reason": "금전" }

/draft와 별개로 존재하는 독립 하드게이트 엔드포인트 — core-backend가 트윈(자동발송) 메시지를 저장하기 직전에 이걸 직접 호출해서, /draft를 거치지 않은 발송 경로도 전부 이 게이트를 통과하게 만든다 (docs/roadmap.md Phase 1 §2.4, AGENTS.md 안전 불변식). TestClient로 금전/약속/감정 케이스와 비대상 텍스트 전부 확인함.

POST /draft

{
  "context_lines": ["상대: 오늘 저녁에 뭐 먹을래?"],
  "style_examples": ["ㅇㅇ 좋지", "나도 궁금하네ㅋㅋ"]
}

style_examples(직접 큐레이션) 또는 history(과거 발화 전체, 자동 검색 — k로 개수 조절) 중 정확히 하나만 넣는다. 둘 다 넣거나 둘 다 안 넣으면 422.

응답:

{ "status": "ok" | "escalate" | "no_key", "text": "..." }
  • ok: text는 생성된 답장 초안
  • escalate: text는 에스컬레이션 사유 (금전/약속 확정/감정적으로 무거운 주제) — 이 경우 LLM은 호출되지 않는다 (escalation_filter.py가 하드 게이트)
  • no_key: GEMINI_API_KEY가 없어서 실제 전송될 프롬프트만 text에 담아 반환

TestClient로 style_examples/history 두 경로, 에스컬레이션 케이스, 검증 오류(422) 전부 확인함.

아직 없는 것

  • 온디바이스 말투 이력 저장 (이건 클라이언트/코어 백엔드 쪽 책임 — tech-design.md §2 참고)
  • 사후 알림 + 되돌리기 UI/전체 흐름 (코어 백엔드는 이제 에스컬레이션 시 escalation_logs에 기록은 하지만, 사용자에게 사후 알림을 띄우고 되돌리는 클라이언트 UX는 아직 — Flutter 쪽 작업)