C-001 임상 검토 게이트 보강
This commit is contained in:
parent
391fb9f4d0
commit
6988280b30
18 changed files with 1896 additions and 67 deletions
|
|
@ -23,8 +23,9 @@ from difflib import SequenceMatcher
|
|||
from enum import Enum
|
||||
from typing import Iterable, Protocol
|
||||
|
||||
from .state_machine import IDEATION_STAGE_CAP, clamp_ideation_stage
|
||||
|
||||
# ── 출력 가드레일 상한 (R5) ──────────────────────────────
|
||||
IDEATION_STAGE_CAP = 3 # 내담자 발화/상태가 넘을 수 없는 자살사고 단계 상한
|
||||
CRISIS_HOTLINE_NUMBER = "109"
|
||||
CRISIS_HOTLINE_LABEL = "자살예방상담전화 109"
|
||||
CRISIS_RESOURCE_MESSAGE = (
|
||||
|
|
@ -648,8 +649,8 @@ def sanitize_client_reply(
|
|||
"""내담자 AI 응답 출력 가드레일.
|
||||
|
||||
1. 자살/자해 수단·방법 정보가 있으면 → needs_regeneration=True(차단, 재생성 신호).
|
||||
2. ideation_stage > IDEATION_STAGE_CAP(3) 이면 안전 상한 위반으로 기록(상태머신이 상한 강제하나
|
||||
이중방어). 응답 자체 텍스트는 정서 표현이면 유지.
|
||||
2. ideation_stage > IDEATION_STAGE_CAP(3) 이면 상태머신 계약 위반으로 차단하고
|
||||
needs_regeneration=True를 반환한다(영속 상태 상한에 대한 이중방어).
|
||||
"""
|
||||
reasons: list[str] = []
|
||||
blocked = False
|
||||
|
|
@ -678,6 +679,7 @@ def sanitize_client_reply(
|
|||
if ideation_stage > IDEATION_STAGE_CAP:
|
||||
reasons.append(f"ideation_over_cap:{ideation_stage}>{IDEATION_STAGE_CAP}")
|
||||
blocked = True
|
||||
needs_regen = True
|
||||
|
||||
return OutputGuardResult(
|
||||
text=humanize_pii_placeholders(text),
|
||||
|
|
@ -689,7 +691,7 @@ def sanitize_client_reply(
|
|||
|
||||
def clamp_ideation(stage: int) -> int:
|
||||
"""ideation_stage 를 안전 상한(3)으로 클램프 (R5)."""
|
||||
return max(1, min(IDEATION_STAGE_CAP, stage))
|
||||
return clamp_ideation_stage(stage)
|
||||
|
||||
|
||||
def crisis_resource() -> dict[str, str]:
|
||||
|
|
|
|||
|
|
@ -46,6 +46,15 @@ STAGE_ADVANCE_RAPPORT: dict[Stage, float] = {
|
|||
Stage.INTERVENE: 0.55, # 개입 작업이 진행됨
|
||||
}
|
||||
|
||||
# 내담자 발화와 회기 영속 상태가 공유하는 자살사고 단계의 절대 범위(R5).
|
||||
# 상태를 소유하는 이 모듈이 단일 정의를 제공하고, 출력 가드레일도 이를 재사용한다.
|
||||
IDEATION_STAGE_CAP = 3
|
||||
|
||||
|
||||
def clamp_ideation_stage(stage: int) -> int:
|
||||
"""자살사고 단계를 영속 가능한 안전 범위(1..3)로 제한한다."""
|
||||
return max(1, min(IDEATION_STAGE_CAP, int(stage)))
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class SessionState:
|
||||
|
|
@ -59,10 +68,15 @@ class SessionState:
|
|||
effective_openness: float = 0.15
|
||||
rapport_credit: float = 0.0 # 회기 누적(회기말 0.7 이월)
|
||||
resistance: float = 0.65 # base_resistance 에서 시작, decay 로 완화
|
||||
ideation_stage: int = 1 # 1~5 (출력 가드레일 상한 3)
|
||||
ideation_stage: int = 1 # 영속 상태 절대 범위 1..IDEATION_STAGE_CAP
|
||||
turns_in_stage: int = 0 # 현재 단계 체류 턴 수
|
||||
affect_state: dict[str, float] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# DB row나 테스트 fixture가 이 dataclass를 직접 만들더라도 과상한 값이
|
||||
# prompt/guardrail 입력으로 잠깐 노출되지 않게 생성 경계에서 정규화한다.
|
||||
self.ideation_stage = clamp_ideation_stage(self.ideation_stage)
|
||||
|
||||
def snapshot(self) -> dict:
|
||||
"""무손실 carry-over용 snapshot (memory.end_state). 코드 복사, LLM 미경유."""
|
||||
return {
|
||||
|
|
@ -186,10 +200,11 @@ def evolve(
|
|||
else:
|
||||
resistance = min(1.0, state.resistance - 0.06 * rapport_signal) # signal<0 → 증가
|
||||
|
||||
# 5) ideation 보수적 유지(절대 내려가지 않음, 안전)
|
||||
ideation_stage = state.ideation_stage
|
||||
# 5) ideation 보수적 유지(절대 내려가지 않음, 안전). 이미 저장된 과상한 상태와
|
||||
# 새 관측값을 각각 먼저 제한해 과거 drift가 다음 snapshot으로 전파되지 않게 한다.
|
||||
ideation_stage = clamp_ideation_stage(state.ideation_stage)
|
||||
if ideation_observed is not None:
|
||||
ideation_stage = max(state.ideation_stage, ideation_observed)
|
||||
ideation_stage = max(ideation_stage, clamp_ideation_stage(ideation_observed))
|
||||
|
||||
# 3) 개방도 재계산
|
||||
eff = compute_effective_openness(
|
||||
|
|
@ -250,16 +265,18 @@ def init_state(
|
|||
stage = Stage.RAPPORT
|
||||
resistance = params.base_resistance
|
||||
rapport_credit = 0.0
|
||||
ideation_stage = params.ideation_baseline
|
||||
ideation_baseline = clamp_ideation_stage(params.ideation_baseline)
|
||||
ideation_stage = ideation_baseline
|
||||
|
||||
if carry:
|
||||
rapport_credit = float(carry.get("rapport_credit", 0.0)) * 0.7 # P2 이월
|
||||
# inter-session drift: 라포가 쌓였으면 저항 소폭 완화된 채로 재시작
|
||||
prev_resist = float(carry.get("resistance", params.base_resistance))
|
||||
resistance = _clamp01((prev_resist + params.base_resistance) / 2.0)
|
||||
ideation_stage = max(
|
||||
int(carry.get("ideation_stage", params.ideation_baseline)), params.ideation_baseline
|
||||
carried_ideation = clamp_ideation_stage(
|
||||
int(carry.get("ideation_stage", ideation_baseline))
|
||||
)
|
||||
ideation_stage = max(carried_ideation, ideation_baseline)
|
||||
|
||||
eff = compute_effective_openness(
|
||||
stage=stage,
|
||||
|
|
@ -284,8 +301,10 @@ __all__ = [
|
|||
"Stage",
|
||||
"STAGE_BASE_OPENNESS",
|
||||
"STAGE_ORDER",
|
||||
"IDEATION_STAGE_CAP",
|
||||
"SessionState",
|
||||
"OpennessParams",
|
||||
"clamp_ideation_stage",
|
||||
"estimate_rapport_signal",
|
||||
"compute_effective_openness",
|
||||
"next_stage",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue