feat: implement t-604 keyword prompt moderation
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from .models import SensitiveWord
|
||||
from .normalization import normalize_text
|
||||
from .providers.keyword import reset_keyword_matcher_cache
|
||||
from .services import moderate_prompt
|
||||
from .versioning import get_sensitive_words_version
|
||||
|
||||
|
||||
@override_settings(MODERATION_CACHE_VERSION_KEY="test:moderation:sensitive_words:version")
|
||||
class SensitiveWordNormalizationTests(TestCase):
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
reset_keyword_matcher_cache()
|
||||
|
||||
def tearDown(self):
|
||||
reset_keyword_matcher_cache()
|
||||
cache.clear()
|
||||
|
||||
def test_normalize_text_removes_separators_zero_width_and_case(self):
|
||||
self.assertEqual(normalize_text("Bad-\u200b Word"), "badword")
|
||||
self.assertEqual(normalize_text("敏 感-词"), normalize_text("敏感词"))
|
||||
|
||||
def test_sensitive_word_saves_normalized_word_and_rejects_empty_normalized_word(self):
|
||||
word = SensitiveWord.objects.create(word=" 敏 感-词 ", category="")
|
||||
|
||||
self.assertEqual(word.category, "custom")
|
||||
self.assertEqual(word.normalized_word, "敏感词")
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
SensitiveWord.objects.create(word=" - \u200b ")
|
||||
|
||||
def test_word_save_bumps_shared_cache_version(self):
|
||||
before = get_sensitive_words_version()
|
||||
|
||||
SensitiveWord.objects.create(word="敏感词")
|
||||
|
||||
self.assertNotEqual(get_sensitive_words_version(), before)
|
||||
|
||||
|
||||
@override_settings(
|
||||
MODERATION_ENABLED=True,
|
||||
MODERATION_PROVIDER="keyword",
|
||||
MODERATION_CACHE_VERSION_KEY="test:moderation:sensitive_words:version",
|
||||
)
|
||||
class KeywordModerationTests(TestCase):
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
reset_keyword_matcher_cache()
|
||||
|
||||
def tearDown(self):
|
||||
reset_keyword_matcher_cache()
|
||||
cache.clear()
|
||||
|
||||
def test_prompt_blocks_normalized_keyword_without_storing_prompt(self):
|
||||
word = SensitiveWord.objects.create(word="敏感词", category="policy")
|
||||
prompt = "请处理敏-感\u200b 词内容"
|
||||
|
||||
with self.assertLogs("apps.moderation.services", level="INFO") as logs:
|
||||
outcome = moderate_prompt(prompt=prompt)
|
||||
|
||||
self.assertTrue(outcome.blocked)
|
||||
self.assertEqual(outcome.labels, ("policy",))
|
||||
self.assertEqual(outcome.keywords, ("敏感词",))
|
||||
self.assertEqual(outcome.matched_word_ids, (word.id,))
|
||||
self.assertNotIn(prompt, "\n".join(logs.output))
|
||||
|
||||
def test_non_matching_prompt_passes(self):
|
||||
SensitiveWord.objects.create(word="敏感词")
|
||||
|
||||
outcome = moderate_prompt(prompt="正常业务标题")
|
||||
|
||||
self.assertFalse(outcome.blocked)
|
||||
|
||||
def test_matcher_rebuilds_after_shared_version_changes(self):
|
||||
self.assertFalse(moderate_prompt(prompt="新增词").blocked)
|
||||
|
||||
SensitiveWord.objects.create(word="新增词")
|
||||
|
||||
self.assertTrue(moderate_prompt(prompt="这个提示包含新增词").blocked)
|
||||
|
||||
|
||||
@override_settings(
|
||||
MODERATION_ENABLED=False,
|
||||
MODERATION_PROVIDER="keyword",
|
||||
MODERATION_CACHE_VERSION_KEY="test:moderation:sensitive_words:version",
|
||||
)
|
||||
class DisabledModerationTests(TestCase):
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
reset_keyword_matcher_cache()
|
||||
|
||||
def tearDown(self):
|
||||
reset_keyword_matcher_cache()
|
||||
cache.clear()
|
||||
|
||||
def test_disabled_moderation_is_noop(self):
|
||||
SensitiveWord.objects.create(word="敏感词")
|
||||
|
||||
outcome = moderate_prompt(prompt="敏感词")
|
||||
|
||||
self.assertFalse(outcome.blocked)
|
||||
Reference in New Issue
Block a user