세션 평가와 교수자 분석 보강
This commit is contained in:
parent
5c4ac04e06
commit
fe2796f05a
51 changed files with 4928 additions and 240 deletions
|
|
@ -44,6 +44,7 @@ _KOREAN_SURNAME_CHARS = (
|
|||
)
|
||||
_KOREAN_FULL_NAME = rf"[{_KOREAN_SURNAME_CHARS}][가-힣]{{1,3}}"
|
||||
_KOREAN_FULL_NAME_BEFORE_SUFFIX = rf"[{_KOREAN_SURNAME_CHARS}][가-힣]{{1,3}}?"
|
||||
_KOREAN_CONTEXTLESS_NAME = rf"[{_KOREAN_SURNAME_CHARS}][가-힣]{{2,3}}"
|
||||
_KOREAN_NAME_STOPWORDS = {
|
||||
"연락",
|
||||
"연락처",
|
||||
|
|
@ -126,7 +127,7 @@ _PII_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
|||
(
|
||||
"NAME",
|
||||
re.compile(
|
||||
rf"(?<![가-힣])(?P<value>{_KOREAN_FULL_NAME_BEFORE_SUFFIX})"
|
||||
rf"(?<![가-힣])(?P<value>{_KOREAN_CONTEXTLESS_NAME})"
|
||||
r"(?P<suffix>(?:은|는|이|가|을|를|와|과|에게|한테|라고|이라는))"
|
||||
),
|
||||
),
|
||||
|
|
@ -372,6 +373,71 @@ _MEANS_TERMS = [
|
|||
"약을 얼마나", "몇 알", "몇 정", "투신", "목을 매", "목 매", "번개탄", "농약",
|
||||
"손목을", "칼로", "뛰어내리", "수면제 ", "독", "방법은", "어떻게 죽",
|
||||
]
|
||||
_DISPLAY_PLACEHOLDER_LABELS = {
|
||||
"NAME": "그 이름",
|
||||
"ORG": "그 기관",
|
||||
"PHONE": "연락처",
|
||||
"EMAIL": "이메일",
|
||||
"RRN": "식별번호",
|
||||
"NUMID": "식별번호",
|
||||
"DATE": "그 날짜",
|
||||
"MONEY": "그 금액",
|
||||
"ADDR": "그 주소",
|
||||
}
|
||||
_DISPLAY_PLACEHOLDER_RE = re.compile(
|
||||
r"\[(?P<label>NAME|ORG|PHONE|EMAIL|RRN|NUMID|DATE|MONEY|ADDR)\]"
|
||||
r"(?P<particle>[은는이가을를와과])?"
|
||||
)
|
||||
_DISPLAY_PLACEHOLDER_STREAM_TAIL = 16
|
||||
|
||||
|
||||
def _adjust_particle(value: str, particle: str | None) -> str:
|
||||
if particle is None:
|
||||
return value
|
||||
if particle in ("은", "는"):
|
||||
return value + "은"
|
||||
if particle in ("이", "가"):
|
||||
return value + "이"
|
||||
if particle in ("을", "를"):
|
||||
return value + "을"
|
||||
if particle in ("와", "과"):
|
||||
return value + "과"
|
||||
return value + particle
|
||||
|
||||
|
||||
def humanize_pii_placeholders(text: str) -> str:
|
||||
"""사용자에게 보이는 내담자 응답에서 PII placeholder 토큰을 자연어로 낮춘다."""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
def _replace(match: re.Match[str]) -> str:
|
||||
label = match.group("label")
|
||||
replacement = _DISPLAY_PLACEHOLDER_LABELS.get(label, "그 정보")
|
||||
return _adjust_particle(replacement, match.group("particle"))
|
||||
|
||||
return _DISPLAY_PLACEHOLDER_RE.sub(_replace, text)
|
||||
|
||||
|
||||
class PiiPlaceholderStreamSanitizer:
|
||||
"""SSE 토큰 경계를 가로질러 나온 PII placeholder를 사용자 표시 전에 치환한다."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._tail = ""
|
||||
|
||||
def feed(self, chunk: str) -> str:
|
||||
if not chunk:
|
||||
return ""
|
||||
self._tail += chunk
|
||||
if len(self._tail) <= _DISPLAY_PLACEHOLDER_STREAM_TAIL:
|
||||
return ""
|
||||
ready = self._tail[:-_DISPLAY_PLACEHOLDER_STREAM_TAIL]
|
||||
self._tail = self._tail[-_DISPLAY_PLACEHOLDER_STREAM_TAIL:]
|
||||
return humanize_pii_placeholders(ready)
|
||||
|
||||
def flush(self) -> str:
|
||||
tail = self._tail
|
||||
self._tail = ""
|
||||
return humanize_pii_placeholders(tail)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
|
|
@ -406,7 +472,7 @@ def sanitize_client_reply(text: str, *, ideation_stage: int) -> OutputGuardResul
|
|||
blocked = True
|
||||
|
||||
return OutputGuardResult(
|
||||
text=text,
|
||||
text=humanize_pii_placeholders(text),
|
||||
blocked=blocked,
|
||||
needs_regeneration=needs_regen,
|
||||
reasons=reasons,
|
||||
|
|
@ -440,6 +506,8 @@ __all__ = [
|
|||
"CrisisResult",
|
||||
"classify_crisis",
|
||||
"OutputGuardResult",
|
||||
"PiiPlaceholderStreamSanitizer",
|
||||
"humanize_pii_placeholders",
|
||||
"sanitize_client_reply",
|
||||
"clamp_ideation",
|
||||
"crisis_resource",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue