"""Master Agent 路由器 - 让 Master Agent 真正成为决策中心""" import json import re import uuid from typing import Dict, Any, List, Optional, Tuple from sqlalchemy.orm import Session from app.schemas.app_schema import ModelParameters from app.services.conversation_state_manager import ConversationStateManager from app.models import ModelConfig, AgentConfig from app.core.logging_config import get_business_logger from app.services.model_service import ModelApiKeyService logger = get_business_logger() class MasterAgentRouter: """Master Agent 路由器 让 Master Agent 作为"大脑",负责: 1. 分析用户意图 2. 选择最合适的 Sub Agent 3. 决定是否需要多 Agent 协作 4. 管理会话上下文 优势: - 更智能的决策(基于完整上下文) - 减少 LLM 调用次数 - 架构更清晰(Master Agent 真正起作用) """ def __init__( self, db: Session, master_model_config: ModelConfig, model_parameters: ModelParameters, sub_agents: Dict[str, Any], state_manager: ConversationStateManager, enable_rule_fast_path: bool = True ): """初始化 Master Agent 路由器 Args: db: 数据库会话 master_model_config: Master Agent 使用的模型配置 sub_agents: 子 Agent 配置字典 state_manager: 会话状态管理器 enable_rule_fast_path: 是否启用规则快速路径(性能优化) """ self.db = db self.master_model_config = master_model_config self.model_parameters = model_parameters self.sub_agents = sub_agents self.state_manager = state_manager self.enable_rule_fast_path = enable_rule_fast_path logger.info( "Master Agent 路由器初始化", extra={ "sub_agent_count": len(sub_agents), "enable_rule_fast_path": enable_rule_fast_path } ) async def route( self, message: str, conversation_id: Optional[str] = None, variables: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """智能路由决策 Args: message: 用户消息 conversation_id: 会话 ID variables: 变量参数 Returns: 路由决策结果 """ logger.info( "开始 Master Agent 路由", extra={ "message_length": len(message), "conversation_id": conversation_id } ) # 1. 获取会话状态 state = None if conversation_id: state = self.state_manager.get_state(conversation_id) # 2. 尝试规则快速路径(可选的性能优化) if self.enable_rule_fast_path: rule_result = self._try_rule_fast_path(message, state) if rule_result: logger.info( "规则快速路径命中", extra={ "agent_id": rule_result["selected_agent_id"], "confidence": rule_result["confidence"] } ) # 更新会话状态 if conversation_id: self.state_manager.update_state( conversation_id, rule_result["selected_agent_id"], message, rule_result.get("topic"), rule_result["confidence"] ) return rule_result # 3. 调用 Master Agent 做决策 decision = await self._master_agent_decide(message, state, variables) # 4. 更新会话状态 if conversation_id: self.state_manager.update_state( conversation_id, decision["selected_agent_id"], message, decision.get("topic"), decision["confidence"] ) logger.info( "Master Agent 路由完成", extra={ "agent_id": decision["selected_agent_id"], "strategy": decision["strategy"], "confidence": decision["confidence"] } ) return decision def _try_rule_fast_path( self, message: str, state: Optional[Dict[str, Any]] ) -> Optional[Dict[str, Any]]: """尝试规则快速路径(性能优化) 对于明确的关键词匹配,直接返回结果,不调用 Master Agent Args: message: 用户消息 state: 会话状态 Returns: 如果命中规则返回决策结果,否则返回 None """ # 定义高置信度关键词规则 high_confidence_rules = [ { "keywords": ["数学", "方程", "计算", "求解"], "agent_role": "数学", "confidence_threshold": 0.9 }, { "keywords": ["物理", "力学", "电路", "光学"], "agent_role": "物理", "confidence_threshold": 0.9 }, { "keywords": ["订单", "发货", "物流", "快递"], "agent_role": "订单", "confidence_threshold": 0.9 }, { "keywords": ["退款", "退货", "售后"], "agent_role": "退款", "confidence_threshold": 0.9 } ] message_lower = message.lower() for rule in high_confidence_rules: matched_keywords = [kw for kw in rule["keywords"] if kw in message_lower] if matched_keywords: confidence = len(matched_keywords) / len(rule["keywords"]) if confidence >= rule["confidence_threshold"]: # 查找对应的 agent for agent_id, agent_data in self.sub_agents.items(): agent_info = agent_data.get("info", {}) if agent_info.get("role") == rule["agent_role"]: return { "selected_agent_id": agent_id, "confidence": confidence, "strategy": "rule_fast_path", "reasoning": f"关键词匹配: {', '.join(matched_keywords)}", "topic": rule["agent_role"], "need_collaboration": False, "routing_method": "rule" } return None async def _master_agent_decide( self, message: str, state: Optional[Dict[str, Any]], variables: Optional[Dict[str, Any]] ) -> Dict[str, Any]: """让 Master Agent 做路由决策 Args: message: 用户消息 state: 会话状态 variables: 变量参数 Returns: 决策结果 """ # 1. 构建决策 prompt prompt = self._build_decision_prompt(message, state, variables) # 2. 调用 Master Agent 的 LLM try: response = await self._call_master_agent_llm(prompt) # 3. 解析决策 decision = self._parse_decision(response) # 4. 验证决策 decision = self._validate_decision(decision) return decision except Exception as e: logger.error(f"Master Agent 决策失败: {str(e)}") # 降级到默认 agent return self._get_fallback_decision(message) def _build_decision_prompt( self, message: str, state: Optional[Dict[str, Any]], variables: Optional[Dict[str, Any]] ) -> str: """构建 Master Agent 的决策 prompt Args: message: 用户消息 state: 会话状态 variables: 变量参数 Returns: prompt 字符串 """ # 1. 构建 Sub Agent 描述(简化版,提升性能) agent_descriptions = [] for agent_id, agent_data in self.sub_agents.items(): agent_info = agent_data.get("info", {}) name = agent_info.get("name", "未命名") role = agent_info.get("role", "") capabilities = agent_info.get("capabilities", []) # 简化格式:一行描述 desc = f"- {agent_id}: {name}" if role: desc += f" ({role})" if capabilities: desc += f" - {', '.join(capabilities[:3])}" # 只取前3个能力 agent_descriptions.append(desc) agents_text = "\n".join(agent_descriptions) # 2. 构建会话上下文 context_text = "" if state: current_agent = state.get("current_agent_id") last_topic = state.get("last_topic") same_turns = state.get("same_agent_turns", 0) if current_agent: context_text = f""" 当前会话上下文: - 当前使用的 Agent: {current_agent} - 上一个主题: {last_topic} - 连续使用轮数: {same_turns} """ # 获取第一个可用的 agent_id 作为示例 example_agent_id = next(iter(self.sub_agents.keys())) if self.sub_agents else "agent_id" # 3. 构建完整 prompt(简化版,提升性能) prompt = f"""路由任务:分析问题并选择合适的 Agent。 可用 Agent: {agents_text} {context_text} 问题:"{message}" 返回 JSON 格式决策: **情况1:单一问题(最常见)** {{"selected_agent_id": "{example_agent_id}", "confidence": 0.9, "need_collaboration": false, "reasoning": "选择理由"}} **情况2:需要拆分成多个独立子问题** 当用户问题包含多个完全独立的子问题时使用(如"写诗+做数学题")。 必须提供 sub_questions 数组,每个子问题必须指定 agent_id: {{"selected_agent_id": "{example_agent_id}", "confidence": 0.9, "need_collaboration": true, "need_decomposition": true, "sub_questions": [ {{"question": "具体的子问题1", "agent_id": "{example_agent_id}", "order": 1, "depends_on": []}}, {{"question": "具体的子问题2", "agent_id": "{example_agent_id}", "order": 2, "depends_on": []}} ], "collaboration_strategy": "decomposition", "reasoning": "问题包含X个独立子问题"}} **情况3:需要多个Agent协作分析同一问题** {{"selected_agent_id": "{example_agent_id}", "confidence": 0.9, "need_collaboration": true, "collaboration_agents": [{{"agent_id": "{example_agent_id}", "role": "primary", "task": "主要任务", "order": 1}}], "collaboration_strategy": "sequential", "reasoning": "需要多角度分析"}} 重要规则: 1. selected_agent_id 必须从上面的可用 Agent 列表中选择 2. 如果选择情况2(拆分),sub_questions 数组不能为空,必须包含具体的子问题 3. 每个子问题的 agent_id 也必须从可用列表中选择 4. depends_on 表示依赖关系(如 [1] 表示依赖第1个子问题的结果) 5. 大多数情况应该选择情况1(单一Agent),只有明确需要时才拆分或协作 6. 只做路由决策,不要回答问题内容 请返回 JSON:""" return prompt async def _call_master_agent_llm(self, prompt: str) -> str: """调用 Master Agent 的 LLM Args: prompt: 提示词 Returns: LLM 响应 """ try: from app.core.models import RedBearLLM from app.core.models.base import RedBearModelConfig from app.models import ModelApiKey, ModelType # 获取 API Key 配置 api_key_config = ModelApiKeyService.get_available_api_key(self.db, self.master_model_config.id) if not api_key_config: raise Exception("Master Agent 模型没有可用的 API Key") logger.info( "调用 Master Agent LLM", extra={ "provider": api_key_config.provider, "model_name": api_key_config.model_name } ) # temperature = 0.3 # 决策任务使用较低温度 # max_tokens = 1000 # if self.model_parameters: # temperature = self.model_parameters["temperature"] # max_tokens = self.model_parameters["max_tokens"] if self.model_parameters: if hasattr(self.model_parameters, 'temperature'): # Pydantic 模型 temperature = self.model_parameters.temperature max_tokens = getattr(self.model_parameters, 'max_tokens', 1000) elif isinstance(self.model_parameters, dict): # 字典 temperature = self.model_parameters.get("temperature", 0.3) max_tokens = self.model_parameters.get("max_tokens", 1000) else: temperature = 0.3 max_tokens = 1000 else: temperature = 0.3 max_tokens = 1000 # extra_params = {"temperature": self.model_parameters.get("temperature", 0.3), # "max_tokens":self.model_parameters.get("max_tokens", 1000) # } extra_params = {"temperature": temperature, "max_tokens": max_tokens} # 创建 RedBearModelConfig model_config = RedBearModelConfig( model_name=api_key_config.model_name, provider=api_key_config.provider, api_key=api_key_config.api_key, base_url=api_key_config.api_base, is_omni=api_key_config.is_omni, extra_params = extra_params ) # 创建 LLM 实例 llm = RedBearLLM(model_config, type=ModelType.CHAT) # 调用模型 response = await llm.ainvoke(prompt) ModelApiKeyService.record_api_key_usage(self.db, api_key_config.id) # 提取响应内容 if hasattr(response, 'content'): return response.content else: return str(response) except Exception as e: logger.error(f"Master Agent LLM 调用失败: {str(e)}") raise def _parse_decision(self, response: str) -> Dict[str, Any]: """解析 Master Agent 的决策 Args: response: LLM 响应 Returns: 决策字典 """ try: # 提取 JSON json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', response, re.DOTALL) if json_match: decision = json.loads(json_match.group()) # 添加默认值 decision.setdefault("confidence", 0.8) decision.setdefault("strategy", "master_agent") decision.setdefault("routing_method", "master_agent") decision.setdefault("need_collaboration", False) decision.setdefault("collaboration_agents", []) return decision else: raise ValueError("无法从响应中提取 JSON") except Exception as e: logger.error(f"解析 Master Agent 决策失败: {str(e)}") logger.debug(f"原始响应: {response}") raise def _validate_decision(self, decision: Dict[str, Any]) -> Dict[str, Any]: """验证决策的有效性 Args: decision: 决策字典 Returns: 验证后的决策 """ # 验证 agent_id selected_agent_id = decision.get("selected_agent_id") if selected_agent_id not in self.sub_agents: logger.warning(f"Master Agent 返回的 agent_id 无效: {selected_agent_id}") # 使用默认 agent decision["selected_agent_id"] = self._get_default_agent_id() decision["confidence"] = 0.5 decision["reasoning"] = "原始选择无效,使用默认 Agent" # 验证 confidence confidence = decision.get("confidence", 0.8) if not isinstance(confidence, (int, float)) or confidence < 0 or confidence > 1: decision["confidence"] = 0.8 # 验证协作 agents if decision.get("need_collaboration"): # 检查是否是问题拆分模式 if decision.get("need_decomposition") or decision.get("sub_questions"): # 问题拆分模式 sub_questions = decision.get("sub_questions", []) # 验证每个子问题 valid_sub_questions = [] for sub_q in sub_questions: if isinstance(sub_q, dict): agent_id = sub_q.get("agent_id") question = sub_q.get("question") if agent_id in self.sub_agents and question: # 确保有必要的字段 sub_q.setdefault("order", len(valid_sub_questions) + 1) sub_q.setdefault("depends_on", []) valid_sub_questions.append(sub_q) else: # 记录验证失败的原因 logger.warning( "子问题验证失败", extra={ "agent_id": agent_id, "has_question": bool(question), "agent_exists": agent_id in self.sub_agents if agent_id else False, "available_agents": list(self.sub_agents.keys()) } ) decision["sub_questions"] = valid_sub_questions # 如果所有子问题都验证失败,降级处理 if not valid_sub_questions and sub_questions: logger.warning( "所有子问题验证失败,降级到单 Agent 模式", extra={ "original_sub_question_count": len(sub_questions), "available_agents": list(self.sub_agents.keys()) } ) # 降级:取消协作标记,使用默认 Agent decision["need_collaboration"] = False decision["need_decomposition"] = False decision["collaboration_strategy"] = None # 选择第一个可用的 Agent if self.sub_agents: first_agent_id = next(iter(self.sub_agents.keys())) decision["selected_agent_id"] = first_agent_id logger.info(f"降级使用默认 Agent: {first_agent_id}") # 设置协作策略为 decomposition decision["collaboration_strategy"] = "decomposition" logger.info( "问题拆分决策验证完成", extra={ "sub_question_count": len(valid_sub_questions), "strategy": "decomposition" } ) else: # 普通协作模式 collaboration_agents = decision.get("collaboration_agents", []) # 如果是简单列表格式,转换为详细格式 if collaboration_agents and isinstance(collaboration_agents[0], str): collaboration_agents = [ { "agent_id": agent_id, "role": "primary" if i == 0 else "secondary", "task": "协作处理", "order": i + 1 } for i, agent_id in enumerate(collaboration_agents) ] # 验证每个协作 agent valid_agents = [] for agent_info in collaboration_agents: if isinstance(agent_info, dict): agent_id = agent_info.get("agent_id") if agent_id in self.sub_agents: # 确保有必要的字段 agent_info.setdefault("role", "secondary") agent_info.setdefault("task", "协作处理") agent_info.setdefault("order", len(valid_agents) + 1) valid_agents.append(agent_info) elif isinstance(agent_info, str) and agent_info in self.sub_agents: valid_agents.append({ "agent_id": agent_info, "role": "secondary", "task": "协作处理", "order": len(valid_agents) + 1 }) decision["collaboration_agents"] = valid_agents # 设置默认协作策略 if not decision.get("collaboration_strategy"): decision["collaboration_strategy"] = "sequential" logger.info( "协作决策验证完成", extra={ "collaboration_agent_count": len(valid_agents), "strategy": decision.get("collaboration_strategy") } ) return decision def _get_fallback_decision(self, message: str) -> Dict[str, Any]: """获取降级决策(当 Master Agent 失败时) Args: message: 用户消息 Returns: 降级决策 """ default_agent_id = self._get_default_agent_id() return { "selected_agent_id": default_agent_id, "confidence": 0.5, "strategy": "fallback", "reasoning": "Master Agent 决策失败,使用默认 Agent", "topic": "未知", "need_collaboration": False, "collaboration_agents": [], "routing_method": "fallback" } def _get_default_agent_id(self) -> str: """获取默认 Agent ID Returns: 默认 Agent ID """ if self.sub_agents: # 返回第一个 agent return next(iter(self.sub_agents.keys())) return "default-agent"