diff --git a/core/orchestrator.py b/core/orchestrator.py index 005e4a6..fa10acb 100644 --- a/core/orchestrator.py +++ b/core/orchestrator.py @@ -27,6 +27,16 @@ _TRANSFER_CALM_REPLIES = [ "在催了在催了,马上哈", ] +_OUTBOUND_BLOCK_MARKERS = ( + "【历史记录摘要】", + "【详细记录】", + "【订单摘要】", + "【订单详情】", + " str: + if not text: + return "" + cleaned = str(text).strip() + if "[转移会话]" in cleaned: + return cleaned + if any(marker in cleaned for marker in _OUTBOUND_BLOCK_MARKERS): + logger.warning("[Orchestrator] 拦截到内部内容外发,替换为安全兜底回复") + return "我在帮你看记录,稍等哈" + return cleaned + async def on_raw_message_received(self, platform: str, raw_data: dict): """链路入口""" try: @@ -286,6 +308,7 @@ class SystemOrchestrator: # E. 发送并记录时间 if std_res.should_reply: + std_res.reply_content = self._sanitize_outbound_text(std_res.reply_content) std_res.metadata = {"acc_id": acc_id, "acc_type": acc_type} # 转接场景:先发一句安抚话,再发转接指令 diff --git a/core/pydantic_ai_agent_v2.py b/core/pydantic_ai_agent_v2.py index f4913c2..952245a 100644 --- a/core/pydantic_ai_agent_v2.py +++ b/core/pydantic_ai_agent_v2.py @@ -14,6 +14,14 @@ logger = logging.getLogger("cs_agent") from core.skill_manager import skill_manager +_INTERNAL_TOOL_MARKERS = ( + "【历史记录摘要】", + "【详细记录】", + "【订单摘要】", + "【订单详情】", +) + + def _clip(text: str, limit: int = 1200) -> str: if text is None: return "" @@ -32,6 +40,34 @@ def _fmt_time(ts: Any) -> str: return s +def _sanitize_reply_text(reply_text: str) -> str: + if not reply_text: + return "" + + text = str(reply_text) + text = re.sub(r'\[\]<\|[^|]+\|>', '', text) + text = re.sub(r'<\|[^|]*\|>', '', text) + text = re.sub(r'\[Function[^\]]*\]', '', text) + text = re.sub(r'\[/?Tool[^\]]*\]', '', text) + text = re.sub(r']*>', '', text, flags=re.IGNORECASE) + text = re.sub(r']*>.*?]*>', '', text, flags=re.DOTALL) + text = re.sub(r']*>.*', '', text, flags=re.DOTALL) + text = re.sub(r']*>', '', text) + text = re.sub(r'```[^`]*```', '', text, flags=re.DOTALL) + text = re.sub(r'AgentRunResult\([^)]*\)', '', text) + text = re.sub(r'\[/?[A-Z][a-zA-Z]*(?:Call|End|Start|Result|Return)[^\]]*\]', '', text) + text = re.sub(r'^\s*\[\s*\{.*$', '', text, flags=re.DOTALL) + text = re.sub(r'^\s*[\[{].*"name"\s*:.*$', '', text, flags=re.DOTALL) + text = re.sub(r'[\[\]]{2,}', '', text) + text = text.strip() + + if any(marker in text for marker in _INTERNAL_TOOL_MARKERS): + logger.warning("[Brain] 拦截到工具原文泄露,降级为安全兜底回复") + return "我在帮你看记录,稍等哈" + + return text.strip() + + class CustomerServiceBrain: """ 重构后的单一 Agent 大脑: @@ -182,21 +218,8 @@ class CustomerServiceBrain: if isinstance(raw_output, str): reply_text = raw_output - # 清理模型泄露的内部标记/乱码(覆盖所有已知格式) - reply_text = re.sub(r'\[\]<\|[^|]+\|>', '', reply_text) - reply_text = re.sub(r'<\|[^|]*\|>', '', reply_text) - reply_text = re.sub(r'\[Function[^\]]*\]', '', reply_text) - reply_text = re.sub(r'\[/?Tool[^\]]*\]', '', reply_text) - reply_text = re.sub(r']*>', '', reply_text, flags=re.IGNORECASE) - reply_text = re.sub(r']*>.*?]*>', '', reply_text, flags=re.DOTALL) - reply_text = re.sub(r']*>.*', '', reply_text, flags=re.DOTALL) - reply_text = re.sub(r']*>', '', reply_text) - reply_text = re.sub(r'```[^`]*```', '', reply_text) - reply_text = re.sub(r'\{["\'][^}]+\}', '', reply_text) - reply_text = re.sub(r'AgentRunResult\([^)]*\)', '', reply_text) - reply_text = re.sub(r'\[/?[A-Z][a-zA-Z]*(?:Call|End|Start|Result|Return)[^\]]*\]', '', reply_text) - reply_text = re.sub(r'[\[\]]{2,}', '', reply_text) - reply_text = reply_text.strip() + # 清理模型泄露的内部标记/工具原文 + reply_text = _sanitize_reply_text(reply_text) # 过滤"在呢铁子" if "在呢铁子" in reply_text: