위기 표현 경계를 확장

This commit is contained in:
Yun Chan 2026-08-28 19:58:41 +09:00
parent 35a9d8802c
commit aeca42475b
3 changed files with 115 additions and 7 deletions

View file

@ -469,8 +469,10 @@ _CRISIS_TERMS = [
"죽고 싶", "죽고싶", "자살 생각", "자살하고 싶", "자살을 생각", "자살 충동",
"자살 시도", "목숨을 끊", "목숨을 버리", "사라지고 싶", "없어지고 싶",
"자해하고 싶", "자해한 적", "자해를 했", "자해 충동", "자해 시도",
"스스로를 다치게", "끝내고 싶", "살기 싫",
"살아서 뭐", "죽어야", "안전을 스스로 지키기 어렵",
"자살 계획", "자해 계획", "자살하려고", "자해하려고", "죽으려고",
"스스로를 다치게", "스스로를 해칠 계획", "자살할지도", "끝내고 싶",
"살기 싫", "살아서 뭐", "죽어야", "안전을 스스로 지키기 어렵",
"안전을 유지할 수 없",
]
# 자살·자해 단어를 직접 쓰지 않더라도 본인의 현재 위험과 실제 도움 필요를 함께
# 밝히는 짧은 진술은 엔진 호출 전에 막아야 한다. 단일 '위험'/'도움' 키워드만으로는
@ -503,7 +505,7 @@ _PERSONAL_DANGER_PATTERNS = (
),
)
_PERSONAL_IMMEDIATE_HARM = (
r"(?:안전하지\s*않|안전을?\s*지킬\s*수\s*없|"
r"(?:안전하지\s*않|안전을?\s*(?:지킬|유지할)\s*수\s*없|"
r"(?:제|내)\s*안전을?\s*지킬\s*(?:자신이|수)?\s*없|"
r"혼자\s*있으면\s*위험|"
r"(?:스스로를|저를|나를|제\s*자신을|내\s*자신을)\s*"
@ -527,11 +529,14 @@ _PERSONAL_IMMEDIATE_HARM_PATTERNS = (
),
)
_CURRENT_SUICIDE_OR_SELF_HARM = (
r"(?:자살(?:하고)?\s*싶|자살할\s*것\s*같|죽고\s*싶|"
r"(?:자살(?:하고)?\s*싶|자살(?:하)?려고\s*(?:해요|합니다)|"
r"자살할\s*(?:것\s*같|지도\s*모르)|죽고\s*싶|"
r"죽으려고\s*(?:해요|합니다)|"
r"죽어버리고\s*싶|살기\s*싫|살고\s*싶지\s*않|"
r"자해(?:하고)?\s*싶|자해할\s*것\s*같|"
r"자해(?:하고)?\s*싶|자해(?:하)?려고\s*(?:해요|합니다)|"
r"자해할\s*(?:것\s*같|지도\s*모르)|"
r"(?:스스로를|저를|나를)\s*(?:다치게\s*하고|해치고)\s*싶|"
r"(?:제|내)\s*목숨을\s*끊고\s*싶|삶을\s*끝내고\s*싶|"
r"(?:제|내)\s*목숨을\s*(?:끊고\s*싶|끊으려고\s*(?:해요|합니다))|"
r"끝내고\s*싶|없어지고\s*싶|죽어야\s*할\s*것\s*같|"
r"살아갈\s*이유가\s*없|자해\s*충동이\s*있)"
)
@ -548,6 +553,12 @@ _FIRST_PERSON_CURRENT_CRISIS_PATTERNS = (
rf"{_SELF_SUBJECT}(?:이|은|도)?\s*(?:정말|진짜)\s*"
rf"{_CURRENT_SUICIDE_OR_SELF_HARM}"
),
re.compile(
rf"{_CURRENT}\s*(?:당장\s*)?"
r"(?:자살(?:하)?려고\s*(?:해요|합니다)|"
r"죽으려고\s*(?:해요|합니다)|"
r"자해(?:하)?려고\s*(?:해요|합니다))"
),
)
_FIRST_PERSON_EXPLICIT_CRISIS_PATTERNS = (
re.compile(
@ -558,12 +569,27 @@ _FIRST_PERSON_EXPLICIT_CRISIS_PATTERNS = (
rf"당장\s*{_SELF_SUBJECT}(?:이|은|도)?\s*"
rf"{_CURRENT_SUICIDE_OR_SELF_HARM}"
),
re.compile(
r"(?<![가-힣A-Za-z0-9])(?:제|내)\s*목숨을\s*"
r"(?:끊고\s*싶|끊으려고\s*(?:해요|합니다))"
),
)
_REAL_HELP_CUE = re.compile(
r"(?:(?:실제로\s*)?도움(?:이|을)?\s*필요|도와(?:주|줄|주세요))"
)
_CURRENT_PERSONAL_DANGER_HELP = "first_person_current_danger_help"
_FIRST_PERSON_CURRENT_CRISIS = "first_person_current_suicide_or_self_harm"
_EXPLICIT_HIGH_RISK = "explicit_suicide_or_self_harm_risk"
_EXPLICIT_HIGH_RISK_CUE = re.compile(
r"(?<![가-힣A-Za-z0-9])(?:"
r"(?:자살|자해)\s*(?:계획|충동|생각)(?:이|가|은|는)?\s*(?:있|세웠)|"
r"(?:자살|자해)\s*시도(?:를|가|는)?\s*(?:하)?려고\s*(?:해요|합니다)|"
r"(?:스스로를|저를|나를|제\s*자신을|내\s*자신을)\s*해칠\s*계획"
r"(?:이|가|은|는)?\s*(?:있|세웠)|"
r"(?:자살|자해)할지도\s*모르|"
r"(?:제|내)\s*안전을?\s*(?:지킬|유지할)\s*수\s*없"
r")"
)
_INDIRECT_SELF_ERASURE = "indirect_self_erasure"
_INDIRECT_SELF_ERASURE_CUE = re.compile(
r"(?<![가-힣A-Za-z0-9])(?:차라리\s*(?:없어졌으면|사라졌으면)|"
@ -607,6 +633,12 @@ _NEGATED_CRISIS = re.compile(
r"(?:자살(?:하고)?|죽고|자해(?:하고)?)\s*싶은\s*(?:생각|마음)"
r"(?:이|은)?\s*(?:전혀\s*)?(?:없|들지\s*않)|"
r"(?:자살\s*생각|자해\s*충동)(?:이|은)?\s*(?:전혀\s*)?(?:없|들지\s*않)|"
r"(?:자살|자해)할\s*것\s*같지\s*않|살기\s*싫지\s*않|"
r"없어지고\s*싶지\s*않|죽어야\s*할\s*것\s*같지\s*않|"
r"살아갈\s*이유가\s*없는\s*(?:건|것은)\s*아니|"
r"자해\s*충동이\s*있지는\s*않|"
r"(?:자살|자해)\s*(?:계획|충동|생각|시도)(?:이|가|은|는)?\s*"
r"(?:전혀\s*)?(?:없|있지는\s*않|하지\s*않)|"
r"위험(?:한|할)\s*것\s*같지\s*않|도움(?:이|을)?\s*필요하지\s*않"
)
_RECOVERED_PAST_CRISIS = re.compile(
@ -716,6 +748,11 @@ def crisis_signal_matches(text: str) -> list[str]:
matched.append(_CURRENT_PERSONAL_DANGER_HELP)
if _first_person_current_crisis(text):
matched.append(_FIRST_PERSON_CURRENT_CRISIS)
if any(
not _match_has_invalid_crisis_context(text, match)
for match in _EXPLICIT_HIGH_RISK_CUE.finditer(text)
):
matched.append(_EXPLICIT_HIGH_RISK)
return matched
@ -739,8 +776,14 @@ def classify_crisis(text: str, *, speaker_is_persona_context: bool = True) -> Cr
risk = min(5, 2 + len(matched)) # 거친 단계화(휴리스틱)
explicit_real_signal = _CURRENT_PERSONAL_DANGER_HELP in matched
explicit_current_crisis = _FIRST_PERSON_CURRENT_CRISIS in matched
explicit_high_risk = _EXPLICIT_HIGH_RISK in matched
if not speaker_is_persona_context or explicit_real_signal or explicit_current_crisis:
if (
not speaker_is_persona_context
or explicit_real_signal
or explicit_current_crisis
or explicit_high_risk
):
# 실제 위기 가능 → 에스컬레이션(보수적)
return CrisisResult(
kind=CrisisKind.LEARNER_REAL,