From 9d58ebc13c4c5d32533fdb8bd5e9da361c2fe907 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 22 Jul 2025 17:30:00 +0800 Subject: [PATCH] =?UTF-8?q?refactor(agent):=20=E5=B0=86=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E4=BD=93=E7=9B=B8=E5=85=B3=E7=9A=84=E5=90=8D=E7=A7=B0=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E6=9B=B4=E6=94=B9=E4=B8=BAID=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=99=BA=E8=83=BD=E4=BD=93=E7=AE=A1=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/chat_router.py | 48 +++++++++++------------ src/agents/__init__.py | 16 ++++---- src/agents/chatbot/graph.py | 4 +- src/agents/registry.py | 45 +++++++++++++-------- web/src/apis/auth_api.js | 2 +- web/src/components/AgentChatComponent.vue | 2 +- web/src/views/AgentView.vue | 10 ++--- 7 files changed, 69 insertions(+), 58 deletions(-) diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index 72ab083a..344907fb 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -36,7 +36,7 @@ async def get_default_agent(current_user: User = Depends(get_required_user)): if not default_agent_id: agents = await agent_manager.get_agents_info() if agents: - default_agent_id = agents[0].get("name", "") + default_agent_id = agents[0].get("id", "") return {"default_agent_id": default_agent_id} except Exception as e: @@ -49,7 +49,7 @@ async def set_default_agent(agent_id: str = Body(..., embed=True), current_user try: # 验证智能体是否存在 agents = await agent_manager.get_agents_info() - agent_ids = [agent.get("name", "") for agent in agents] + agent_ids = [agent.get("id", "") for agent in agents] if agent_id not in agent_ids: raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在") @@ -96,8 +96,8 @@ async def get_agent(current_user: User = Depends(get_required_user)): # logger.debug(f"agents: {agents}") return {"agents": agents} -@chat.post("/agent/{agent_name}") -async def chat_agent(agent_name: str, +@chat.post("/agent/{agent_id}") +async def chat_agent(agent_id: str, query: str = Body(...), config: dict = Body({}), meta: dict = Body({}), @@ -106,8 +106,8 @@ async def chat_agent(agent_name: str, meta.update({ "query": query, - "agent_name": agent_name, - "server_model_name": config.get("model", agent_name), + "agent_id": agent_id, + "server_model_name": config.get("model", agent_id), "thread_id": config.get("thread_id"), "user_id": current_user.id }) @@ -127,10 +127,10 @@ async def chat_agent(agent_name: str, yield make_chunk(status="init", meta=meta, msg=HumanMessage(content=query).model_dump()) try: - agent = agent_manager.get_agent(agent_name) + agent = agent_manager.get_agent(agent_id) except Exception as e: - logger.error(f"Error getting agent {agent_name}: {e}, {traceback.format_exc()}") - yield make_chunk(message=f"Error getting agent {agent_name}: {e}", status="error") + logger.error(f"Error getting agent {agent_id}: {e}, {traceback.format_exc()}") + yield make_chunk(message=f"Error getting agent {agent_id}: {e}", status="error") return messages = [{"role": "user", "content": query}] @@ -185,25 +185,25 @@ async def get_tools(current_user: User = Depends(get_admin_user)): """获取所有可用工具(需要登录)""" return {"tools": list(get_all_tools().keys())} -@chat.post("/agent/{agent_name}/config") +@chat.post("/agent/{agent_id}/config") async def save_agent_config( - agent_name: str, + agent_id: str, config: dict = Body(...), current_user: User = Depends(get_admin_user) ): """保存智能体配置到YAML文件(需要管理员权限)""" try: # 获取Agent实例和配置类 - agent = agent_manager.get_agent(agent_name) + agent = agent_manager.get_agent(agent_id) if not agent: - raise HTTPException(status_code=404, detail=f"智能体 {agent_name} 不存在") + raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在") # 使用配置类的save_to_file方法保存配置 config_cls = agent.config_schema - result = config_cls.save_to_file(config, agent_name) + result = config_cls.save_to_file(config, agent.module_name) if result: - return {"success": True, "message": f"智能体 {agent_name} 配置已保存"} + return {"success": True, "message": f"智能体 {agent.name} 配置已保存"} else: raise HTTPException(status_code=500, detail="保存智能体配置失败") @@ -211,18 +211,18 @@ async def save_agent_config( logger.error(f"保存智能体配置出错: {e}, {traceback.format_exc()}") raise HTTPException(status_code=500, detail=f"保存智能体配置出错: {str(e)}") -@chat.get("/agent/{agent_name}/history") +@chat.get("/agent/{agent_id}/history") async def get_agent_history( - agent_name: str, + agent_id: str, thread_id: str, current_user: User = Depends(get_required_user) ): """获取智能体历史消息(需要登录)""" try: # 获取Agent实例和配置类 - agent = agent_manager.get_agent(agent_name) + agent = agent_manager.get_agent(agent_id) if not agent: - raise HTTPException(status_code=404, detail=f"智能体 {agent_name} 不存在") + raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在") # 获取历史消息 history = await agent.get_history(user_id=str(current_user.id), thread_id=thread_id) @@ -232,18 +232,18 @@ async def get_agent_history( logger.error(f"获取智能体历史消息出错: {e}, {traceback.format_exc()}") raise HTTPException(status_code=500, detail=f"获取智能体历史消息出错: {str(e)}") -@chat.get("/agent/{agent_name}/config") +@chat.get("/agent/{agent_id}/config") async def get_agent_config( - agent_name: str, + agent_id: str, current_user: User = Depends(get_required_user) ): """从YAML文件加载智能体配置(需要登录)""" try: # 检查智能体是否存在 - if not (agent := agent_manager.get_agent(agent_name)): - raise HTTPException(status_code=404, detail=f"智能体 {agent_name} 不存在") + if not (agent := agent_manager.get_agent(agent_id)): + raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在") - config = agent.config_schema.from_runnable_config(config={}, agent_name=agent_name) + config = agent.config_schema.from_runnable_config(config={}, module_name=agent.module_name) return {"success": True, "config": config} except Exception as e: diff --git a/src/agents/__init__.py b/src/agents/__init__.py index 6c5890b5..e7820289 100644 --- a/src/agents/__init__.py +++ b/src/agents/__init__.py @@ -10,19 +10,19 @@ class AgentManager: self._instances = {} # 存储已创建的 agent 实例 def register_agent(self, agent_class): - self._classes[agent_class.name] = agent_class + self._classes[agent_class.__name__] = agent_class def init_all_agents(self): - for agent_class in self._classes.values(): - self.get_agent(agent_class.name) + for agent_id in self._classes.keys(): + self.get_agent(agent_id) - def get_agent(self, agent_name, **kwargs): + def get_agent(self, agent_id, **kwargs): # 检查是否已经创建了该 agent 的实例 - if agent_name not in self._instances: - agent_class = self._classes[agent_name] - self._instances[agent_name] = agent_class() + if agent_id not in self._instances: + agent_class = self._classes[agent_id] + self._instances[agent_id] = agent_class() - return self._instances[agent_name] + return self._instances[agent_id] def get_agents(self): return list(self._instances.values()) diff --git a/src/agents/chatbot/graph.py b/src/agents/chatbot/graph.py index 657b3c6c..da012c06 100644 --- a/src/agents/chatbot/graph.py +++ b/src/agents/chatbot/graph.py @@ -26,7 +26,7 @@ class ChatbotAgent(BaseAgent): def __init__(self, **kwargs): super().__init__(**kwargs) self.graph = None - self.workdir = Path(sys_config.save_dir) / "agents" / self.name + self.workdir = Path(sys_config.save_dir) / "agents" / self.id self.workdir.mkdir(parents=True, exist_ok=True) def _get_tools(self, tools: list[str]): @@ -47,7 +47,7 @@ class ChatbotAgent(BaseAgent): async def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]: """调用 llm 模型 - 异步版本以支持异步工具""" - conf = self.config_schema.from_runnable_config(config, agent_name=self.name) + conf = self.config_schema.from_runnable_config(config, module_name=self.module_name) system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}" model = load_chat_model(conf.model) diff --git a/src/agents/registry.py b/src/agents/registry.py index 59733cdd..dad31457 100644 --- a/src/agents/registry.py +++ b/src/agents/registry.py @@ -51,13 +51,13 @@ class Configuration(dict): @classmethod def from_runnable_config( - cls, config: RunnableConfig | None = None, agent_name: str | None = None + cls, config: RunnableConfig | None = None, module_name: str | None = None ) -> Configuration: """Create a Configuration instance from a RunnableConfig object. Args: config: RunnableConfig object with highest priority - agent_name: Name of the agent to load config file for + module_name: Name of the agent to load config file for Returns: Configuration instance with merged config values @@ -68,8 +68,8 @@ class Configuration(dict): # 尝试加载文件配置(中等优先级) file_config = {} - if agent_name: - file_config = cls.from_file(agent_name) + if module_name: + file_config = cls.from_file(module_name) # 获取运行时配置(最高优先级) configurable = (config.get("configurable") or {}) if config else {} @@ -94,39 +94,39 @@ class Configuration(dict): return cls(**merged_config) @classmethod - def from_file(cls, agent_name: str) -> Configuration: + def from_file(cls, module_name: str) -> Configuration: """从文件加载配置""" - config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml") + config_file_path = Path(f"src/agents/{module_name}/config.private.yaml") file_config = {} if os.path.exists(config_file_path): try: with open(config_file_path, encoding='utf-8') as f: file_config = yaml.safe_load(f) or {} - # logger.info(f"从文件加载智能体 {agent_name} 配置: {file_config}") + # logger.info(f"从文件加载智能体 {module_name} 配置: {file_config}") except Exception as e: logger.error(f"加载智能体配置文件出错: {e}") return file_config @classmethod - def save_to_file(cls, config: dict, agent_name: str) -> bool: + def save_to_file(cls, config: dict, module_name: str) -> bool: """Save configuration to a YAML file Args: config: Configuration dictionary to save - agent_name: Name of the agent to save config for + module_name: Name of the agent to save config for Returns: True if saving was successful, False otherwise """ try: - config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml") + config_file_path = Path(f"src/agents/{module_name}/config.private.yaml") # 确保目录存在 os.makedirs(os.path.dirname(config_file_path), exist_ok=True) with open(config_file_path, 'w', encoding='utf-8') as f: yaml.dump(config, f, indent=2, allow_unicode=True) - # logger.info(f"智能体 {agent_name} 配置已保存到 {config_file_path}") + # logger.info(f"智能体 {module_name} 配置已保存到 {config_file_path}") return True except Exception as e: logger.error(f"保存智能体配置文件出错: {e}") @@ -183,7 +183,7 @@ class BaseModelConfiguration(BaseModel): def from_runnable_config( cls, config: Optional["RunnableConfig"] = None, - agent_name: Optional[str] = None, + module_name: Optional[str] = None, ) -> "BaseModelConfiguration": """ 从 RunnableConfig 和 YAML 文件中构建 Configuration 实例 @@ -193,7 +193,7 @@ class BaseModelConfiguration(BaseModel): default_values = default_instance.dict() # 文件配置 - file_config = cls.from_file(agent_name) if agent_name else {} + file_config = cls.from_file(module_name) if module_name else {} # 运行时配置(最高优先级) runtime_config = config.get("configurable") if config else {} @@ -207,11 +207,11 @@ class BaseModelConfiguration(BaseModel): return cls(**merged_config) @classmethod - def from_file(cls, agent_name: str) -> dict[str, Any]: + def from_file(cls, module_name: str) -> dict[str, Any]: """ 从 YAML 文件加载配置 """ - config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml") + config_file_path = Path(f"src/agents/{module_name}/config.private.yaml") if os.path.exists(config_file_path): try: with open(config_file_path, encoding="utf-8") as f: @@ -221,12 +221,12 @@ class BaseModelConfiguration(BaseModel): return {} @classmethod - def save_to_file(cls, config: dict, agent_name: str) -> bool: + def save_to_file(cls, config: dict, module_name: str) -> bool: """ 保存配置到 YAML 文件 """ try: - config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml") + config_file_path = Path(f"src/agents/{module_name}/config.private.yaml") os.makedirs(config_file_path.parent, exist_ok=True) with open(config_file_path, "w", encoding="utf-8") as f: yaml.dump(config, f, indent=2, allow_unicode=True) @@ -290,8 +290,19 @@ class BaseAgent: def __init__(self, **kwargs): self.check_requirements() + @property + def module_name(self) -> str: + """Get the module name of the agent class.""" + return self.__class__.__module__.split('.')[-2] + + @property + def id(self) -> str: + """Get the agent's class name.""" + return self.__class__.__name__ + async def get_info(self): return { + "id": self.id, "name": self.name if hasattr(self, "name") else "Unknown", "description": self.description if hasattr(self, "description") else "Unknown", "config_schema": self.config_schema.to_dict(), diff --git a/web/src/apis/auth_api.js b/web/src/apis/auth_api.js index 6231c6a9..811eb0f3 100644 --- a/web/src/apis/auth_api.js +++ b/web/src/apis/auth_api.js @@ -92,7 +92,7 @@ export const chatApi = { * @param {string} threadId - 会话ID * @returns {Promise} - 历史消息 */ - getAgentHistory: (agentName, threadId) => apiGet(`/api/chat/agent/${agentName}/history?thread_id=${threadId}`, {}, true), + getAgentHistory: (agentId, threadId) => apiGet(`/api/chat/agent/${agentId}/history?thread_id=${threadId}`, {}, true), /** * 获取可用工具列表 diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 7585003c..745459dc 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -1014,7 +1014,7 @@ const fetchAgents = async () => { const data = await chatApi.getAgents(); // 将数组转换为对象 agents.value = data.agents.reduce((acc, agent) => { - acc[agent.name] = agent; + acc[agent.id] = agent; return acc; }, {}); console.log("agents", agents.value); diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index 6a766611..13d95a66 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -15,13 +15,13 @@ @change="selectAgent" >
-

{{ agent.name }}

+

{{ agent.name }}

{{ agent.description }}

@@ -338,7 +338,7 @@ const fetchAgents = async () => { const data = await chatApi.getAgents(); // 将数组转换为对象 agents.value = data.agents.reduce((acc, agent) => { - acc[agent.name] = agent; + acc[agent.id] = agent; return acc; }, {}); // console.log("agents", agents.value);