Files
assistant/agent/agents/router.py

221 lines
6.8 KiB
Python
Raw Normal View History

"""
Router Agent - Intent recognition and routing
"""
import json
from typing import Any, Optional
from core.state import AgentState, Intent, ConversationState, set_intent, add_entity
from core.llm import get_llm_client, Message
from utils.logger import get_logger
logger = get_logger(__name__)
# Intent classification prompt
CLASSIFICATION_PROMPT = """你是一个 B2B 购物网站的智能助手路由器。
你的任务是分析用户消息识别用户意图并提取关键实体
## 可用意图分类
1. **customer_service** - 通用咨询
- FAQ 问答
- 产品使用问题
- 公司信息查询
- 政策咨询退换货政策隐私政策等
2. **order** - 订单相关
- 订单查询"我的订单在哪""查一下订单"
- 物流跟踪"快递到哪了""什么时候到货"
- 订单修改"改一下收货地址""修改订单数量"
- 订单取消"取消订单""不想要了"
- 发票查询"开发票""要发票"
3. **aftersale** - 售后服务
- 退货申请"退货""不满意想退"
- 换货申请"换货""换一个"
- 投诉"投诉""服务态度差"
- 工单/问题反馈
4. **product** - 商品相关
- 商品搜索"有没有xx""找一下xx"
- 商品推荐"推荐""有什么好的"
- 询价"多少钱""批发价""大量购买价格"
- 库存查询"有货吗""还有多少"
5. **human_handoff** - 需要转人工
- 用户明确要求转人工
- 复杂问题 AI 无法处理
- 敏感问题需要人工处理
## 实体提取
请从消息中提取以下实体如果存在
- order_id: 订单号 ORD123456
- product_id: 商品ID
- product_name: 商品名称
- quantity: 数量
- date_reference: 时间引用今天昨天上周具体日期等
- tracking_number: 物流单号
- phone: 电话号码
- address: 地址信息
## 输出格式
请以 JSON 格式返回包含以下字段
```json
{
"intent": "意图分类",
"confidence": 0.95,
"sub_intent": "子意图(可选)",
"entities": {
"entity_type": "entity_value"
},
"reasoning": "简短的推理说明"
}
```
## 注意事项
- 如果意图不明确置信度应该较低
- 如果无法确定意图返回 "unknown"
- 实体提取要准确没有的字段不要填写
"""
async def classify_intent(state: AgentState) -> AgentState:
"""Classify user intent and extract entities
This is the first node in the workflow that analyzes the user's message
and determines which agent should handle it.
Args:
state: Current agent state
Returns:
Updated state with intent and entities
"""
logger.info(
"Classifying intent",
conversation_id=state["conversation_id"],
message=state["current_message"][:100]
)
state["state"] = ConversationState.CLASSIFYING.value
state["step_count"] += 1
# Build context from conversation history
context_summary = ""
if state["context"]:
context_parts = []
if state["context"].get("order_id"):
context_parts.append(f"当前讨论的订单: {state['context']['order_id']}")
if state["context"].get("product_id"):
context_parts.append(f"当前讨论的商品: {state['context']['product_id']}")
if context_parts:
context_summary = "\n".join(context_parts)
# Build messages for LLM
messages = [
Message(role="system", content=CLASSIFICATION_PROMPT),
]
# Add recent conversation history for context
for msg in state["messages"][-6:]: # Last 3 turns
messages.append(Message(role=msg["role"], content=msg["content"]))
# Add current message with context
user_content = f"用户消息: {state['current_message']}"
if context_summary:
user_content += f"\n\n当前上下文:\n{context_summary}"
messages.append(Message(role="user", content=user_content))
try:
llm = get_llm_client()
response = await llm.chat(messages, temperature=0.3)
# Parse JSON response
content = response.content.strip()
# Handle markdown code blocks
if content.startswith("```"):
content = content.split("```")[1]
if content.startswith("json"):
content = content[4:]
result = json.loads(content)
# Extract intent
intent_str = result.get("intent", "unknown")
try:
intent = Intent(intent_str)
except ValueError:
intent = Intent.UNKNOWN
confidence = float(result.get("confidence", 0.5))
sub_intent = result.get("sub_intent")
# Set intent in state
state = set_intent(state, intent, confidence, sub_intent)
# Extract entities
entities = result.get("entities", {})
for entity_type, entity_value in entities.items():
if entity_value:
state = add_entity(state, entity_type, entity_value)
logger.info(
"Intent classified",
intent=intent.value,
confidence=confidence,
entities=list(entities.keys())
)
# Check if human handoff is needed
if intent == Intent.HUMAN_HANDOFF or confidence < 0.5:
state["requires_human"] = True
state["handoff_reason"] = result.get("reasoning", "Intent unclear")
return state
except json.JSONDecodeError as e:
logger.error("Failed to parse intent response", error=str(e))
state["intent"] = Intent.UNKNOWN.value
state["intent_confidence"] = 0.0
return state
except Exception as e:
logger.error("Intent classification failed", error=str(e))
state["error"] = str(e)
state["intent"] = Intent.UNKNOWN.value
return state
def route_by_intent(state: AgentState) -> str:
"""Route to appropriate agent based on intent
This is the routing function used by conditional edges in the graph.
Args:
state: Current agent state
Returns:
Name of the next node to execute
"""
intent = state.get("intent")
requires_human = state.get("requires_human", False)
# Human handoff takes priority
if requires_human:
return "human_handoff"
# Route based on intent
routing_map = {
Intent.CUSTOMER_SERVICE.value: "customer_service_agent",
Intent.ORDER.value: "order_agent",
Intent.AFTERSALE.value: "aftersale_agent",
Intent.PRODUCT.value: "product_agent",
Intent.HUMAN_HANDOFF.value: "human_handoff",
Intent.UNKNOWN.value: "customer_service_agent" # Default to customer service
}
return routing_map.get(intent, "customer_service_agent")