refactor: 重构智能体管理和基础代理类
- 将 AgentManager 类中的方法重命名为更具描述性的名称,并优化了代理的注册和实例化逻辑。 - 添加了线程ID和用户ID字段到基础代理类,并改进了获取历史消息的逻辑,增加了错误处理。 - 在 ChatbotAgent 中确保创建图时设置 checkpointer,并处理可能的异常。
This commit is contained in:
parent
44fad724bf
commit
6475170c4b
@ -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} 不存在")
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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={
|
||||
|
||||
@ -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:
|
||||
"""获取异步数据库连接"""
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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
|
||||
@ -44,7 +44,7 @@
|
||||
<span>正在加载历史记录...</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="convs.length === 0" class="chat-examples">
|
||||
<div v-else-if="convs.length === 0 && !onGoingConv.messages.length" class="chat-examples">
|
||||
<h1>{{ currentAgent ? currentAgent.name : '请选择一个智能体开始对话' }}</h1>
|
||||
<p>{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}</p>
|
||||
</div>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -59,14 +59,14 @@
|
||||
<div class="sidebar" :class="{ 'is-open': state.agentSiderbarConfigOpen }">
|
||||
<div class="agent-info">
|
||||
<h3 @click="toggleDebugMode">描述</h3>
|
||||
<p>{{ agents[selectedAgentId]?.description }}</p>
|
||||
<p>{{ selectedAgent.description }}</p>
|
||||
<pre v-if="state.debug_mode">{{ selectedAgent }}</pre>
|
||||
|
||||
<div v-if="selectedAgentId && configSchema" class="config-modal-content">
|
||||
<!-- 配置表单 -->
|
||||
<a-form :model="agentConfig" layout="vertical">
|
||||
<div class="empty-config" v-if="state.isEmptyConfig">
|
||||
<a-alert type="warning" message="该智能体没有配置项" show-icon/>
|
||||
</div>
|
||||
<a-alert v-if="state.isEmptyConfig" type="warning" message="该智能体没有配置项" show-icon/>
|
||||
<a-alert v-if="!selectedAgent.has_checkpointer" type="error" message="该智能体没有配置 Checkpointer,功能无法正常使用,参考:https://langchain-ai.github.io/langgraph/concepts/persistence/" show-icon/>
|
||||
<!-- 统一显示所有配置项 -->
|
||||
<template v-for="(value, key) in configurableItems" :key="key">
|
||||
<a-form-item
|
||||
@ -185,7 +185,9 @@ const state = reactive({
|
||||
Object.keys(configurableItems.value).length === 0
|
||||
)
|
||||
});
|
||||
const configSchema = computed(() => agents.value[selectedAgentId.value]?.config_schema || {});
|
||||
|
||||
const selectedAgent = computed(() => agents.value[selectedAgentId.value] || {});
|
||||
const configSchema = computed(() => selectedAgent.value.config_schema || {});
|
||||
const configurableItems = computed(() => configSchema.value.configurable_items || {});
|
||||
|
||||
// 配置状态
|
||||
@ -617,6 +619,11 @@ const toggleSidebar = () => {
|
||||
.config-modal-content {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
user-select: text;
|
||||
|
||||
div[role="alert"] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 12px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user