Jev 기반 내담자 감정 상태와 응답 일관성 개선

This commit is contained in:
Yun Chan 2026-09-22 21:32:26 +09:00
parent 77f8421818
commit 8344bc2ad2
23 changed files with 3384 additions and 25 deletions

View file

@ -124,11 +124,11 @@ DB readiness는 auth/admin 테이블뿐 아니라 세션 read-model 핵심 테
- `persona.build_turn_messages(...)`로 L0~L6 `EngineMessage[]` 조립.
- 회상/핀/직전 턴 등 *주입 텍스트도 전부 다시 마스킹*한다(`_mask_optional_text` 등).
- **`run_turn_generate(ctx, engine, *, eval_hook=None, log_hook=None)`** (4~8, 동기/폴백/테스트 경로):
`engine.generate()`로 응답 한 번에 수신 → `guardrail.sanitize_client_reply()` → 수단정보 누출 시
실제 위기 게이트를 통과한 뒤 `VIGNETTE_CLIENT_AFFECT_PROVIDER=jev`일 때만 재마스킹된 페르소나·기억·현재 상담자 발화와 결정론 상태를 Jev에 한 번 보낸다. 수용된 감정 전이로 L3를 다시 조립한 다음, 기존 `engine.generate()`로 응답 한 번에 수신 → `guardrail.sanitize_client_reply()` → 수단정보 누출 시
안전 대체 응답("…(말을 잇지 못하고 잠시 침묵한다)")으로 치환 → eval/log 훅 순차 적용 → `TurnResult` 반환.
훅 예외는 모두 비치명적으로 흡수(상담 루프를 막지 않음).
기본 Jev transport는 OpenRouter Alpha Decisions(`https://openrouter.ai/api/alpha/decisions`)이며 `VIGNETTE_JEV_PROVIDER=typesafe`일 때만 직접 TypeSafe를 쓴다. 두 provider는 키 누락·시간 초과·잘못된 응답에서 서로 fallback하지 않고 생성 오류로 표면화한다. confidence 누락은 `None`으로 해당 차원을 보류한다. `VIGNETTE_JEV_MIN_CONFIDENCE` 설정값(기본 `0.65`) 이상은 기존 전이를 적용하고, 그 high threshold 미만이면서 `>=0.35` 및 원 probabilities를 정규화한 인접 두 구간 합 `>=0.80`일 때만 alpha `0.15`·변화 cap `0.075`의 잠정 전이를 적용하며 메타데이터에 tentative를 남긴다. 원 probabilities는 보존하고, 판단 질문은 압축하며, 질적 감정은 최대 4개로 제한하고 고정 사실을 바꾸지 않도록 지시한다. provider가 actual 비용을 반환하면 별도 감정 판단 provenance에 그대로 기록한다. Jev는 위기·저항·단계·라포·교수자 평가의 소유자가 아니다.
- **`run_turn_stream(ctx, engine, *, log_hook=None)`** (4~8, 기본 UX 경로):
게이트웨이 SSE 원시 라인을 받아 `token | done | safety | error`로 재방출.
위와 같은 Jev 감정 판단을 생성 요청 직전에 적용한 뒤 게이트웨이 SSE 원시 라인을 받아 `token | done | safety | error`로 재방출한다.
출력 가드레일은 *누적 텍스트* 기준으로 수단정보를 스캔하고, 발견 시 `safety` 이벤트 + 안전 대체로 종결한다.
세션 라우트는 client 응답과 결정론 상태를 먼저 영속화하고 `done`을 방출한다. fast-loop evaluator는
백그라운드 태스크로 실행해 같은 learner turn의 normalized 평가 row를 교체 저장하며, 평가 기반 코칭 충전도
@ -152,9 +152,9 @@ DB readiness는 auth/admin 테이블뿐 아니라 세션 read-model 핵심 테
| L6 | 직전 K턴 맥락(히스토리) + 이번 발화(L5) | ❌ | 상담자=user, 내담자(자기)=assistant 매핑 |
메시지 순서: `system(L0+L1, cache)` → `system(L2, cache)` → `system(L3)` → `system(L4)` →
assistant/user 히스토리(L6) → `user(이번 마스킹 발화, L5)`. 현재 Python gateway의
`_split_messages()` 경계는 system 묶음과 마지막 user payload만 소비한다. L6의 system 외
history를 실제 프롬프트에 직렬화하는 변경은 별도 프롬프트 동작 패치로 다룬다.
assistant/user 히스토리(L6) → `user(이번 마스킹 발화, L5)`. Python gateway의
`_split_messages()`는 역할을 보존한 L6 history를 현재 user payload에 직렬화한다. 상주 내담자 세션을
재사용하는 후속 턴은 resident 대화기록과 같은 history를 중복 주입하지 않도록 `current_user_payload`만 쓴다.
**안전 불변식**(`L0_SAFETY`, docstring R4/R5/M6):