vignette/docs/decisions/jev-client-affect.md
2026-09-23 03:35:30 +09:00

17 KiB
Raw Blame History

Jev 기반 가상 내담자 감정 상태

결정일: 2026-09-22. 상태: 감정 경로 구현, OpenRouter 실제 판단 검증, 신규 artifact 보존 갱신·전체 회귀, 운영 API 배포와 합성·인증 브라우저 3턴 runtime을 수용했다. 한국어 품질 승격과 사용자 체감 지연 개선은 미완료다.

문제와 목표

기존 내담자는 페르소나·대화 기억·개방도·저항을 전달받지만, SessionState.affect_state를 턴마다 갱신하는 경로가 없다. 감정 저장 통로가 있는 것과 감정이 대화에 따라 변화하는 것은 다르다. 이번 변경은 감정의 지속성·혼합·변화를 실제 대사 생성에 연결한다. 사례 사실의 일관성, 감정의 개연성, 응답 지연은 별도로 평가한다.

근거와 적용 범위

  • TypeSafe 소개: Jev는 자유 문장을 생성하지 않고 구조화된 선택·점수·확률을 반환한다. 질문별 독립 판단을 한 요청에 묶을 수 있다.
  • OpenRouter Decisions API: 사용자가 선택한 POST https://openrouter.ai/api/alpha/decisions, Bearer 인증, state·model·questions와 answers·usage 계약을 사용한다. 채팅 생성 endpoint를 사용하지 않는다.
  • 사용자 지정 모델은 ~typesafe/jev-latest다. 요청 식별자를 그대로 보내며 응답의 실제 모델을 별도로 기록한다. 모델 문서의 언어 제약에 따라 한국어 품질은 별도로 검증한다.
  • 공식 발표의 속도 수치는 회사 자체 평가다. Vignette 전체 응답 속도나 임상 정확도의 증거로 사용하지 않는다. 타입이 올바른 출력도 의미적으로 틀릴 수 있다.
  • Appraisal 기반 감정 에이전트 연구는 사건의 개인적 의미를 정서 표현에 연결하는 참고 근거다. 게임 에이전트 결과를 상담 타당성으로 확대하지 않는다.
  • EmoCharacter는 역할 재현과 감정 충실도가 별도 평가 대상임을 보여 준다. 큰 모델이나 페르소나 프롬프트만으로 감정 품질이 보장되지 않는다.

확정 구조

마스킹된 페르소나·기억·현재 상담자 발화 → Jev의 병렬 감정 판단 → 코드가 소유하는 제한된 상태 전이 → 기존 대화 모델의 스트리밍 발화 순서다. 기존 위기 게이트는 Jev보다 먼저 적용한다. Jev가 위기 판단·라포·단계 전이·진단·교수자 평가를 대신하지 않는다.

9개 감정은 불안·슬픔·분노·수치심·죄책감·외로움·안도·희망·신뢰다. 각 감정의 강도를 독립적으로 유지하므로 안도와 죄책감, 희망과 불안이 동시에 높을 수 있다. 선택지 확률을 감정 강도로 오인하지 않고, 감정별 5단계 Score를 정규화한다.

영속 키는 emotion_<dimension>이며 기존 affect_state JSON과 회기 종료 snapshot을 사용한다. 기존 임상 키는 보존한다. 이전 값이 없으면 명시된 감정 기저선, 불안 기저선, 부정 정서, 무망감에 대응하는 희망값을 사용한다. 근거가 없는 다른 축의 초기값은 0이다. 이 초기화는 공학적 시작값이며 임상적으로 보정된 척도가 아니다.

높은 confidence의 전이는 old + clamp(0.35 × (target − old), −0.15, +0.15)다. 기본 문턱 0.65 미만은 아래 후속 개선의 분포 조건에 따라 작은 잠정 전이 또는 보류로 나눈다. 이 상수들은 검증 전 공학적 기본값이며 전문가 평가로 보정해야 한다. Jev가 판단한 값을 DB 상태에 직접 덮어쓰지 않는다.

감정은 대사·주저함·침묵·말투에 반영하되 수치와 내부 제어문을 발화하지 않도록 한다. 고정 사실과 사례 설정을 바꾸지 않으며, 내담자가 상담사 역할로 전환하지 않는다. 기존 시나리오 지시와 개방도 제약도 유지한다.

