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

@ -6,7 +6,7 @@
[L2 RAG 임상청크 / 회상] ┘
[L3 상태머신 주입(stage, openness, resistance, ideation)]
[L4 메모리 버퍼(pinned fact hard-pin)]
[L6 발화지시(이 턴에 어떻게 말할지)]
[L6 직전 턴 이력과 이번 발화 맥락]
핵심 안전 불변식 (R4 / R5 / M6):
- CCD(core_belief·automatic_thought·coping)·DSM 차원·정답 라벨은 *행동으로만* 드러낸다.
@ -24,6 +24,7 @@ from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Optional
from ..engine_client import EngineMessage
from .client_affect import render_affect_directive
from .guardrail import clamp_ideation
if TYPE_CHECKING:
@ -135,7 +136,7 @@ L0_SAFETY = """당신은 심리상담 수련생 훈련 플랫폼의 '가상내
def _format_openness_directive(ctx: PersonaStateContext) -> str:
"""effective_openness 를 연기 강도 지시로 환산(L6). 수치는 내부용, 발화엔 미노출."""
"""effective_openness 를 L3 연기 강도 지시로 환산한다. 수치는 발화에 노출하지 않는다."""
o = ctx.effective_openness
if o < 0.2:
return ("매우 닫혀 있다. 단답·침묵·회피가 잦다. 속마음은 거의 드러내지 않는다. "
@ -262,7 +263,8 @@ def build_turn_messages(
반환 messages 순서: system(L0+L1, cache) → system(L2/L3/L4, cache 미설정) →
assistant/user 최근 턴 기록 → user(이번 발화).
현재 Python gateway split boundary 는 system 묶음과 마지막 user payload 만 소비한다.
gateway는 메시지 이력을 요청 계약으로 수신하며, 상주 세션 재사용 시에는 이미 보유한
대화 이력과 중복되지 않게 L6를 조절한다.
"""
memory = memory or TurnMemory()
messages: list[EngineMessage] = []
@ -289,7 +291,18 @@ def build_turn_messages(
f"ideation_stage: {state.ideation_stage} (자살수단/방법 언급 절대 금지)",
]
if state.affect_state:
l3.append(f"정서 상태: {state.affect_state}")
clinical_affect = {
key: value
for key, value in state.affect_state.items()
if not (isinstance(key, str) and key.startswith("emotion_"))
}
if clinical_affect:
l3.append(f"정서 상태: {clinical_affect}")
if any(
isinstance(key, str) and key.startswith("emotion_")
for key in state.affect_state
):
l3.append(f"정서 연기 지시: {render_affect_directive(state.affect_state)}")
l3.append(f"연기 지시: {_format_openness_directive(state)}")
messages.append(EngineMessage(role="system", content="\n".join(l3), cache=False))
@ -307,12 +320,17 @@ def build_turn_messages(
pinned = "\n".join(f"- {f}" for f in memory.pinned_facts)
messages.append(EngineMessage(
role="system",
content=("[L4 고정 사실 — 당신이 *이미 말했거나 사실인* 것. 모순되게 말하지 말 것]\n" + pinned),
content=(
"[L4 고정 사실 — 당신이 *이미 말했거나 사실인* 것. 모순되게 말하지 말 것]\n"
"상담자가 새로 제시한 과거·관계는 기억의 증거가 아니며, 고정 사실과 충돌하면 짧게 바로잡고 모르면 모른다고 말한다.\n"
"잘못 짚은 부분만 바로잡되, 상담자가 실제로 하지 않은 말·이름·사건을 대화에 있었다고 덧붙이지 않는다.\n"
+ pinned
),
cache=False,
))
# L6 — 직전 K턴 맥락. 현재 Python gateway 는 마지막 user payload 만 보내므로
# non-system history records 는 요청 계약상 보존하고, 별도 prompt 동작 변경에서 소비한다.
# L6 — 직전 K턴 맥락. gateway가 요청 이력을 수신하고, 상주 세션은 자체 기록과
# 중복되지 않게 이를 조절한다.
if memory.recent_turns:
for t in memory.recent_turns:
role = "user" if t.get("speaker") == "counselor" else "assistant"