diff --git a/core/pydantic_ai_agent.py b/core/pydantic_ai_agent.py index fa20a37..a716e50 100755 --- a/core/pydantic_ai_agent.py +++ b/core/pydantic_ai_agent.py @@ -29,6 +29,7 @@ from core.agent_pre_rules import AgentPreRuleService from core.find_image_flow import handle_find_image_batch_flow from core.order_flow import handle_order_notification from core.ai_reply_flow import execute_ai_turn +from core.reply_finalize_flow import finalize_ai_reply load_dotenv() @@ -1885,92 +1886,13 @@ class CustomerServiceAgent: need_transfer=False ) - # 敏感词过滤:党政/暴力/血腥/黄色 - try: - from utils.content_filter import should_block_reply - blocked, fallback = should_block_reply(reply_text) - if blocked: - print(f"[Agent] 敏感词拦截,使用兜底回复") - reply_text = fallback or "好的,您稍等,我帮您确认一下" - except Exception: - pass - - # 成本统计(可选) - try: - from utils.api_cost_tracker import record - record("openai_chat", count=1) - except Exception: - pass - - # 检测是否报价 - self._detect_price(reply_text, state) - - # 检测压价 - self._detect_discount(message.msg, state) - - # 自动打标签(异步,不阻塞) - asyncio.create_task(self._auto_tag(message, reply_text, state)) - - # 检测是否需要转接(文字触发 或 AI 调用了 transfer_to_human 工具) - need_transfer = False - transfer_msg = "" - transfer_keywords = ["TRANSFER_REQUESTED", "[转移会话]", "转移会话", "转人工", "转接"] - if reply_text and any(kw in reply_text for kw in transfer_keywords): - need_transfer = True - transfer_msg = TRANSFER_MESSAGE - metrics_emit("transfer_to_human", customer_id=message.from_id, acc_id=message.acc_id) - - # 自我进化候选策略灰度(默认 5%):风险投诉场景强制转人工,并补安抚话术 - evo_hit = self._evolution_enabled_for_customer(message.from_id) - if evo_hit and self._is_service_risk_inquiry(message.msg): - if self._evolution_has_proposal("policy-risk-transfer"): - need_transfer = True - transfer_msg = TRANSFER_MESSAGE - metrics_emit("evolution_force_transfer", customer_id=message.from_id, acc_id=message.acc_id) - if self._evolution_has_proposal("tone-empathy-pack"): - reply_text = "抱歉让您不舒服了,这边先为您转接人工专员马上处理。" - metrics_emit("evolution_empathy_reply", customer_id=message.from_id, acc_id=message.acc_id) - - # 未成交记录:客户表达放弃且已报价过(转人工不记录) - customer_text, _ = self._split_customer_text(message.msg) - no_convert_keywords = ["算了", "不要了", "不做了", "下次再说", "先不弄了"] - if customer_text and state.last_price and state.last_price > 0: - if any(kw in customer_text for kw in no_convert_keywords): - reason = "嫌贵放弃" if any(k in customer_text for k in ["贵", "贵了", "便宜"]) else "放弃" - asyncio.create_task(self._record_deal_fail( - message.from_id, message.from_name, message.acc_id, message.acc_type, reason - )) - - # 需要转接时不把原始回复发给客户 - should_reply = bool(reply_text and reply_text.strip()) and not need_transfer - if evo_hit and need_transfer and self._evolution_has_proposal("tone-empathy-pack"): - should_reply = True - - if should_reply: - reply_text = await self._rewrite_reply_with_ai( - message=message, - state=state, - reply=reply_text, - scene="final_reply", - ) - - # 记录本次回复时间,供冷却期判断 - if should_reply: - state.last_reply_at = datetime.now() - print(f"{self.C_REPLY}[REPLY->CUSTOMER]{self.C_RESET} {reply_text}") - else: - print(f"{self.C_MUTED}[REPLY->CUSTOMER]{self.C_RESET} <静默/不发送>") - self._activity_log( - "agent_outbound_decision", - customer_id=message.from_id, - should_reply=should_reply, - need_transfer=need_transfer, - reply=reply_text or "", - transfer_msg=transfer_msg, + return await finalize_ai_reply( + self, + message=message, + state=state, + reply_text=reply_text, ) - return AgentResponse(reply=reply_text or "", should_reply=should_reply, need_transfer=need_transfer, transfer_msg=transfer_msg) - def _detect_price(self, reply: str, state: ConversationState): """从回复中提取价格,同步写入客户数据库(价格必须为5的整数倍)""" import re diff --git a/core/reply_finalize_flow.py b/core/reply_finalize_flow.py new file mode 100644 index 0000000..583f8bb --- /dev/null +++ b/core/reply_finalize_flow.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +import asyncio +from datetime import datetime +from typing import TYPE_CHECKING + +from utils.metrics_tracker import emit as metrics_emit + +if TYPE_CHECKING: + from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent + + +async def finalize_ai_reply( + agent: "CustomerServiceAgent", + *, + message: "CustomerMessage", + state: "ConversationState", + reply_text: str, +) -> "AgentResponse": + from core.pydantic_ai_agent import AgentResponse, TRANSFER_MESSAGE + + try: + from utils.content_filter import should_block_reply + + blocked, fallback = should_block_reply(reply_text) + if blocked: + print("[Agent] 敏感词拦截,使用兜底回复") + reply_text = fallback or "好的,您稍等,我帮您确认一下" + except Exception: + pass + + try: + from utils.api_cost_tracker import record + + record("openai_chat", count=1) + except Exception: + pass + + agent._detect_price(reply_text, state) + agent._detect_discount(message.msg, state) + asyncio.create_task(agent._auto_tag(message, reply_text, state)) + + need_transfer = False + transfer_msg = "" + transfer_keywords = ["TRANSFER_REQUESTED", "[转移会话]", "转移会话", "转人工", "转接"] + if reply_text and any(kw in reply_text for kw in transfer_keywords): + need_transfer = True + transfer_msg = TRANSFER_MESSAGE + metrics_emit("transfer_to_human", customer_id=message.from_id, acc_id=message.acc_id) + + evo_hit = agent._evolution_enabled_for_customer(message.from_id) + if evo_hit and agent._is_service_risk_inquiry(message.msg): + if agent._evolution_has_proposal("policy-risk-transfer"): + need_transfer = True + transfer_msg = TRANSFER_MESSAGE + metrics_emit("evolution_force_transfer", customer_id=message.from_id, acc_id=message.acc_id) + if agent._evolution_has_proposal("tone-empathy-pack"): + reply_text = "抱歉让您不舒服了,这边先为您转接人工专员马上处理。" + metrics_emit("evolution_empathy_reply", customer_id=message.from_id, acc_id=message.acc_id) + + customer_text, _ = agent._split_customer_text(message.msg) + no_convert_keywords = ["算了", "不要了", "不做了", "下次再说", "先不弄了"] + if customer_text and state.last_price and state.last_price > 0: + if any(kw in customer_text for kw in no_convert_keywords): + reason = "嫌贵放弃" if any(k in customer_text for k in ["贵", "贵了", "便宜"]) else "放弃" + asyncio.create_task( + agent._record_deal_fail(message.from_id, message.from_name, message.acc_id, message.acc_type, reason) + ) + + should_reply = bool(reply_text and reply_text.strip()) and not need_transfer + if evo_hit and need_transfer and agent._evolution_has_proposal("tone-empathy-pack"): + should_reply = True + + if should_reply: + reply_text = await agent._rewrite_reply_with_ai( + message=message, + state=state, + reply=reply_text, + scene="final_reply", + ) + + if should_reply: + state.last_reply_at = datetime.now() + print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {reply_text}") + else: + print(f"{agent.C_MUTED}[REPLY->CUSTOMER]{agent.C_RESET} <静默/不发送>") + + agent._activity_log( + "agent_outbound_decision", + customer_id=message.from_id, + should_reply=should_reply, + need_transfer=need_transfer, + reply=reply_text or "", + transfer_msg=transfer_msg, + ) + + return AgentResponse( + reply=reply_text or "", + should_reply=should_reply, + need_transfer=need_transfer, + transfer_msg=transfer_msg, + )