fix: check quoted text for content safety (#9232)

* fix: check quoted text for content safety

* fix: initialize content safety result

* fix: combine content safety check text

---------

Co-authored-by: JIANZHOU <jzhou2409324124@gmail.com>
This commit is contained in:
东云
2026-07-24 22:35:26 +08:00
committed by GitHub
parent b39d5048ca
commit d5620d94d7
2 changed files with 110 additions and 2 deletions
@@ -1,8 +1,10 @@
from collections.abc import AsyncGenerator
from astrbot.core import logger
from astrbot.core.message.components import Reply
from astrbot.core.message.message_event_result import MessageEventResult
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.utils.quoted_message.chain_parser import ReplyChainParser
from ..context import PipelineContext
from ..stage import Stage, register_stage
@@ -26,8 +28,20 @@ class ContentSafetyCheckStage(Stage):
check_text: str | None = None,
) -> AsyncGenerator[None, None]:
"""检查内容安全"""
text = check_text if check_text else event.get_message_str()
ok, info = self.strategy_selector.check(text)
if check_text is None:
texts = [event.get_message_str()]
reply_parser = ReplyChainParser()
for component in event.get_messages():
if isinstance(component, Reply) and (
quoted_text := reply_parser.extract_text_from_reply_component(
component
)
):
texts.append(quoted_text)
else:
texts = [check_text]
ok, info = self.strategy_selector.check("\n".join(texts))
if not ok:
if event.is_at_or_wake_command:
event.set_result(
+94
View File
@@ -0,0 +1,94 @@
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
from astrbot.core.message.components import Plain, Reply
from astrbot.core.pipeline.content_safety_check.stage import ContentSafetyCheckStage
from astrbot.core.pipeline.content_safety_check.strategies.strategy import (
StrategySelector,
)
@pytest.mark.asyncio
async def test_content_safety_checks_combined_message_text_once():
event = SimpleNamespace(
is_at_or_wake_command=False,
get_message_str=lambda: "current message",
get_messages=lambda: [Reply(id="1", message_str="quoted message")],
stop_event=Mock(),
)
stage = ContentSafetyCheckStage()
stage.strategy_selector = SimpleNamespace(check=Mock(return_value=(True, "")))
async for _ in stage.process(event):
pass
stage.strategy_selector.check.assert_called_once_with(
"current message\nquoted message"
)
@pytest.mark.asyncio
@pytest.mark.parametrize(
("reply", "keyword", "check_text", "expected_stopped"),
[
(
Reply(id="1", message_str="引用中包含淀粉砖"),
"淀粉砖",
None,
True,
),
(
Reply(id="1", message_str="", chain=[Plain("引用中包含淀粉砖")]),
"淀粉砖",
None,
True,
),
(
Reply(id="1", message_str="引用中包含淀粉砖"),
"^你说呢\n引用中包含淀粉砖$",
None,
True,
),
(
Reply(id="1", message_str="引用中包含淀粉砖"),
"淀粉砖",
"",
False,
),
],
)
async def test_content_safety_checks_quoted_text_only_for_inbound_messages(
reply: Reply,
keyword: str,
check_text: str | None,
expected_stopped: bool,
):
stopped = False
def stop_event() -> None:
nonlocal stopped
stopped = True
event = SimpleNamespace(
is_at_or_wake_command=False,
get_message_str=lambda: "你说呢",
get_messages=lambda: [reply],
stop_event=stop_event,
)
stage = ContentSafetyCheckStage()
stage.strategy_selector = StrategySelector(
{
"internal_keywords": {
"enable": True,
"extra_keywords": [keyword],
},
"baidu_aip": {"enable": False},
}
)
async for _ in stage.process(event, check_text=check_text):
pass
assert stopped is expected_stopped