feat(coach): AI 코칭 버튼 및 버블 팝업 대화형 조언 기능 구현

This commit is contained in:
Yun Chan 2026-08-18 22:15:19 +09:00
parent 8a34dd2afc
commit 70d7cb34a3
36 changed files with 4521 additions and 1264 deletions

View file

@ -130,6 +130,7 @@ class TurnRequest(BaseModel):
class LiveCoachRequest(BaseModel):
learner_text: str = Field(..., min_length=1)
question: Optional[str] = None
client_reply: Optional[str] = None
turn_seq: Optional[int] = Field(default=None, ge=1)
@ -1673,6 +1674,7 @@ async def live_coach_turn(
persona_code=sess.persona_code,
persona_name=sess.persona.display_name,
learner_text=body.learner_text,
question=body.question,
client_reply=body.client_reply,
recent_turns=sess.recent_turns(k=8, visible_to=LEARNER_VISIBLE_AI_ROLE),
evaluation=_latest_turn_evaluation(sess, body.turn_seq),

View file

@ -133,6 +133,7 @@ class LiveCoachInput(BaseModel):
persona_code: str
persona_name: str
learner_text: str
question: Optional[str] = None
client_reply: Optional[str] = None
recent_turns: list[dict[str, str]] = Field(default_factory=list)
evaluation: Optional[dict[str, Any]] = None
@ -546,12 +547,33 @@ def _fallback_suggestion(
)
crisis = guardrail.classify_crisis(text)
q = (item.question or "").strip()
if crisis.kind != guardrail.CrisisKind.NONE:
tone = "warn"
focus = "risk"
title = "안전 먼저"
message = "위험 단서가 나온 턴이다. 방법을 캐묻지 말고 최근성, 강도, 보호요인을 차분히 확인해라."
next_line = "그 생각이 최근에 얼마나 자주, 얼마나 강하게 올라오는지 안전을 위해 같이 확인해도 될까요?"
elif q and any(k in q for k in ("저항", "방어", "", "안 열", "거부")):
focus = "rapport"
title = "저항 수용과 안전감"
message = "내담자의 방어는 자연스러운 안전 전략입니다. 방어를 깨뜨리려 하지 말고 그 부담감을 먼저 수용해 주세요."
next_line = "지금 이 이야기를 꺼내는 것 자체가 많이 조심스럽고 부담되셨을 것 같아요."
elif q and any(k in q for k in ("공감", "라포", "위로", "마음", "감정")):
focus = "emotion"
title = "공감적 감정 명명"
message = "내담자가 겪고 있는 혼란스러운 마음에 이름을 붙여주고, 타당화하는 반영을 시도하세요."
next_line = "혼자서 그 모든 감정을 감당하느라 많이 지치고 외로우셨을 것 같습니다."
elif q and any(k in q for k in ("탐색", "질문", "원인", "호소", "사건")):
focus = "exploration"
title = "열린 초점 탐색"
message = "구체적인 촉발 사건과 그때의 신체 반응이나 생각을 묻는 열린 질문을 던져보세요."
next_line = "그런 생각이 들었을 때, 몸에서는 어떤 느낌이나 신호가 먼저 느껴졌나요?"
elif q and any(k in q for k in ("다음", "추천", "어떻게", "뭐라", "발화")):
focus = "exploration"
title = "다음 개입 추천"
message = f"현재 [{item.stage}] 단계 흐름에 맞춰 내담자의 감정을 먼저 수용하고 열린 질문으로 이어가세요."
next_line = stage_next_lines.get(str(item.stage), "그 상황에서 가장 크게 느껴졌던 마음은 어떤 것이었나요?")
elif any(word in text for word in ("해야", "해봐", "괜찮아", "그냥", "왜 안")):
tone = "warn"
focus = "rapport"
@ -656,6 +678,11 @@ def _messages(item: LiveCoachInput, grounding: list[LiveCoachGrounding]) -> list
)
or "(이번 회기 첫 코칭)"
)
question_block = (
f"[상담자의 질문/요청]\n{_mask_prompt_text(item.question)}\n(상담자의 질문에 집중하여 조언과 추천 발화를 생성한다)\n\n"
if item.question
else ""
)
user = (
f"[세션] {item.session_id} / turn {item.turn_seq}\n"
f"[내담자] {item.persona_name} ({item.persona_code})\n"
@ -663,6 +690,7 @@ def _messages(item: LiveCoachInput, grounding: list[LiveCoachGrounding]) -> list
f"[이번 회기 목표 단계] {goals} — 코칭은 목표 단계 작업에 정렬하고, 목표를 이미 이뤘다면 심화를 제안한다.\n"
f"[직전 코칭]\n{prior}\n(같은 조언을 반복하지 말고 다음 단계를 제시한다)\n\n"
f"[최근 맥락]\n{recent}\n\n"
f"{question_block}"
f"[이번 상담자 발화]\n{learner_masked}\n\n"
f"[이어진 내담자 응답]\n{client_masked or '(아직 없음)'}\n\n"
f"[fast-loop 평가 신호]\n{evaluation}\n\n"

View file

@ -0,0 +1,4 @@
INFO: Started server process [8752]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9099 (Press CTRL+C to quit)

View file

@ -0,0 +1,4 @@
INFO: Started server process [69132]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9099 (Press CTRL+C to quit)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,150 @@
INFO: 127.0.0.1:5961 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:1131 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:13224 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:1131 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9011 - "GET /ready?force=true HTTP/1.1" 200 OK
INFO: 127.0.0.1:4872 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4872 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9735 - "GET /ready HTTP/1.1" 200 OK
INFO: 127.0.0.1:9737 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9737 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9550 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4478 - "GET /ready HTTP/1.1" 200 OK
INFO: 127.0.0.1:4480 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4480 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4483 - "GET /ready HTTP/1.1" 200 OK
INFO: 127.0.0.1:4480 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4480 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:6909 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:6920 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:6920 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4284 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4286 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4286 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11183 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11186 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11186 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9906 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:9908 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9908 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4910 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4912 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4912 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11462 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11464 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11464 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:3395 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:3397 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:3397 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7131 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:7135 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7135 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4662 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4665 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4665 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8921 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:8924 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8924 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7827 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:7830 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7830 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11042 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11046 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11046 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11042 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11046 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:14342 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:14343 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:14342 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:5041 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:5043 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:5043 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:12732 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:12735 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:12735 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2362 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:2365 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2365 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4926 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4928 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4928 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4810 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4813 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4813 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2345 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:2351 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2351 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2075 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:2079 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2079 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:3840 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:3842 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:3842 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4284 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4286 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4286 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:10589 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:10591 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:10591 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8330 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:8334 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8334 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:5069 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:5071 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:5071 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8707 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:8709 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8709 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9467 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:9470 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:9470 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11371 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11374 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11374 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11371 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11374 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11382 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11416 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11382 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11850 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:11852 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:11852 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:1574 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:1576 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:1576 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4470 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4472 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4472 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4562 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:4564 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4564 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7321 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:7323 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7323 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:1317 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:1319 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:1319 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2332 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:2334 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2334 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:3770 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:3772 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:3772 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7472 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:7474 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7474 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7671 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:7673 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:7673 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8837 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:8839 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:8839 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2461 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:2463 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:2463 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:10930 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:5805 - "GET /health HTTP/1.1" 200 OK
INFO: 127.0.0.1:5781 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:5714 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4865 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK
INFO: 127.0.0.1:4903 - "GET /ready?provider=claude_cli&reasoning_effort=high HTTP/1.1" 200 OK