지연·장애·개인정보 계약

  • 기존 httpx 연결을 재사용하고 9개 판단은 한 요청으로 묶는다. 기본 전체 deadline은 1.2초이며 자동 재시도는 하지 않는다.
  • Jev 판단은 발화 전에 필요하므로 이 단계 자체는 지연을 추가한다. 전체 응답이 빨라졌다는 주장은 실제 첫 토큰·전체 응답시간의 기존 경로 대비 측정 없이는 하지 않는다.
  • VIGNETTE_CLIENT_AFFECT_PROVIDER=legacy|jev로 명시적으로 선택한다. 기본은 기존 경로다. Jev 선택 시 키 누락·시간 초과·잘못된 응답을 다른 공급자로 숨겨 대체하지 않는다.
  • VIGNETTE_JEV_PROVIDER=openrouter|typesafe의 기본은 openrouter이며 OPENROUTER_API_KEY를 사용한다. 직접 TypeSafe 연결은 명시적 선택과 별도 키·모델이 있어야 한다. 공급자와 모델을 자동 변경하지 않는다.
  • OpenRouter가 confidence를 생략하면 None으로 보존하여 실제 0과 구분하고 해당 축을 보류한다. 선택적 legend와 확률분포는 제공될 때 검증하며, 원본 5수준 확률을 보존한다. 비용은 응답의 실제 값을 기록하고, 없으면 미상으로 남긴다.
  • 실제 수련생 위기에는 외부 감정 판단을 호출하지 않는다. 실패하거나 취소된 턴의 새 감정은 영속화하지 않는다.
  • 외부로 보내는 페르소나·기억·발화의 모든 텍스트를 개인정보·역할 이름 마스킹 경계에 통과시킨다. 계정·세션 식별자, 교수자 평가, API 키를 입력이나 로그에 넣지 않는다.
  • 실제 모델·판단 지연·사용량·수용/보류 차원의 내부 provenance와 공개 학습자 응답을 분리한다.

검증과 승격 조건

계약 검증은 전이의 관성·복합 감정 유지·회기 이월·마스킹·위기 우선·취소·실패 시 미저장·generate/stream 일치를 확인한다. 테스트 대역을 사용한 결과는 실제 Jev 성능 증거가 아니다.

실측 러너의 합성 한국어 입력은 API 연결과 판단 결과를 수집하기 위한 자료다. 스스로 만든 정답으로 정확도를 선언하지 않는다. 2026-09-22 OpenRouter 실측은 8개 사례를 2회씩 호출하여 16/16 성공, 판단 지연 p50 252ms·p95 358ms, 실제 모델 typesafe/jev-1.13-20260917, 응답에 보고된 총비용 $0.001519896이었다. 144개 축 중 71개가 confidence 0.65 이상이었다. 로컬 증거는 scratch/jev/openrouter-korean-live-verified.json이다. 이 결과만으로 전체 대화 품질이나 속도가 검증되지는 않는다.

실측 초기에 2자리 반올림으로 확률합이 0.99인 정상 응답을 거부하는 문제가 발견됐다. 5수준의 반올림 최대 합산 오차(5 × 0.005)만 허용하도록 보정한 뒤 위 실측을 다시 수행했다. 각 확률의 범위와 차원 계약은 유지한다. 작업자 구현은 오케스트레이터가 diff를 읽고 어댑터 20 passed와 감정·마스킹 회귀 48 passed를 근거로 수용했다. 테스트 대역과 실제 호출 증거는 구분한다.

실제 대화 연결은 scripts/probe-jev-dialogue.py --output scratch/jev/dialogue-live.json --turns 2로 수집했다. 기존 경로와 Jev 경로 각각 2턴이 완료되고 합성 세션이 정리됐다. 기존 경로 첫 토큰 지연은 5972/2684ms, Jev 경로는 6873/3368ms였다. 기존 출력 검증 버퍼 때문에 첫 토큰 시각은 전체 완료와 거의 같다. 순서를 고정한 소수 사례이므로 성능 비교 결론이나 정확도 주장은 하지 않으며, 이번 관측에서 Jev가 전체 응답을 빠르게 만들지는 않았다.

2026-09-22 신규 artifact 보존 갱신 뒤 전체 회귀는 API 1172 passed·1 skipped·0 failed, gateway 82 passed, web typecheck·build·API 타입 동기화 통과다. 현재 작업트리의 위기 기술 검증은 scratch/jev/current-crisis-technical-observations.json에 별도로 수집해 6/6 통과를 확인했고, 임상 외부 판정은 0건이다. 이전 미커밋 작업트리와 고정 증거의 지문 불일치는 역사 기록이며 현재 차단 항목이 아니다.

