refactor: migrate workflow to v2 core and archive legacy modules
This commit is contained in:
182
legacy/ai_reply_flow.py
Normal file
182
legacy/ai_reply_flow.py
Normal file
@@ -0,0 +1,182 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Optional, Tuple
|
||||
from core.post_ops import negotiation_strategy_reply
|
||||
|
||||
logger = logging.getLogger("cs_agent")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.pydantic_ai_agent import AgentDeps, ConversationState, CustomerMessage, CustomerServiceAgent
|
||||
|
||||
|
||||
def _select_agent_by_intent(
|
||||
agent: "CustomerServiceAgent",
|
||||
message: "CustomerMessage",
|
||||
state: "ConversationState",
|
||||
) -> Tuple[Optional[Any], str]:
|
||||
"""
|
||||
AI 意图优先路由;识别不到时返回 (None, "intent:none"),由关键词兜底。
|
||||
"""
|
||||
try:
|
||||
from utils.intent_analyzer import detect_intent
|
||||
|
||||
decision = detect_intent(message.msg or "")
|
||||
intent = (decision.intent or "").strip()
|
||||
source = decision.source or "none"
|
||||
score = float(decision.score or 0.0)
|
||||
except Exception:
|
||||
intent, source, score = "", "error", 0.0
|
||||
|
||||
if not intent:
|
||||
return None, "intent:none"
|
||||
|
||||
if intent in ("询价", "砍价"):
|
||||
return agent.agent_pricing, f"intent:{intent}|src:{source}|score:{score:.3f}"
|
||||
if intent in ("修改", "加急"):
|
||||
return agent.agent_processing, f"intent:{intent}|src:{source}|score:{score:.3f}"
|
||||
if intent == "售后":
|
||||
return agent.agent_after_sale, f"intent:{intent}|src:{source}|score:{score:.3f}"
|
||||
if intent == "转接":
|
||||
return agent.agent_after_sale, f"intent:{intent}|src:{source}|score:{score:.3f}"
|
||||
if intent in ("打招呼", "批量", "发图"):
|
||||
target = agent.agent_after_sale if state.stage == "售后" else agent.agent
|
||||
return target, f"intent:{intent}|src:{source}|score:{score:.3f}"
|
||||
|
||||
return None, f"intent:unmapped:{intent}|src:{source}|score:{score:.3f}"
|
||||
|
||||
|
||||
def select_target_agent(agent: "CustomerServiceAgent", message: "CustomerMessage", state: "ConversationState") -> Tuple[Any, str]:
|
||||
msg_lower = message.msg.lower()
|
||||
pricing_kw = ["多少钱", "多少一张", "报价", "给个价", "几块", "价位", "能便宜点吗"]
|
||||
processing_kw = ["安排", "处理一下", "开始做", "做一下", "尽快", "加急", "付款了", "已付款"]
|
||||
similar_kw = ["有一样的", "有一样吗", "一样的吗", "类似的", "类似的吗", "同款", "相似", "类似吗"]
|
||||
order_markers = ["[系统订单信息]", "订单状态", "买家已付款"]
|
||||
risk_kw = [
|
||||
"黄色",
|
||||
"擦边",
|
||||
"色情",
|
||||
"涉黄",
|
||||
"涉政",
|
||||
"政治",
|
||||
"裸",
|
||||
"不雅",
|
||||
"天安门",
|
||||
"政治人物",
|
||||
"政治事件",
|
||||
"领导人",
|
||||
"党政",
|
||||
"习近平",
|
||||
"毛泽东",
|
||||
"邓小平",
|
||||
"江泽民",
|
||||
"胡锦涛",
|
||||
"特朗普",
|
||||
"拜登",
|
||||
"普京",
|
||||
"泽连斯基",
|
||||
"地图",
|
||||
"地形图",
|
||||
"行政区划图",
|
||||
"卫星地图",
|
||||
]
|
||||
target_agent = agent.agent_after_sale if state.stage == "售后" else agent.agent
|
||||
|
||||
ai_target, ai_reason = _select_agent_by_intent(agent, message, state)
|
||||
if ai_target is not None:
|
||||
return ai_target, ai_reason
|
||||
|
||||
risk_hit = any(k in msg_lower for k in risk_kw) or agent._is_political_inquiry(message.msg) or agent._is_map_inquiry(message.msg)
|
||||
if risk_hit:
|
||||
return agent.agent_risk, "keyword:risk"
|
||||
if any(k in message.msg for k in order_markers):
|
||||
return agent.agent_order, "keyword:order"
|
||||
if any(k in msg_lower for k in processing_kw):
|
||||
return agent.agent_processing, "keyword:processing"
|
||||
if any(k in msg_lower for k in pricing_kw):
|
||||
return agent.agent_pricing, "keyword:pricing"
|
||||
if any(k in msg_lower for k in similar_kw):
|
||||
return agent.agent_similar, "keyword:similar"
|
||||
return target_agent, "fallback:default"
|
||||
|
||||
|
||||
async def execute_ai_turn(
|
||||
agent: "CustomerServiceAgent",
|
||||
*,
|
||||
message: "CustomerMessage",
|
||||
state: "ConversationState",
|
||||
user_prompt: str,
|
||||
deps: "AgentDeps",
|
||||
history: list,
|
||||
) -> str:
|
||||
target_agent, route_reason = select_target_agent(agent, message, state)
|
||||
logger.info("[路由] %s", route_reason)
|
||||
result = await target_agent.run(user_prompt, deps=deps, message_history=history)
|
||||
agent.message_histories[message.from_id] = result.all_messages()[-30:]
|
||||
reply_text = agent._colloquialize_reply(agent._normalize_reply_text(result.output))
|
||||
|
||||
strategy_reply = negotiation_strategy_reply(message.msg, state)
|
||||
if strategy_reply:
|
||||
reply_text = strategy_reply
|
||||
|
||||
try:
|
||||
from config.config import MIN_PRICE_FLOOR
|
||||
import re
|
||||
|
||||
offer = None
|
||||
m = re.search(r"(\d{1,4})\s*(?:元|块|块钱|元钱)\b", message.msg)
|
||||
if m:
|
||||
offer = int(m.group(1))
|
||||
else:
|
||||
m2 = re.search(r"(?:能|可以|可否|能否)\s*(\d{1,4})\b", message.msg)
|
||||
offer = int(m2.group(1)) if m2 else None
|
||||
st = agent._get_conversation_state(message.from_id)
|
||||
floor = st.last_min_price if isinstance(st.last_min_price, int) and st.last_min_price > 0 else MIN_PRICE_FLOOR
|
||||
if offer is not None and offer < floor:
|
||||
reply_text = "不好意思"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
from config.config import MIN_PRICE_FLOOR
|
||||
import re
|
||||
|
||||
st = agent._get_conversation_state(message.from_id)
|
||||
floor = st.last_min_price if isinstance(st.last_min_price, int) and st.last_min_price > 0 else MIN_PRICE_FLOOR
|
||||
|
||||
def _adjust(text: str) -> str:
|
||||
def _repl(m: Any):
|
||||
num = int(m.group(1))
|
||||
adj = max(floor, round(num / 5) * 5)
|
||||
return m.group(0).replace(str(num), str(adj))
|
||||
|
||||
patterns = [
|
||||
r"按(\d{1,4})元",
|
||||
r"报价[::]\s*(\d{1,4})\s*元",
|
||||
r"(\d{1,4})\s*元一张",
|
||||
r"打包(\d{1,4})\s*元",
|
||||
]
|
||||
t = text
|
||||
for p in patterns:
|
||||
t = re.sub(p, _repl, t)
|
||||
return t
|
||||
|
||||
reply_text = _adjust(reply_text or "")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for msg in result.new_messages():
|
||||
for part in getattr(msg, "parts", []):
|
||||
part_type = type(part).__name__
|
||||
if "ToolCall" in part_type:
|
||||
logger.info(
|
||||
"[THINK/TOOL_CALL] %s(%s)",
|
||||
getattr(part, "tool_name", ""),
|
||||
getattr(part, "args", ""),
|
||||
)
|
||||
elif "ToolReturn" in part_type:
|
||||
ret = str(getattr(part, "content", ""))[:120]
|
||||
logger.info("[THINK/TOOL_RETURN] %s", ret)
|
||||
|
||||
logger.info("[THINK/RAW_OUTPUT] %r", reply_text)
|
||||
return reply_text
|
||||
Reference in New Issue
Block a user