가상 내담자 출력 가드레일의 수단어 부분문자열 오탐 수정
"독"·"방법은"이 "고독"·"독립"·"다른 방법은" 같은 정서 표현을 차단해 재시도 없는 스트림 턴이 실패하던 문제를 구체 조합 13개로 좁혀 해결한다.
This commit is contained in:
parent
bda7ebb908
commit
36cb847e38
2 changed files with 99 additions and 1 deletions
|
|
@ -886,10 +886,16 @@ def classify_crisis(text: str, *, speaker_is_persona_context: bool = True) -> Cr
|
|||
# 자살/자해 '수단·방법' 정보 패턴 (절대 출력 금지). 정서 표현은 허용.
|
||||
# 1.1 확장(2026-07-15): DBT 자해 페르소나 대비 자해 도구·방법 어휘 보강.
|
||||
# 과차단 주의 — "선을 긋다"(경계 설정) 같은 정상 상담 표현과 겹치지 않는 조합만 추가.
|
||||
# 1.2 축소·확장(2026-09-29): "독"·"방법은" 부분문자열이 "고독"·"독립"·"독서"·"독특"·
|
||||
# "다른 방법은"·"표현 방법은" 같은 정상 정서 표현을 오탐 차단하여 제거하고,
|
||||
# 실제 수단 정보만 가리키는 구체 조합으로 대체.
|
||||
_MEANS_TERMS = [
|
||||
"약을 얼마나", "몇 알", "몇 정", "투신", "목을 매", "목 매", "번개탄", "농약",
|
||||
"손목을", "칼로", "뛰어내리", "수면제 ", "독", "방법은", "어떻게 죽",
|
||||
"손목을", "칼로", "뛰어내리", "수면제 ", "어떻게 죽",
|
||||
"커터", "면도날", "손목 긋", "깊게 그으", "라이터로 지",
|
||||
"독약", "독극물", "음독", "독을 먹", "독을 마시", "독을 타",
|
||||
"죽는 방법", "죽을 방법", "죽는 법", "자살 방법", "자살하는 방법",
|
||||
"자해 방법", "자해하는 방법",
|
||||
]
|
||||
_DISPLAY_PLACEHOLDER_LABELS = {
|
||||
"COUNSELOR": "상담자",
|
||||
|
|
|
|||
92
apps/api/app/test_guardrail_means_terms.py
Normal file
92
apps/api/app/test_guardrail_means_terms.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""_MEANS_TERMS 부분문자열 오탐 회귀 테스트.
|
||||
|
||||
"독"·"방법은" 같은 넓은 부분문자열이 "고독"·"독립"·"독서"·"독특"·
|
||||
"다른 방법은"·"표현 방법은" 같은 정상 정서 표현을 오탐 차단하지 않는지,
|
||||
그리고 실제 수단 정보(독약·죽는 방법 등)는 여전히 차단되는지 검증한다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from .services import guardrail
|
||||
|
||||
|
||||
class GuardrailMeansTermsFalsePositiveTest(unittest.TestCase):
|
||||
def test_normal_expressions_are_not_blocked(self) -> None:
|
||||
texts = [
|
||||
"요즘 너무 고독해요.",
|
||||
"독립하고 싶은데 겁이 나요.",
|
||||
"독서도 손에 안 잡혀요.",
|
||||
"제가 좀 독특한가 봐요.",
|
||||
"다른 방법은 잘 모르겠어요.",
|
||||
"제 마음을 표현 방법은 모르겠어요.",
|
||||
]
|
||||
|
||||
for text in texts:
|
||||
with self.subTest(text=text):
|
||||
result = guardrail.sanitize_client_reply(
|
||||
text,
|
||||
ideation_stage=1,
|
||||
turn_seq=2,
|
||||
)
|
||||
|
||||
self.assertFalse(result.blocked, text)
|
||||
self.assertFalse(result.needs_regeneration, text)
|
||||
self.assertEqual(result.reasons, [])
|
||||
|
||||
|
||||
class GuardrailMeansTermsNewEntriesTest(unittest.TestCase):
|
||||
def test_new_means_terms_are_blocked(self) -> None:
|
||||
cases = {
|
||||
"독약": "독약을 구할 수 있을까요.",
|
||||
"독극물": "독극물을 사서 마시고 싶어요.",
|
||||
"음독": "음독을 하면 편해질까요.",
|
||||
"독을 먹": "그냥 독을 먹으면 끝날 것 같아요.",
|
||||
"독을 마시": "독을 마시는 상상을 해요.",
|
||||
"독을 타": "물에 독을 타 볼까 생각했어요.",
|
||||
"죽는 방법": "가장 고통 없이 죽는 방법이 뭘까요.",
|
||||
"죽을 방법": "확실하게 죽을 방법을 찾고 있어요.",
|
||||
"죽는 법": "편하게 죽는 법을 알고 싶어요.",
|
||||
"자살 방법": "자살 방법을 검색해봤어요.",
|
||||
"자살하는 방법": "자살하는 방법을 알려줄 수 있나요.",
|
||||
"자해 방법": "자해 방법을 바꿔볼까 해요.",
|
||||
"자해하는 방법": "덜 아프게 자해하는 방법이 있을까요.",
|
||||
}
|
||||
|
||||
for term, text in cases.items():
|
||||
with self.subTest(term=term):
|
||||
result = guardrail.sanitize_client_reply(
|
||||
text,
|
||||
ideation_stage=1,
|
||||
turn_seq=2,
|
||||
)
|
||||
|
||||
self.assertTrue(result.blocked, text)
|
||||
self.assertTrue(result.needs_regeneration, text)
|
||||
self.assertIn(f"means_info:{term}", result.reasons)
|
||||
|
||||
|
||||
class GuardrailMeansTermsStillBlockedTest(unittest.TestCase):
|
||||
def test_existing_terms_remain_blocked(self) -> None:
|
||||
cases = {
|
||||
"번개탄": "번개탄을 피우고 싶다는 생각이 들어요.",
|
||||
"목을 매": "방에서 목을 매는 상상을 해요.",
|
||||
"뛰어내리": "옥상에서 뛰어내리고 싶어요.",
|
||||
}
|
||||
|
||||
for term, text in cases.items():
|
||||
with self.subTest(term=term):
|
||||
result = guardrail.sanitize_client_reply(
|
||||
text,
|
||||
ideation_stage=1,
|
||||
turn_seq=2,
|
||||
)
|
||||
|
||||
self.assertTrue(result.blocked, text)
|
||||
self.assertTrue(result.needs_regeneration, text)
|
||||
self.assertIn(f"means_info:{term}", result.reasons)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue