diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index 4a8e74e8..bcdded99 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -31,7 +31,7 @@ async def get_default_agent(current_user: User = Depends(get_required_user)): default_agent_id = config.default_agent_id # 如果没有设置默认智能体,尝试获取第一个可用的智能体 if not default_agent_id: - agents = [agent.get_info() for agent in agent_manager.agents.values()] + agents = await agent_manager.get_agents_info() if agents: default_agent_id = agents[0].get("name", "") @@ -45,7 +45,7 @@ async def set_default_agent(agent_id: str = Body(..., embed=True), current_user """设置默认智能体ID (仅管理员)""" try: # 验证智能体是否存在 - agents = [agent.get_info() for agent in agent_manager.agents.values()] + agents = await agent_manager.get_agents_info() agent_ids = [agent.get("name", "") for agent in agents] if agent_id not in agent_ids: @@ -161,7 +161,8 @@ async def call(query: str = Body(...), meta: dict = Body(None), current_user: Us @chat.get("/agent") async def get_agent(current_user: User = Depends(get_required_user)): """获取所有可用智能体(需要登录)""" - agents = [agent.get_info() for agent in agent_manager.agents.values()] + agents = await agent_manager.get_agents_info() + # logger.debug(f"agents: {agents}") return {"agents": agents} @chat.post("/agent/{agent_name}") @@ -195,7 +196,7 @@ 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_runnable_agent(agent_name) + agent = agent_manager.get_agent(agent_name) 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") @@ -284,7 +285,7 @@ async def get_agent_history( """获取智能体历史消息(需要登录)""" try: # 获取Agent实例和配置类 - agent = agent_manager.get_runnable_agent(agent_name) + agent = agent_manager.get_agent(agent_name) if not agent: raise HTTPException(status_code=404, detail=f"智能体 {agent_name} 不存在") diff --git a/src/agents/__init__.py b/src/agents/__init__.py index 634292e8..389b0049 100644 --- a/src/agents/__init__.py +++ b/src/agents/__init__.py @@ -1,37 +1,42 @@ +import asyncio from src.agents.chatbot import ChatbotAgent from src.agents.react import ReActAgent class AgentManager: def __init__(self): - self.agents = {} - self.agent_instances = {} # 存储已创建的 agent 实例 + self._classes = {} + self._instances = {} # 存储已创建的 agent 实例 - def add_agent(self, agent_id, agent_class): - self.agents[agent_id] = agent_class + def register_agent(self, agent_id, agent_class): + self._classes[agent_id] = agent_class - def get_runnable_agent(self, agent_id, **kwargs): + def init_all_agents(self): + for agent_id, agent_class in self._classes.items(): + self.get_agent(agent_id) + + def get_agent(self, agent_id, **kwargs): # 检查是否已经创建了该 agent 的实例 - if agent_id not in self.agent_instances: - agent_class = self.get_agent(agent_id) - self.agent_instances[agent_id] = agent_class() - return self.agent_instances[agent_id] + if agent_id not in self._instances: + agent_class = self._classes[agent_id] + self._instances[agent_id] = agent_class() - def get_agent(self, agent_id): - return self.agents[agent_id] + return self._instances[agent_id] + + def get_agents(self): + return list(self._instances.values()) + + async def get_agents_info(self): + agents = self.get_agents() + return await asyncio.gather(*[a.get_info() for a in agents]) agent_manager = AgentManager() -agent_manager.add_agent("chatbot", ChatbotAgent) -# agent_manager.add_agent("react", ReActAgent) # 暂时屏蔽 ReActAgent +agent_manager.register_agent("chatbot", ChatbotAgent) +agent_manager.register_agent("react", ReActAgent) # 暂时屏蔽 ReActAgent +agent_manager.init_all_agents() __all__ = ["agent_manager"] if __name__ == "__main__": - agent = agent_manager.get_agent("chatbot") - conf = agent_manager.get_configuration("chatbot") - agent_info = { - "name": agent.name, - "description": agent.description, - } - print(agent_info) + pass \ No newline at end of file diff --git a/src/agents/chatbot/configuration.py b/src/agents/chatbot/configuration.py index dd8fd2d5..57c5847e 100644 --- a/src/agents/chatbot/configuration.py +++ b/src/agents/chatbot/configuration.py @@ -15,24 +15,6 @@ class ChatbotConfiguration(Configuration): configurable 为 False 的配置项不能被用户配置,只能由开发者预设。 """ - thread_id: str = field( - default_factory=lambda: str(uuid.uuid4()), - metadata={ - "name": "线程ID", - "configurable": False, - "description": "用来描述智能体的角色和行为" - }, - ) - - user_id: str = field( - default_factory=lambda: str(uuid.uuid4()), - metadata={ - "name": "用户ID", - "configurable": False, - "description": "用来描述智能体的角色和行为" - }, - ) - system_prompt: str = field( default="You are a helpful assistant.", metadata={ diff --git a/src/agents/chatbot/graph.py b/src/agents/chatbot/graph.py index d28d490e..9daf6c35 100644 --- a/src/agents/chatbot/graph.py +++ b/src/agents/chatbot/graph.py @@ -75,11 +75,18 @@ class ChatbotAgent(BaseAgent): workflow.add_edge("tools", "chatbot") workflow.add_edge("chatbot", END) - # 创建数据库连接 - sqlite_checkpointer = AsyncSqliteSaver(await self.get_async_conn()) - graph = workflow.compile(checkpointer=sqlite_checkpointer) - self.graph = graph - return graph + # 创建数据库连接并确保设置 checkpointer + try: + sqlite_checkpointer = AsyncSqliteSaver(await self.get_async_conn()) + graph = workflow.compile(checkpointer=sqlite_checkpointer) + self.graph = graph + return graph + except Exception as e: + logger.error(f"构建 Graph 设置 checkpointer 时出错: {e}") + # 即使出错也返回一个可用的图实例,只是无法保存历史 + graph = workflow.compile() + self.graph = graph + return graph async def get_async_conn(self) -> aiosqlite.Connection: """获取异步数据库连接""" diff --git a/src/agents/react/configuration.py b/src/agents/react/configuration.py index 22986169..a9885692 100644 --- a/src/agents/react/configuration.py +++ b/src/agents/react/configuration.py @@ -1,36 +1,12 @@ -from dataclasses import dataclass, field -from datetime import datetime, timezone -from langchain_core.tools import tool -from langchain_openai import ChatOpenAI +from dataclasses import dataclass from src.agents.registry import Configuration -def get_default_requirements(): - return ["TAVILY_API_KEY"] - -@tool -def multiply(first_int: int, second_int: int) -> int: - """Multiply two integers together.""" - return first_int * second_int @dataclass(kw_only=True) class ReActConfiguration(Configuration): - """ReAct 的配置""" + """配置""" - system_prompt: str = field( - default=f"You are a helpful assistant. Now is {datetime.now(tz=timezone.utc).isoformat()}", - metadata={ - "description": "The system prompt to use for the agent's interactions. " - "This prompt sets the context and behavior for the agent." - }, - ) - - model: str = field( - default="zhipu/glm-4-plus", - metadata={ - "description": "The name of the language model to use for the agent's main interactions. " - "Should be in the form: provider/model-name." - }, - ) + pass diff --git a/src/agents/react/graph.py b/src/agents/react/graph.py index 7de5b3df..ca4d543d 100644 --- a/src/agents/react/graph.py +++ b/src/agents/react/graph.py @@ -2,28 +2,22 @@ import asyncio import uuid +from src.utils import logger from src.agents.registry import BaseAgent from src.agents.react.configuration import ReActConfiguration class ReActAgent(BaseAgent): name = "react" description = "A react agent that can answer questions and help with tasks." + config_schema = ReActConfiguration - def get_graph(self, **kwargs): - """构建图""" + async def get_graph(self, **kwargs): from .workflows import graph return graph -def main(): - agent = ReActAgent(ReActConfiguration()) - thread_id = str(uuid.uuid4()) - config = {"configurable": {"thread_id": thread_id}} - from src.agents.utils import agent_cli - asyncio.run(agent_cli(agent, config)) if __name__ == "__main__": - main() - # asyncio.run(main()) + pass diff --git a/src/agents/react/workflows.py b/src/agents/react/workflows.py index ae9db167..5588d3a6 100644 --- a/src/agents/react/workflows.py +++ b/src/agents/react/workflows.py @@ -1,8 +1,7 @@ import os from langchain_openai import ChatOpenAI - -from src import graph_base +from langgraph.checkpoint.memory import InMemorySaver model = ChatOpenAI(model="glm-4-plus", api_key=os.getenv("ZHIPUAI_API_KEY"), @@ -23,4 +22,4 @@ tools = [] from langgraph.prebuilt import create_react_agent -graph = create_react_agent(model, tools=tools) +graph = create_react_agent(model, tools=tools, checkpointer=InMemorySaver()) diff --git a/src/agents/registry.py b/src/agents/registry.py index d2a134a2..d7c0236e 100644 --- a/src/agents/registry.py +++ b/src/agents/registry.py @@ -2,6 +2,7 @@ from __future__ import annotations import os import yaml +import uuid from pathlib import Path from typing import Type, Annotated, Optional, TypedDict from enum import Enum @@ -139,6 +140,23 @@ class Configuration(dict): return confs + thread_id: str = field( + default_factory=lambda: str(uuid.uuid4()), + metadata={ + "name": "线程ID", + "configurable": False, + "description": "用来描述智能体的角色和行为" + }, + ) + + user_id: str = field( + default_factory=lambda: str(uuid.uuid4()), + metadata={ + "name": "用户ID", + "configurable": False, + "description": "用来描述智能体的角色和行为" + }, + ) class BaseAgent(): @@ -146,66 +164,85 @@ class BaseAgent(): 定义一个基础 Agent 供 各类 graph 继承 """ - name: str = field(default="base_agent") - description: str = field(default="base_agent") + name = "base_agent" + description = "base_agent" config_schema: Configuration = Configuration requirements: list[str] def __init__(self, **kwargs): self.check_requirements() - @classmethod - def get_info(cls): + async def get_info(self): return { - "name": cls.name, - "description": cls.description, - "config_schema": cls.config_schema.to_dict(), - "requirements": cls.requirements if hasattr(cls, "requirements") else [], - "all_tools": cls.all_tools if hasattr(cls, "all_tools") else [], + "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(), + "requirements": self.requirements if hasattr(self, "requirements") else [], + "all_tools": self.all_tools if hasattr(self, "all_tools") else [], + "has_checkpointer": await self.check_checkpointer(), + "met_requirements": self.check_requirements(), } def check_requirements(self): if not hasattr(self, "requirements") or not self.requirements: - return + return True for requirement in self.requirements: if requirement not in os.environ: raise ValueError(f"没有配置{requirement} 环境变量,请在 src/.env 文件中配置,并重新启动服务") + return True async def stream_values(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs): - graph = await self.get_graph(config_schema=config_schema, **kwargs) + graph = await self.get_graph() logger.debug(f"stream_values: {config_schema}") for event in graph.astream({"messages": messages}, stream_mode="values", config=config_schema): yield event["messages"] async def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs): - graph = await self.get_graph(config_schema=config_schema, **kwargs) + graph = await self.get_graph() logger.debug(f"stream_messages: {config_schema}") async for msg, metadata in graph.astream({"messages": messages}, stream_mode="messages", config=config_schema): yield msg, metadata + async def check_checkpointer(self): + app = await self.get_graph() + if not hasattr(app, "checkpointer") or app.checkpointer is None: + logger.warning(f"智能体 {self.name} 的 Graph 未配置 checkpointer,无法获取历史记录") + return False + return True + async def get_history(self, user_id, thread_id) -> list[dict]: """获取历史消息""" - # 获取LangGraph应用实例 - app = await self.get_graph() - # 构建配置信息 - config = {"configurable": {"thread_id": thread_id, "user_id": user_id}} - # 获取状态 - state = await app.aget_state(config) + try: + app = await self.get_graph() - result = [] - if state: - messages = state.values.get('messages', []) - for msg in messages: - if hasattr(msg, 'model_dump'): - msg_dict = msg.model_dump() # 转换成字典 - else: - # 如果消息没有model_dump方法,尝试转成dict - msg_dict = dict(msg) if hasattr(msg, '__dict__') else {"content": str(msg)} - result.append(msg_dict) + if not await self.check_checkpointer(): + return [] - return result + config = {"configurable": {"thread_id": thread_id, "user_id": user_id}} + state = await app.aget_state(config) + + result = [] + if state: + messages = state.values.get('messages', []) + for msg in messages: + if hasattr(msg, 'model_dump'): + msg_dict = msg.model_dump() # 转换成字典 + else: + msg_dict = dict(msg) if hasattr(msg, '__dict__') else {"content": str(msg)} + result.append(msg_dict) + + return result + + except Exception as e: + logger.error(f"获取智能体 {self.name} 历史消息出错: {e}") + return [] @abstractmethod - def get_graph(self, **kwargs) -> CompiledStateGraph: + async def get_graph(self, **kwargs) -> CompiledStateGraph: + """ + 获取并编译对话图实例。 + 必须确保在编译时设置 checkpointer,否则将无法获取历史记录。 + 例如: graph = workflow.compile(checkpointer=sqlite_checkpointer) + """ pass \ No newline at end of file diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 85aab5f3..bf55f0d1 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -44,7 +44,7 @@ 正在加载历史记录... -
+

{{ currentAgent ? currentAgent.name : '请选择一个智能体开始对话' }}

{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}

diff --git a/web/src/components/UserInfoComponent.vue b/web/src/components/UserInfoComponent.vue index cab76307..3d64ed45 100644 --- a/web/src/components/UserInfoComponent.vue +++ b/web/src/components/UserInfoComponent.vue @@ -35,7 +35,7 @@ import { useRouter } from 'vue-router'; import { useUserStore } from '@/stores/user'; import { UserOutlined, LogoutOutlined } from '@ant-design/icons-vue'; import { message } from 'ant-design-vue'; -import { CircleUser, LogOut } from 'lucide-vue-next'; +import { CircleUser, UserRoundCheck } from 'lucide-vue-next'; const router = useRouter(); const userStore = useUserStore(); diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index e01a94d5..40ddcfa9 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -59,14 +59,14 @@