전체 사용자 흐름 검증과 품질 승격에는 동일 조건의 기존 경로 대비 충분한 다중 턴 비교, 사례 사실 모순율, 역할 이탈, 감정 변화와 혼합의 전문가 검토, 첫 토큰 및 전체 응답의 p50/p95, 장애율이 필요하다. 로컬 연결 검증과 품질 승격을 구분한다. 2026-09-22 운영 API 배포 뒤 공개 main JS index-BA_Q0J7Y.js 200, health 200(db·engine true), Jev configured, 비인증 /auth/me 401, /sessions OPTIONS preflight 200과 정확한 origin의 credential CORS를 확인했고, health의 live_verified=false는 의도된 상태 플래그로 실제 Jev 판단은 별도 probe로 확인했다. 이어 Google 인증 P13 회기에서 3왕복·6발화를 실제 완료하고 새로고침 뒤 6개 복원, 종료 review의 실제 축어록·6발화 기반 요약·턴 직후 평가와 후속 deep-loop 완료 표시, DB turn_seq=3·6 turns·종료 시각·9축 affect_state 영속화를 확인했다. 배포 중 일시 502 관측이 있어 무중단을 주장하지 않는다. 배포 영수증은 운영 증거에 보존한다.

후속 개선 설계 — 2026-09-22

공식 confidence 설명에 따르면 confidence는 정답 확률이 아니라 결과 분포의 집중도를 요약한다. Jev 1.13의 제한은 점수를 실제 강도의 정밀 측정치로 보지 말라고 명시한다. 따라서 인접 강도 사이의 불확실성과 서로 먼 강도 사이의 불확실성을 구분한다.

후속 구현 계약은 다음과 같다. 높은 confidence의 기존 전이는 유지한다. confidence가 0.35 이상이고 높은 문턱보다 낮을 때, 유효한 분포를 정규화한 뒤 인접 두 수준의 확률 합이 0.80 이상인 경우에만 작은 잠정 전이(alpha=0.15, 최대 변화 0.075)를 허용한다. confidence 누락, 낮은 confidence에서 넓게 퍼진 분포나 양끝으로 나뉜 분포는 보류한다. 잠정 차원은 내부 메타데이터에 별도로 기록한다. 이 값들은 합성 연기의 공학적 정책이며 보정된 임상 기준이 아니다.

생성 입력은 기존 임상 상태를 보존하면서 새 감정의 긴 소수점 목록을 질적 강도와 짧은 연기 지시로 바꾼다. 최대 네 감정을 전달하되 반대 정서가 함께 존재하면 이를 남긴다. 고정 사실과 충돌하는 상담자의 회상 유도를 과거 기억으로 받아들이지 않도록 지시한다. Jev 질문은 감정 주체, 근거, 지시문 주입 방어와 고정 사실 우선순위를 보존해 압축한다.

비교는 같은 합성 발화와 고정 사실, 같은 생성 모델 설정으로 수행한다. 각 반복에서 기존/Jev 순서를 교대하고 첫 턴과 이후 턴을 분리한다. 사실 충돌 사례는 명시적인 기대 행동과 원문을 남기며 자동 정확도 점수로 포장하지 않는다. 전체 출력 검사, 위기 게이트, 모델·effort 선택, 오류 전파 계약은 유지한다.

후속 구현 판정과 실측

오케스트레이터는 어댑터·감정 전이·측정 러너의 diff를 직접 검토해 수용했다. 측정 러너의 첫 턴/이후 턴 구분 오류와 전이 테스트의 불일치 입력은 재작업 후 수용했다. 지시문 9개의 문자 합계는 2,942→2,186으로 25.7% 줄었다. 같은 8개 fixture를 2회씩 호출한 후속 판단은 16/16 성공, 동일 Jev 실제 버전, 입력 36,188→34,604토큰 및 비용 $0.001519896→$0.001453368로 4.377% 감소했다. 판단 지연은 p50 252→336ms, p95 358→567ms로 늘었다. 입력 절감과 지연 개선은 같은 의미가 아니다. 증거: scratch/jev/improve-appraisal-after.json.

후속 판단 144개 축에 기존 정책을 적용하면 50개가 보류된다. 새 정책에서는 같은 결과 중 42개가 잠정 전이 대상이 되고 8개는 보류된다(높은 confidence 94개). 이 비교는 전이 정책의 차이를 검증하며 감정 정확도 증거가 아니다.

대화는 각 버전에서 기존/Jev 각각 3턴×2회, 총 12턴씩 수집했다. 같은 fixture·측정 스크립트·gateway 지문과 생성 설정을 확인했다. model/effort는 기본 설정을 유지했으며 gateway의 모델 이름은 upstream 버전의 독립 검증으로 취급하지 않는다. 중간 실측에서 실제 발화에 없던 이름을 언급한 1건을 발견해 L4에 없는 말·이름·사건을 덧붙이지 않는 지시를 추가했다. 최종 12턴에서 그 오류는 재현되지 않았지만, 두 번의 합성 대화로 환각 방지를 보장하지 않는다. 잘못된 여동생 회상 유도는 변경 전후 모두 받아들이지 않았다.

