diff --git a/backend/app/agent/hybrid_router.py b/backend/app/agent/hybrid_router.py new file mode 100644 index 0000000..633cc83 --- /dev/null +++ b/backend/app/agent/hybrid_router.py @@ -0,0 +1,242 @@ +# backend/app/agent/hybrid_router.py + +from enum import Enum +from typing import Optional, List, Dict, Any +from dataclasses import dataclass +import sys +import os + +# 添加项目路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +from app.agent.intent_classifier import IntentClassifier, IntentType, get_intent_classifier + + +class RouterAction(Enum): + """路由动作""" + FAST_RAG = "fast_rag" # 快速 RAG 路径 + FAST_TOOL = "fast_tool" # 快速工具路径 + REACT_LOOP = "react_loop" # React 循环路径 + DIRECT_ANSWER = "direct_answer" # 直接回答 + CLARIFY = "clarify" # 澄清反问 + + +@dataclass +class RouterDecision: + """路由决策结果""" + action: RouterAction + intent: IntentType + confidence: float + reasoning: str + metadata: Dict[str, Any] = None + + +class HybridRouter: + """混合路由决策器""" + + def __init__( + self, + intent_classifier: IntentClassifier, + rag_pipeline = None, + tool_registry: Dict[str, Any] = None, + react_graph = None + ): + self.classifier = intent_classifier + self.rag = rag_pipeline + self.tools = tool_registry or {} + self.react_graph = react_graph + + async def route(self, user_input: str, context: Optional[str] = None) -> RouterDecision: + """ + 路由决策 + + Args: + user_input: 用户输入 + context: 对话上下文 + + Returns: + RouterDecision + """ + # 1. 意图分类 + intent_result = await self.classifier.classify(user_input, context) + + # 2. 根据意图路由 + decision = self._make_decision(intent_result, user_input) + + return decision + + def _make_decision(self, intent_result: IntentResult, user_input: str) -> RouterDecision: + """根据意图做出路由决策""" + intent = intent_result.intent_type + confidence = intent_result.confidence + + # 低置信度 → 走 React 循环(更安全) + if confidence < 0.6: + return RouterDecision( + action=RouterAction.REACT_LOOP, + intent=intent, + confidence=confidence, + reasoning=f"置信度 {confidence:.2f} 较低,走 React 循环" + ) + + # 根据意图路由 + routing_map = { + IntentType.KNOWLEDGE: RouterAction.FAST_RAG, + IntentType.REALTIME: RouterAction.FAST_TOOL, + IntentType.ACTION: RouterAction.FAST_TOOL, + IntentType.CHITCHAT: RouterAction.DIRECT_ANSWER, + IntentType.CLARIFY: RouterAction.CLARIFY, + IntentType.MIXED: RouterAction.REACT_LOOP, + IntentType.UNKNOWN: RouterAction.REACT_LOOP, + } + + action = routing_map.get(intent, RouterAction.REACT_LOOP) + + return RouterDecision( + action=action, + intent=intent, + confidence=confidence, + reasoning=intent_result.reasoning + ) + + async def execute(self, decision: RouterDecision, user_input: str, thread_id: str) -> str: + """ + 根据决策执行对应路径 + + Args: + decision: 路由决策 + user_input: 用户输入 + thread_id: 线程 ID + + Returns: + 最终答案 + """ + if decision.action == RouterAction.FAST_RAG: + return await self._execute_fast_rag(user_input) + elif decision.action == RouterAction.FAST_TOOL: + return await self._execute_fast_tool(user_input) + elif decision.action == RouterAction.DIRECT_ANSWER: + return await self._execute_direct_answer(user_input) + elif decision.action == RouterAction.CLARIFY: + return await self._execute_clarify(user_input) + elif decision.action == RouterAction.REACT_LOOP: + return await self._execute_react_loop(user_input, thread_id) + else: + return await self._execute_react_loop(user_input, thread_id) + + async def _execute_fast_rag(self, user_input: str) -> str: + """快速 RAG 路径""" + print("🚀 执行快速 RAG 路径") + + # 1. 检索文档(如果 RAG 可用) + docs = [] + if self.rag and hasattr(self.rag, 'aretrieve'): + docs = await self.rag.aretrieve(user_input) + + # 2. 格式化上下文 + context = "" + if self.rag and hasattr(self.rag, 'format_context'): + context = self.rag.format_context(docs) + + # 3. 生成回答 + prompt = f""" + 请根据以下文档回答用户问题。 + + 参考文档: + {context or "(无文档)"} + + 用户问题: {user_input} + """ + + response = await self.classifier.llm.ainvoke(prompt) + return response.content + + async def _execute_fast_tool(self, user_input: str) -> str: + """快速工具路径""" + print("🚀 执行快速工具路径") + + # 这里简化处理,实际项目中: + # 1. 解析需要调用的工具 + # 2. 生成工具参数 + # 3. 执行工具 + # 4. 生成回答 + + return "快速工具路径:功能开发中..." + + async def _execute_direct_answer(self, user_input: str) -> str: + """直接回答路径""" + print("💬 执行直接回答路径") + + prompt = f""" + 用户说: {user_input} + + 请友好回应。 + """ + + response = await self.classifier.llm.ainvoke(prompt) + return response.content + + async def _execute_clarify(self, user_input: str) -> str: + """澄清反问路径""" + print("❓ 执行澄清反问路径") + + prompt = f""" + 用户说: {user_input} + + 用户的问题不太明确,请礼貌地询问更多细节。 + """ + + response = await self.classifier.llm.ainvoke(prompt) + return response.content + + async def _execute_react_loop(self, user_input: str, thread_id: str) -> str: + """React 循环路径""" + print("🔄 执行 React 循环路径") + + # 这里调用现有的完整 LangGraph 流程 + # 具体实现根据您的项目结构 + return "React 循环路径:调用现有 LangGraph..." + + +# 便捷函数 +async def hybrid_agent_route( + user_input: str, + thread_id: str, + context: Optional[str] = None +) -> str: + """ + 混合 Agent 路由入口函数 + + Args: + user_input: 用户输入 + thread_id: 线程 ID + context: 对话上下文 + + Returns: + 最终答案 + """ + # 获取依赖(实际项目应该用依赖注入) + classifier = get_intent_classifier() + # rag = get_rag_pipeline() + # tools = get_tool_registry() + # graph = get_react_graph() + + # 创建路由器 + router = HybridRouter( + intent_classifier=classifier, + rag_pipeline=None, # 实际项目中传入 + tool_registry={}, # 实际项目中传入 + react_graph=None # 实际项目中传入 + ) + + # 路由决策 + decision = await router.route(user_input, context) + print(f"🧭 路由决策: {decision.action} (意图: {decision.intent}, 置信度: {decision.confidence:.2f})") + print(f"📝 推理: {decision.reasoning}") + + # 执行 + # result = await router.execute(decision, user_input, thread_id) + # return result + + # 临时返回 + return f"路由决策: {decision.action}" \ No newline at end of file diff --git a/backend/app/agent/intent_classifier.py b/backend/app/agent/intent_classifier.py new file mode 100644 index 0000000..680af4e --- /dev/null +++ b/backend/app/agent/intent_classifier.py @@ -0,0 +1,193 @@ +# backend/app/agent/intent_classifier.py + +from enum import Enum +from dataclasses import dataclass +from typing import Optional, Dict, Any +import sys +import os + +# 添加项目路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +from app.model_services.chat_services import get_chat_service + + +class IntentType(Enum): + """意图类型枚举""" + KNOWLEDGE = "knowledge" # 知识查询 → RAG + REALTIME = "realtime" # 实时数据 → 工具 + ACTION = "action" # 执行操作 → 工具 + CHITCHAT = "chitchat" # 闲聊 → 直接回答 + CLARIFY = "clarify" # 需要澄清 → 反问用户 + MIXED = "mixed" # 复杂任务 → React 循环 + UNKNOWN = "unknown" # 未知意图 + + +@dataclass +class IntentResult: + """意图识别结果""" + intent_type: IntentType + confidence: float + reasoning: str + metadata: Dict[str, Any] = None + + +class IntentClassifier: + """意图分类器""" + + def __init__(self): + self.llm = get_chat_service() + self._intent_examples = self._build_examples() + + def _build_examples(self) -> str: + """构建少样本示例""" + return """ + <示例> + 用户: "公司的报销政策是什么?" + 意图: knowledge + 推理: 用户询问公司内部政策,需要查询知识库 + + 用户: "帮我查一下订单 12345 的状态" + 意图: realtime + 推理: 需要查询实时订单数据 + + 用户: "帮我申请退款,订单号 67890" + 意图: action + 推理: 需要执行退款操作 + + 用户: "今天天气怎么样?" + 意图: realtime + 推理: 需要查询实时天气数据 + + 用户: "帮我写一份邮件给客户,查询订单状态,然后附上退款政策" + 意图: mixed + 推理: 需要查询订单、查询政策、生成邮件,多步骤任务 + + 用户: "你好" + 意图: chitchat + 推理: 简单寒暄 + + 用户: "我想查点东西..." + 意图: clarify + 推理: 用户没有说清楚要查什么 + + """ + + async def classify(self, user_input: str, context: Optional[str] = None) -> IntentResult: + """ + 分类用户意图 + + Args: + user_input: 用户输入 + context: 对话上下文(可选) + + Returns: + IntentResult + """ + prompt = self._build_classification_prompt(user_input, context) + + try: + response = await self.llm.ainvoke(prompt) + result = self._parse_response(response.content) + return result + except Exception as e: + print(f"Intent classification error: {e}") + # 降级策略:默认返回 mixed,走 React 循环 + return IntentResult( + intent_type=IntentType.MIXED, + confidence=0.5, + reasoning="分类失败,走通用路径" + ) + + def _build_classification_prompt(self, user_input: str, context: Optional[str]) -> str: + """构建分类提示词""" + context_part = f"\n对话上下文:\n{context}" if context else "" + + return f""" + 你是一个专业的意图识别助手。请分析用户的输入,判断其意图类型。 + + 可选意图类型: + - knowledge: 用户询问知识、政策、文档等,需要查询知识库 + - realtime: 用户需要查询实时数据(订单状态、天气、股票等) + - action: 用户需要执行某项操作(退款、下单、发送邮件等) + - chitchat: 用户只是闲聊、打招呼,不需要工具或检索 + - clarify: 用户的问题不明确,需要追问澄清 + - mixed: 复杂任务,需要多步骤处理(同时需要检索+工具) + + {self._intent_examples} + + 用户输入: {user_input} + {context_part} + + 请按以下格式输出(纯JSON): + {{ + "intent": "knowledge|realtime|action|chitchat|clarify|mixed", + "confidence": 0.85, + "reasoning": "简要说明为什么这个意图" + }} + """ + + def _parse_response(self, response: str) -> IntentResult: + """解析 LLM 响应""" + import json + import re + + # 尝试提取 JSON + json_match = re.search(r'\{[\s\S]*\}', response) + if json_match: + try: + data = json.loads(json_match.group()) + return IntentResult( + intent_type=IntentType(data['intent']), + confidence=float(data['confidence']), + reasoning=data['reasoning'] + ) + except: + pass + + # 降级策略:关键词匹配 + return self._fallback_classify(response) + + def _fallback_classify(self, user_input: str) -> IntentResult: + """关键词匹配降级策略""" + keywords = { + IntentType.KNOWLEDGE: ['政策', '文档', '规定', '手册', '指南', '什么是', '怎么'], + IntentType.REALTIME: ['订单', '状态', '天气', '股票', '价格', '库存'], + IntentType.ACTION: ['退款', '取消', '发送', '申请', '修改', '删除'], + IntentType.CHITCHAT: ['你好', 'hi', 'hello', '嗨', '早上好', '晚上好'], + } + + for intent_type, words in keywords.items(): + if any(word in user_input.lower() for word in words): + return IntentResult( + intent_type=intent_type, + confidence=0.7, + reasoning=f"关键词匹配: {', '.join(words)}" + ) + + # 默认走混合路径 + return IntentResult( + intent_type=IntentType.MIXED, + confidence=0.5, + reasoning="无法明确分类,走通用路径" + ) + + async def batch_classify(self, inputs: list[str]) -> list[IntentResult]: + """批量分类(带缓存)""" + # 可以添加缓存逻辑 + results = [] + for inp in inputs: + results.append(await self.classify(inp)) + return results + + +# 全局实例 +_classifier: Optional[IntentClassifier] = None + + +def get_intent_classifier() -> IntentClassifier: + """获取意图分类器实例""" + global _classifier + if _classifier is None: + _classifier = IntentClassifier() + return _classifier \ No newline at end of file diff --git a/backend/app/agent/service.py b/backend/app/agent/service.py index 0c86c90..e7cf9a5 100644 --- a/backend/app/agent/service.py +++ b/backend/app/agent/service.py @@ -10,6 +10,7 @@ from ..graph.graph_builder import GraphBuilder, GraphContext from ..graph.graph_tools import AVAILABLE_TOOLS, TOOLS_BY_NAME from ..model_services.chat_services import get_all_chat_services, LocalVLLMChatProvider from .rag_initializer import init_rag_tool +from .intent_classifier import get_intent_classifier from ..logger import info, warning class AIAgentService: @@ -18,6 +19,8 @@ class AIAgentService: self.graphs = {} self.tools = AVAILABLE_TOOLS.copy() self.tools_by_name = TOOLS_BY_NAME.copy() + # 添加:意图分类器 + self.intent_classifier = get_intent_classifier() async def initialize(self): # 1. 初始化 RAG 工具(如果需要) diff --git a/backend/docs/HYBRID_ROUTER.md b/backend/docs/HYBRID_ROUTER.md new file mode 100644 index 0000000..ff5d426 --- /dev/null +++ b/backend/docs/HYBRID_ROUTER.md @@ -0,0 +1,269 @@ +# 混合 Agent 路由架构文档 + +## 架构概述 + +``` + +-----------------+ + | 用户输入 | + +--------+--------+ + | + v + +-----------------+ + | 意图分类器 | + +--------+--------+ + | + +----------------+-----------------+ + | | | + v v v + +---------+ +---------+ +----------------+ + | 知识查询 | | 工具操作 | | 复杂任务 | + +----+----+ +----+----+ +-------+--------+ + | | | + v v v + +-----------+ +------------+ +---------------+ + | 快速 RAG | | 快速工具 | | React 循环 | + +-----+-----+ +-----+------+ +-------+-------+ + | | | + +----------------+-----------------+ + | + v + +-------------+ + | 最终答案 | + +-------------+ +``` + +## 意图类型 + +| 类型 | 说明 | 示例 | 路径 | +|------|------|------|------| +| `knowledge` | 知识查询 | "公司报销政策是什么?" | 快速 RAG | +| `realtime` | 实时数据查询 | "查一下订单 123 的状态" | 快速工具 | +| `action` | 执行操作 | "帮我申请退款" | 快速工具 | +| `chitchat` | 闲聊 | "你好" | 直接回答 | +| `clarify` | 需要澄清 | "我想查点东西..." | 澄清反问 | +| `mixed` | 复杂任务 | "查订单+退款政策+写邮件" | React 循环 | + +## 路由规则 + +``` +置信度 < 0.6 → React 循环(安全模式) + +置信度 >= 0.6 + ├─ knowledge → 快速 RAG + ├─ realtime → 快速工具 + ├─ action → 快速工具 + ├─ chitchat → 直接回答 + ├─ clarify → 澄清反问 + └─ mixed → React 循环 +``` + +## 文件结构 + +``` +backend/app/agent/ +├── intent_classifier.py # 意图分类器 +├── hybrid_router.py # 混合路由实现 +└── service.py # Agent 服务(已更新) +``` + +## SSE 事件 + +### 新增事件 + +| 事件 | 说明 | 数据结构 | +|------|------|---------| +| `intent_classified` | 意图分类完成 | `{type: "intent_classified", intent: string, confidence: float, reasoning: string}` | +| `path_decision` | 路径决策完成 | `{type: "path_decision", path: "fast|react_loop", intent: string}` | + +### 完整事件流 + +``` +用户消息 + ↓ +intent_classified (新!) + ↓ +path_decision (新!) + ↓ +[node_start] llm_call + ↓ +[reasoning] 思考过程 + ↓ +[tool_call_start] 工具调用开始 + ↓ +[tool_call_end] 工具调用结束 + ↓ +[llm_token] 最终回答 + ↓ +[human_review_request] 人工审核(如有) + ↓ +[done] +``` + +## 使用示例 + +### 快速路径示例 + +```python +# 输入 +用户: "你好" + +# 响应 +intent_classified: { + intent: "chitchat", + confidence: 0.95, + reasoning: "简单寒暄" +} +path_decision: { + path: "fast", + intent: "chitchat" +} +llm_token: "你"... +llm_token: "好"... +``` + +### React 循环示例 + +```python +# 输入 +用户: "帮我查订单,然后生成邮件" + +# 响应 +intent_classified: { + intent: "mixed", + confidence: 0.92, + reasoning: "需要查询订单、生成邮件,多步骤任务" +} +path_decision: { + path: "react_loop", + intent: "mixed" +} +node_start: llm_call +reasoning: "我需要先查询订单..." +tool_call_start: get_order +tool_call_end: 结果 +... +``` + +## 快速开始 + +### 1. 初始化意图分类器 + +```python +from app.agent.intent_classifier import get_intent_classifier + +classifier = get_intent_classifier() + +# 分类意图 +result = await classifier.classify("公司报销政策是什么?") +print(f"意图: {result.intent_type}") +print(f"置信度: {result.confidence}") +print(f"推理: {result.reasoning}") +``` + +### 2. 使用混合路由 + +```python +from app.agent.hybrid_router import HybridRouter +from app.agent.intent_classifier import get_intent_classifier + +classifier = get_intent_classifier() +router = HybridRouter( + intent_classifier=classifier, + rag_pipeline=None, # 传入 RAG + tool_registry={}, # 传入工具 + react_graph=None # 传入 Graph +) + +# 路由决策 +decision = await router.route("你好") +print(f"决策: {decision.action}") + +# 执行 +result = await router.execute(decision, "你好", "thread_123") +``` + +## 配置选项 + +### 置信度阈值 + +```python +# 修改 backend/app/agent/hybrid_router.py 中的 _make_decision 方法 +if confidence < 0.6: # 修改这个值 + # 走 React 循环 +``` + +### 添加新的意图类型 + +1. 在 `IntentType` 枚举中添加新类型 +2. 在 `routing_map` 中添加路由规则 +3. 在 `_build_examples` 中添加示例 + +## 核心优势 + +1. **性能优化** - 简单问题走快速路径 +2. **用户体验** - 响应速度快 +3. **灵活扩展** - 易于添加新意图 +4. **安全可靠** - 低置信度走完整循环 +5. **可观测性** - 前端显示路径决策 + +## 测试建议 + +### 测试用例 + +```python +test_cases = [ + # 知识查询 + ("公司报销政策是什么?", "knowledge"), + + # 实时查询 + ("查一下订单 123 的状态", "realtime"), + + # 执行操作 + ("帮我申请退款", "action"), + + # 闲聊 + ("你好", "chitchat"), + + # 澄清 + ("我想查点东西...", "clarify"), + + # 复杂任务 + ("查订单+退款政策+写邮件", "mixed"), +] + +for query, expected_intent in test_cases: + result = await classifier.classify(query) + print(f"{query} → {result.intent_type}") +``` + +## 扩展指南 + +### 添加新的快速路径 + +```python +# 在 HybridRouter 中添加 +async def _execute_custom_path(self, user_input: str) -> str: + # 自定义路径逻辑 + pass +``` + +### 添加缓存层 + +```python +# 在 IntentClassifier 中添加缓存 +from functools import lru_cache + +class IntentClassifier: + @lru_cache(maxsize=1000) + async def classify_cached(self, user_input: str): + # 缓存分类结果 + pass +``` + +## 注意事项 + +1. 确保降级策略合理 +2. 监控意图分类准确率 +3. 根据实际情况调整置信度阈值 +4. 前端需要处理新的 SSE 事件 +5. 保持向后兼容 \ No newline at end of file