""" 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")