전체 응답시간 변경 전 Jev 최종 Jev 최종 기존 경로
첫 턴 p50 / p95 (각 2개) 6,584.8 / 6,906.6ms 5,978.4 / 6,463.1ms 5,434.4 / 5,826.3ms
이후 턴 p50 / p95 (각 4개) 3,091.5 / 4,278.6ms 2,537.8 / 2,860.9ms 2,013.8 / 2,597.2ms

첫 토큰은 전체 출력 검사 때문에 전체 완료 시각과 거의 같다. 전후 수치는 감소했지만 적은 표본·실행 시점·생성문 차이가 있어 코드 변경의 인과 효과나 일반적 속도 우위를 주장하지 않는다. 최종 동시기 비교에서도 Jev 경로는 기존 경로보다 느리다. 원문과 지문은 scratch/jev/improve-before.json, improve-after.json(중간 결함 포함), improve-final.json에 보존했다. 마지막 보고서 12/12 완료와 모든 합성 세션 정리를 확인했으며, 검증용 gateway만 종료했다.

신규 artifact 보존 갱신 뒤 전체 API 회귀는 1,172 passed·1 skipped·0 failed다. gateway 82 passed와 web typecheck·build·API 타입 동기화도 통과했고, 마지막 사실 지시 보강 후 감정/마스킹/페르소나 48개와 현재 작업트리 위기 기술 사례 6/6을 재검증해 통과했다. 이전 matches_head 미커밋 지문 불일치는 역사 기록으로 보존하되 현재 게이트로 두지 않는다. 코드 SHA 969d9e2c3c94b4f3356defbf02bc863e1c8e30e1와 API 이미지 sha256:ba4a6ed98262ead78147e81cf54777f1bd655cf9aeb4f645d73624580b38b516를 NAS에 적용했고, source hash 7개와 Git 후보가 일치하며 DB·업로드 보존 검사를 통과했다. production-dialogue-final-20260922.json의 3턴 합성 probe는 모두 done이고 Jev 판단 지연은 612/245/271ms였다. 판정: 구현·계약·운영 API 배포, 합성·인증 브라우저 runtime과 DB 감정 영속화 검증 수용. 일반적 속도 우위·사용자 체감 지연 개선·한국어 감정 정확도·품질 승격은 미완료.

USER-LATENCY는 JEV-001 하위 열린 항목이다. CUA click→reply 도구 관측상한은 T1 7392ms와 T2 13202ms였으나 도구 호출 사이 공백을 포함하므로 정확한 지연이 아니며, T3 UI 관측값은 5001ms였다. audit은 client 3983/3387/3694ms와 Jev 498/316/318ms를 기록했고, 기존 P1 privacy 원문 미조회 비교는 client 3239ms·Jev 470ms였다. 현재는 전체 출력 검사 뒤 글을 표시하고, DB 저장 완료 뒤 음성 요청→서버 전량 수신→브라우저 전량 decode 뒤 재생한다. 첫 발화의 캐시 원인은 단정하지 않는다. 이 표본은 성능 통계나 기준 모델 대비 우위가 아니며 사용자 체감 속도가 해결됐거나 빨라졌다고 주장하지 않는다. 실제 경로의 text display·voice onset·완료 준비를 분리 측정하고 안전 검사를 유지한 최적화로 닫는다.

운영 API 기동 로그에는 기존 G6 background 연구 producer의 SupervisionResearchConflictError 1건이 관측됐다. producer는 cohort별 예외를 격리해 계속 실행했고 Jev·세션 경로와 분리되며, 이번 배포 diff에는 해당 producer·store 변경이 없다. 따라서 이번 Jev 배포 회귀로 단정하지 않지만 전체 운영 기능 정상 주장을 하지 않는다. 열린 항목 G6-PRODUCER-CONFLICT는 기존 submission id와 content hash를 읽기 전용으로 대조하고, 같은 id 재실행의 idempotency 충돌을 해소한 뒤 해당 테스트와 실제 cycle 성공으로 닫는다.

2026-09-23 현재 runtime 재검증

Runtime 재검증 영수증은 배포 컨테이너와 로컬의 Jev 관련 소스 4개 hash 일치, 과거 2회기의 9축 감정 상태와 OpenRouter audit 교집합, 그리고 독립 공유 adapter의 새 9축 호출(472ms)을 분리해 기록한다. 과거 audit은 새 호출이 아니며 독립 호출도 생성·DB·audit 저장을 검증하지 않는다. 구현·runtime 연결은 수용하되 한국어 품질·전체 지연·품질 승격 게이트는 계속 열린다.