From e43f1cd618c99ab3799980b32d51d47c41583c04 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Mon, 31 Mar 2025 22:20:29 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E5=A4=A7=E5=A0=86=EF=BC=9A=EF=BC=881=EF=BC=89=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=86=E4=B8=80=E4=B8=AA=E9=80=82=E9=85=8D=20LangGr?= =?UTF-8?q?aph=20=E7=9A=84=20graph=20=E7=9A=84=E7=B1=BB=EF=BC=8C=E8=BF=99?= =?UTF-8?q?=E6=A0=B7=E5=8F=AF=E4=BB=A5=E6=9B=B4=E5=8A=A0=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E7=9A=84=E8=B0=83=E8=AF=95=20Agent=20=E4=BA=86=E3=80=82?= =?UTF-8?q?=EF=BC=882=EF=BC=89=E5=A2=9E=E5=8A=A0=E4=BA=86=20Agent=20call?= =?UTF-8?q?=20=E7=9A=84=E6=8A=A5=E9=94=99=E4=BD=93=E9=AA=8C=EF=BC=8C?= =?UTF-8?q?=E4=BD=86=E6=98=AF=E5=89=8D=E7=AB=AF=E8=BF=98=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E5=8A=A0=E8=BF=9B=E5=8E=BB=E3=80=82=EF=BC=883=EF=BC=89?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E9=83=A8=E5=88=86=E6=97=A0=E5=85=B3=E7=B4=A7?= =?UTF-8?q?=E8=A6=81=E7=9A=84=20logger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/chatbot/configuration.py | 10 ------ src/agents/chatbot/graph.py | 28 +++++----------- src/agents/react/graph.py | 52 ++--------------------------- src/agents/react/workflows.py | 36 ++++++++++++++++++++ src/agents/registry.py | 37 ++++++++++++++++++-- src/agents/tools_factory.py | 24 +++++++++++-- src/core/retriever.py | 2 +- src/routers/chat_router.py | 35 ++++++++++--------- src/routers/data_router.py | 2 +- src/routers/tool_router.py | 2 +- web/src/views/AgentView.vue | 2 +- 11 files changed, 127 insertions(+), 103 deletions(-) create mode 100644 src/agents/react/workflows.py diff --git a/src/agents/chatbot/configuration.py b/src/agents/chatbot/configuration.py index 2296f354..161f3358 100644 --- a/src/agents/chatbot/configuration.py +++ b/src/agents/chatbot/configuration.py @@ -1,19 +1,9 @@ from dataclasses import dataclass, field from datetime import datetime, timezone -from langchain_core.tools import tool -from langchain_openai import ChatOpenAI 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 ChatbotConfiguration(Configuration): """Chatbot 的配置""" diff --git a/src/agents/chatbot/graph.py b/src/agents/chatbot/graph.py index 51c362d9..0625cdcf 100644 --- a/src/agents/chatbot/graph.py +++ b/src/agents/chatbot/graph.py @@ -12,26 +12,28 @@ from langchain_community.tools.tavily_search import TavilySearchResults from src.agents.registry import State, BaseAgent from src.agents.utils import load_chat_model -from src.agents.chatbot.configuration import ChatbotConfiguration, multiply +from src.agents.tools_factory import multiply, add, subtract, divide +from src.agents.chatbot.configuration import ChatbotConfiguration class ChatbotAgent(BaseAgent): name = "chatbot" description = "A chatbot that can answer questions and help with tasks." + requirements = ["TAVILY_API_KEY", "ZHIPUAI_API_KEY"] _graph_cache = None - config_schema = ChatbotConfiguration.to_dict() + config_schema = ChatbotConfiguration def __init__(self, **kwargs): super().__init__(**kwargs) def _get_tools(self, config_schema: RunnableConfig): """根据配置获取工具""" - tools = [multiply, TavilySearchResults(max_results=10)] + tools = [multiply, add, subtract, divide, TavilySearchResults(max_results=10)] return tools def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]: """调用 llm 模型""" config_schema = config or {} - conf = ChatbotConfiguration.from_runnable_config(config_schema) + conf = self.config_schema.from_runnable_config(config_schema) model = load_chat_model(conf.model) model_with_tools = model.bind_tools(self._get_tools(config_schema)) @@ -40,9 +42,9 @@ class ChatbotAgent(BaseAgent): ) return {"messages": [res]} - def get_graph(self, config_schema: RunnableConfig = None): + def get_graph(self, config_schema: RunnableConfig = None, **kwargs): """构建图""" - workflow = StateGraph(State, config_schema=ChatbotConfiguration) + workflow = StateGraph(State, config_schema=self.config_schema) workflow.add_node("chatbot", self.llm_call) workflow.add_node("tools", ToolNode(tools=self._get_tools(config_schema))) workflow.add_edge(START, "chatbot") @@ -56,20 +58,6 @@ class ChatbotAgent(BaseAgent): graph = workflow.compile(checkpointer=MemorySaver()) return graph - def stream_values(self, messages: list[str], config_schema: RunnableConfig = None): - graph = self.get_graph(config_schema) - for event in graph.stream({"messages": messages}, stream_mode="values", config=config_schema): - yield event["messages"] - - def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None): - graph = self.get_graph(config_schema) - conf = ChatbotConfiguration.from_runnable_config(config_schema) - for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema): - msg_type = msg.type - - return_keys =conf.return_keys - if not return_keys or msg_type in return_keys: - yield msg, metadata def main(): agent = ChatbotAgent(ChatbotConfiguration()) diff --git a/src/agents/react/graph.py b/src/agents/react/graph.py index 02650c83..f67b1854 100644 --- a/src/agents/react/graph.py +++ b/src/agents/react/graph.py @@ -17,60 +17,12 @@ from src.agents.react.configuration import ReActConfiguration, multiply class ReActAgent(BaseAgent): name = "react" description = "A react agent that can answer questions and help with tasks." - _graph_cache = None - config_schema = ReActConfiguration.to_dict() - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def _get_tools(self, config_schema: RunnableConfig): - """根据配置获取工具""" - tools = [multiply, TavilySearchResults(max_results=10)] - return tools - - def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]: - """调用 llm 模型""" - config_schema = config or {} - conf = ReActConfiguration.from_runnable_config(config_schema) - model = load_chat_model(conf.model) - model_with_tools = model.bind_tools(self._get_tools(config_schema)) - - res = model_with_tools.invoke( - [{"role": "system", "content": conf.system_prompt}, *state["messages"]] - ) - return {"messages": [res]} - - def get_graph(self, config_schema: RunnableConfig = None): + def get_graph(self, **kwargs): """构建图""" - workflow = StateGraph(State, config_schema=ReActConfiguration) - workflow.add_node("react", self.llm_call) - workflow.add_node("tools", ToolNode(tools=self._get_tools(config_schema))) - workflow.add_edge(START, "react") - workflow.add_conditional_edges( - "react", - tools_condition, - ) - workflow.add_edge("tools", "react") - workflow.add_edge("react", END) - - graph = workflow.compile(checkpointer=MemorySaver()) + from .workflows import graph return graph - def stream_values(self, messages: list[str], config_schema: RunnableConfig = None): - graph = self.get_graph(config_schema) - for event in graph.stream({"messages": messages}, stream_mode="values", config=config_schema): - yield event["messages"] - - def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None): - graph = self.get_graph(config_schema) - conf = ReActConfiguration.from_runnable_config(config_schema) - for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema): - msg_type = msg.type - - return_keys =conf.return_keys - if not return_keys or msg_type in return_keys: - yield msg, metadata - def main(): agent = ReActAgent(ReActConfiguration()) diff --git a/src/agents/react/workflows.py b/src/agents/react/workflows.py new file mode 100644 index 00000000..1337d133 --- /dev/null +++ b/src/agents/react/workflows.py @@ -0,0 +1,36 @@ +import os + +from langchain_openai import ChatOpenAI + +model = ChatOpenAI(model="glm-4-plus", + api_key=os.getenv("ZHIPUAI_API_KEY"), + base_url="https://open.bigmodel.cn/api/paas/v4/", + temperature=0) + + +# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF) + +from typing import Literal + +from langchain_core.tools import tool + + +@tool +def get_weather(city: Literal["nyc", "sf"]): + """Use this to get weather information.""" + if city == "nyc": + return "It might be cloudy in nyc" + elif city == "sf": + return "It's always sunny in sf" + else: + raise AssertionError("Unknown city") + + +tools = [get_weather] + + +# Define the graph + +from langgraph.prebuilt import create_react_agent + +graph = create_react_agent(model, tools=tools) diff --git a/src/agents/registry.py b/src/agents/registry.py index 7c365ed2..1db2621d 100644 --- a/src/agents/registry.py +++ b/src/agents/registry.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os + from typing import Type, Annotated, Optional, TypedDict from abc import abstractmethod from dataclasses import dataclass, fields, field @@ -46,9 +48,40 @@ class Configuration(SimpleConfig): class BaseAgent(): + """ + 定义一个基础 Agent 供 各类 graph 继承 + """ + + name: str = field(default="base_agent") + description: str = field(default="base_agent") + config_schema: Configuration = Configuration + requirements: list[str] + def __init__(self, **kwargs): - pass + self.check_requirements() + + def check_requirements(self): + if not hasattr(self, "requirements") or not self.requirements: + return + for requirement in self.requirements: + if requirement not in os.environ: + raise ValueError(f"{requirement} is not set") + + def stream_values(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs): + graph = self.get_graph(config_schema=config_schema, **kwargs) + for event in graph.stream({"messages": messages}, stream_mode="values", config=config_schema): + yield event["messages"] + + def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs): + graph = self.get_graph(config_schema=config_schema, **kwargs) + conf = self.config_schema.from_runnable_config(config_schema) + for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema): + msg_type = msg.type + + return_keys =conf.return_keys + if not return_keys or msg_type in return_keys: + yield msg, metadata @abstractmethod - def get_graph(self) -> CompiledStateGraph: + def get_graph(self, **kwargs) -> CompiledStateGraph: pass \ No newline at end of file diff --git a/src/agents/tools_factory.py b/src/agents/tools_factory.py index 95d04426..3dd0eee8 100644 --- a/src/agents/tools_factory.py +++ b/src/agents/tools_factory.py @@ -5,8 +5,7 @@ from typing import Any, Callable, Optional, Type, Union from pydantic import BaseModel, Field -from langchain.agents import tool -from langchain_core.tools import Tool, BaseTool +from langchain_core.tools import tool, BaseTool _TOOLS_REGISTRY = {} @@ -95,3 +94,24 @@ class BaseToolOutput: else: return str(self.data) + + +@tool +def multiply(first_int: int, second_int: int) -> int: + """Multiply two integers together.""" + return first_int * second_int + +@tool +def add(first_int: int, second_int: int) -> int: + """Add two integers together.""" + return first_int + second_int + +@tool +def subtract(first_int: int, second_int: int) -> int: + """Subtract two integers.""" + return first_int - second_int + +@tool +def divide(first_int: int, second_int: int) -> int: + """Divide two integers.""" + return first_int / second_int diff --git a/src/core/retriever.py b/src/core/retriever.py index 3181a870..aad40905 100644 --- a/src/core/retriever.py +++ b/src/core/retriever.py @@ -229,7 +229,7 @@ class Retriever: return formatted_results def format_query_results(self, results): - logger.debug(f"Graph Query Results: {results}") + # logger.debug(f"Graph Query Results: {results}") formatted_results = {"nodes": [], "edges": []} node_dict = {} diff --git a/src/routers/chat_router.py b/src/routers/chat_router.py index 977ecd27..8fd11398 100644 --- a/src/routers/chat_router.py +++ b/src/routers/chat_router.py @@ -1,3 +1,4 @@ +import os import json import asyncio import traceback @@ -126,7 +127,7 @@ async def get_agent(): agents = [{ "name": agent.name, "description": agent.description, - "config_schema": agent.config_schema + "config_schema": agent.config_schema.to_dict() } for agent in agent_manager.agents.values()] return {"agents": agents} @@ -138,19 +139,6 @@ def chat_agent(agent_name: str, thread_id: str | None = Body(None)): meta["server_model_name"] = agent_name - agent = agent_manager.get_runnable_agent(agent_name) - - history_manager = HistoryManager(history) - messages = history_manager.get_history_with_msg(query, max_rounds=meta.get('history_round')) - history_manager.add_user(query) # 注意这里使用原始查询 - - runnable_config = { - "configurable": { - "thread_id": thread_id or str(uuid.uuid4()), - "use_web": meta.get("use_web", False), - "return_keys": [] - } - } def make_chunk(content=None, **kwargs): return json.dumps({ @@ -160,6 +148,23 @@ def chat_agent(agent_name: str, **kwargs }, ensure_ascii=False).encode('utf-8') + b"\n" + try: + agent = agent_manager.get_runnable_agent(agent_name) + except Exception as e: + logger.error(f"Error getting agent {agent_name}: {e}") + return StreamingResponse(make_chunk(message=f"Error getting agent {agent_name}: {e}", status="error"), media_type='application/json') + + history_manager = HistoryManager(history) + messages = history_manager.get_history_with_msg(query, max_rounds=meta.get('history_round')) + history_manager.add_user(query) # 注意这里使用原始查询 + + runnable_config = { + "configurable": { + "thread_id": thread_id or str(uuid.uuid4()), + "return_keys": [] + } + } + def stream_messages(): content = "" yield make_chunk(status="waiting") @@ -178,4 +183,4 @@ def chat_agent(agent_name: str, yield make_chunk(status="finished", history=history_manager.update_ai(content)) - return StreamingResponse(stream_messages(), media_type='application/json') + return StreamingResponse(stream_messages(), media_type='application/json') \ No newline at end of file diff --git a/src/routers/data_router.py b/src/routers/data_router.py index fd60290e..e4493b24 100644 --- a/src/routers/data_router.py +++ b/src/routers/data_router.py @@ -72,7 +72,7 @@ async def create_document_by_file(db_id: str = Body(...), files: List[str] = Bod @data.post("/add-by-chunks") async def add_by_chunks(db_id: str = Body(...), file_chunks: dict = Body(...)): - logger.debug(f"Add chunks in {db_id}: {file_chunks}") + # logger.debug(f"Add chunks in {db_id}: {len(file_chunks)} chunks") try: loop = asyncio.get_event_loop() await loop.run_in_executor( diff --git a/src/routers/tool_router.py b/src/routers/tool_router.py index 8a44616b..73c6d071 100644 --- a/src/routers/tool_router.py +++ b/src/routers/tool_router.py @@ -50,7 +50,7 @@ async def route_index(): description=agent.description, url=f"/agent/{agent.name}", method="POST", - metadata=agent.config_schema, + metadata=agent.config_schema.to_dict(), ) ) diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index 86d90a12..0a2f31bf 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -527,7 +527,7 @@ const updateExistingMessage = async (data, existingMsgIndex) => { // 如果消息状态是loading,更新为processing if (msgInstance.status === 'loading') { - msgInstance.status = 'processing'; + msgInstance.status = 'loading'; msgInstance.run_id = data.metadata?.run_id; msgInstance.step = data.metadata?.langgraph_step; } From 4c2f44ed6c2bd4ccc728a0be5a52fb70ae3aac96 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Mon, 31 Mar 2025 22:32:19 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/AgentChatComponent.vue | 1175 +++++++++++++++++ .../components/AgentSingleViewComponent.vue | 0 web/src/router/index.js | 40 +- web/src/views/AgentSingleView.vue | 7 + web/src/views/AgentView.vue | 1172 +--------------- 5 files changed, 1204 insertions(+), 1190 deletions(-) create mode 100644 web/src/components/AgentChatComponent.vue delete mode 100644 web/src/components/AgentSingleViewComponent.vue create mode 100644 web/src/views/AgentSingleView.vue diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue new file mode 100644 index 00000000..ee9c491d --- /dev/null +++ b/web/src/components/AgentChatComponent.vue @@ -0,0 +1,1175 @@ + + + + + diff --git a/web/src/components/AgentSingleViewComponent.vue b/web/src/components/AgentSingleViewComponent.vue deleted file mode 100644 index e69de29b..00000000 diff --git a/web/src/router/index.js b/web/src/router/index.js index 84945a8c..fd63f14f 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -31,29 +31,29 @@ const router = createRouter({ } ] }, - { - path: '/agent', - name: 'agent', - component: AppLayout, - children: [ - { - path: '', - name: 'AgentMain', - component: () => import('../views/AgentView.vue'), - meta: { keepAlive: true } - }, - { - path: ':agent_id', - name: 'AgentSinglePage', - component: () => import('../components/AgentSingleViewComponent.vue'), - meta: { keepAlive: false } - } - ] - }, + // { + // path: '/agent', + // name: 'agent', + // component: AppLayout, + // children: [ + // { + // path: '', + // name: 'AgentMain', + // component: () => import('../views/AgentView.vue'), + // meta: { keepAlive: true } + // }, + // { + // path: ':agent_id', + // name: 'AgentSinglePage', + // component: () => import('../components/AgentSingleViewComponent.vue'), + // meta: { keepAlive: false } + // } + // ] + // }, { path: '/agent/:agent_id', name: 'AgentSinglePage', - component: () => import('../views/AgentView.vue'), + component: () => import('../components/AgentChatComponent.vue'), }, { path: '/graph', diff --git a/web/src/views/AgentSingleView.vue b/web/src/views/AgentSingleView.vue new file mode 100644 index 00000000..49b0ffe5 --- /dev/null +++ b/web/src/views/AgentSingleView.vue @@ -0,0 +1,7 @@ + + + diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index 0a2f31bf..d9405ab7 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -1,1175 +1,7 @@ - - - From b7df4ecd4a6024f56144ef057766b81a6ea23184 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Mon, 31 Mar 2025 23:02:05 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E4=BD=93=E7=9A=84=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/assets/markdown.css | 5 + web/src/components/AgentChatComponent.vue | 195 +++++++++++----------- web/src/views/AgentSingleView.vue | 21 ++- web/src/views/AgentView.vue | 161 +++++++++++++++++- 4 files changed, 276 insertions(+), 106 deletions(-) diff --git a/web/src/assets/markdown.css b/web/src/assets/markdown.css index ffaf9991..8a742f1f 100644 --- a/web/src/assets/markdown.css +++ b/web/src/assets/markdown.css @@ -16,6 +16,11 @@ .message-md pre code.hljs { font-size: 0.8rem; + font-family: 'Menlo', 'Monaco', 'Consolas', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Source Han Sans CN', 'Courier New', monospace; + line-height: 1.5; + letter-spacing: 0.025em; + tab-size: 4; + -moz-tab-size: 4; background-color: var(--gray-100); } diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index ee9c491d..11179bc0 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -3,31 +3,33 @@
- - - - + +
+
+
-
- -
@@ -118,10 +120,17 @@ import { import MessageInputComponent from '@/components/MessageInputComponent.vue' import MessageComponent from '@/components/MessageComponent.vue' -// ========= 加载路由参数并获取 agent id ==================== +// 新增props属性,允许父组件传入agentId +const props = defineProps({ + agentId: { + type: String, + default: null + } +}); + +// 移除路由相关逻辑,使用props const route = useRoute(); const router = useRouter(); -const useSingleMode = computed(() => !!route.params.agent_id); // ==================== 状态管理 ==================== @@ -262,7 +271,6 @@ const prepareMessageHistory = (msgs) => { // 选择智能体 const selectAgent = (agentName) => { - if (useSingleMode.value) return; // 单页面模式下不允许切换智能体 currentAgent.value = agents.value[agentName]; messages.value = []; threadId.value = null; @@ -638,8 +646,7 @@ onMounted(async () => { await fetchAgents(); // 检查加载状态 - console.log("单页面模式:", useSingleMode.value); - console.log("路由参数:", route.params.agent_id); + console.log("路由参数:", props.agentId); console.log("智能体列表:", Object.keys(agents.value)); // 初始加载 - 确保使用 Vue Router 的解析后路由 @@ -653,26 +660,6 @@ onMounted(async () => { } }); -// 添加路由参数变化监听 -watch(() => route.params.agent_id, async (newAgentId, oldAgentId) => { - try { - console.log("路由参数变化", oldAgentId, "->", newAgentId); - - // 如果路由参数变化了(包括从有参数变为无参数的情况) - if (oldAgentId !== newAgentId) { - // 重置会话 - messages.value = []; - threadId.value = null; - resetStatusSteps(); - - // 加载新的智能体数据 - await loadAgentData(); - } - } catch (error) { - console.error('路由参数变化处理出错:', error); - } -}, { immediate: true }); - // 处理元数据 const handleMetadata = (data) => { // 更新线程ID @@ -708,8 +695,8 @@ const toggleToolCall = (toolCallId) => { const loadState = () => { try { // 确定存储前缀 - const storagePrefix = useSingleMode.value ? - (route.params.agent_id ? `agent-single-${route.params.agent_id}` : null) : + const storagePrefix = props.agentId ? + `agent-${props.agentId}` : 'agent-multi'; if (!storagePrefix) { @@ -719,17 +706,6 @@ const loadState = () => { console.log("loadState with prefix:", storagePrefix); - // 在单页面模式下,直接从路由参数加载智能体 - if (useSingleMode.value) { - // 智能体已在 loadAgentData 中设置,这里不再需要重复设置 - } else { - // 多智能体模式下,从本地存储加载当前选择的智能体 - const savedAgent = localStorage.getItem(`${storagePrefix}-current-agent`); - if (savedAgent && agents.value && agents.value[savedAgent]) { - currentAgent.value = agents.value[savedAgent]; - } - } - // 加载设置选项 const savedOptions = localStorage.getItem(`${storagePrefix}-options`); if (savedOptions) { @@ -768,6 +744,62 @@ const loadState = () => { } }; +// 监听agentId变化 +watch(() => props.agentId, async (newAgentId, oldAgentId) => { + try { + console.log("智能体ID变化", oldAgentId, "->", newAgentId); + + // 如果变化了,重置会话并加载新数据 + if (newAgentId !== oldAgentId) { + // 重置会话 + messages.value = []; + threadId.value = null; + resetStatusSteps(); + + // 加载新的智能体数据 + await loadAgentData(); + } + } catch (error) { + console.error('智能体ID变化处理出错:', error); + } +}, { immediate: true }); + +// 加载智能体数据的方法 +const loadAgentData = async () => { + try { + // 确保智能体列表已加载 + if (Object.keys(agents.value).length === 0) { + await fetchAgents(); + } + + // 设置当前智能体 + if (props.agentId && agents.value && agents.value[props.agentId]) { + // 如果传入了指定的agentId,就加载对应的智能体 + currentAgent.value = agents.value[props.agentId]; + console.log("设置当前智能体", currentAgent.value.name); + } else if (!props.agentId) { + // 多智能体模式下,尝试从本地存储恢复上次选择的智能体 + const storagePrefix = 'agent-multi'; + const savedAgent = localStorage.getItem(`${storagePrefix}-current-agent`); + if (savedAgent && agents.value && agents.value[savedAgent]) { + currentAgent.value = agents.value[savedAgent]; + console.log("从存储中恢复智能体", currentAgent.value.name); + } + } + + // 加载保存的状态 + loadState(); + + // 处理消息历史 + if (messages.value && messages.value.length > 0) { + console.log("处理消息历史:", messages.value.length); + messages.value = prepareMessageHistory(messages.value); + } + } catch (error) { + console.error('加载智能体数据出错:', error); + } +}; + // 保存状态到localStorage const saveState = () => { try { @@ -777,21 +809,12 @@ const saveState = () => { return; } - // 确定存储前缀 - 确保agent_id总是可用 - let prefix = 'agent-multi'; - if (useSingleMode.value) { - if (route.params.agent_id) { - prefix = `agent-single-${route.params.agent_id}`; - } else { - console.error("保存状态时缺少agent_id"); - return; // 不保存 - } - } - + // 确定存储前缀 + const prefix = props.agentId ? `agent-${props.agentId}` : 'agent-multi'; console.log("saveState with prefix:", prefix); - // 多智能体模式下,保存当前选择的智能体 - if (!useSingleMode.value && currentAgent.value) { + // 如果是多智能体模式,保存当前选择的智能体 + if (!props.agentId && currentAgent.value) { localStorage.setItem(`${prefix}-current-agent`, currentAgent.value.name); } @@ -818,38 +841,6 @@ const saveState = () => { } }; -// 加载智能体数据的方法 -const loadAgentData = async () => { - try { - // 确保智能体列表已加载 - if (Object.keys(agents.value).length === 0) { - await fetchAgents(); - } - - // 在单页面模式下,设置当前智能体 - if (useSingleMode.value && route.params.agent_id) { - const agentId = route.params.agent_id; - if (agents.value && agents.value[agentId]) { - currentAgent.value = agents.value[agentId]; - console.log("设置当前智能体", currentAgent.value.name); - } else { - console.error("未找到指定的智能体:", agentId); - } - } - - // 加载保存的状态 - 在设置好currentAgent后再加载状态 - loadState(); - - // 处理消息历史 - if (messages.value && messages.value.length > 0) { - console.log("处理消息历史:", messages.value.length); - messages.value = prepareMessageHistory(messages.value); - } - } catch (error) { - console.error('加载智能体数据出错:', error); - } -}; - // 监听状态变化并保存 watch([currentAgent, options, messages, threadId], () => { try { diff --git a/web/src/views/AgentSingleView.vue b/web/src/views/AgentSingleView.vue index 49b0ffe5..e9fbc690 100644 --- a/web/src/views/AgentSingleView.vue +++ b/web/src/views/AgentSingleView.vue @@ -1,7 +1,24 @@ + + + + diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index d9405ab7..050b917d 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -1,7 +1,164 @@ + + + + From b216034c07977accbb560242ffe1b5352869b962 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Mon, 31 Mar 2025 23:04:27 +0800 Subject: [PATCH 04/14] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Agent=20?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=9C=A8=E7=A7=BB=E5=8A=A8=E7=AB=AF=E7=9A=84?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/AgentChatComponent.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 11179bc0..b2ebee56 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -1142,10 +1142,6 @@ watch([currentAgent, options, messages, threadId], () => { } @media (max-width: 520px) { - .chat { - height: calc(100vh - 60px); - } - .chat-box { padding: 1rem 1rem; } From a534111002841da554f686b08d9bbb09ee5e9e4d Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 1 Apr 2025 00:50:39 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E4=BC=98=E5=8C=96=20markdown=20=E8=A7=86?= =?UTF-8?q?=E8=A7=89=E8=A1=A8=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/assets/markdown.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/assets/markdown.css b/web/src/assets/markdown.css index 8a742f1f..7acc8984 100644 --- a/web/src/assets/markdown.css +++ b/web/src/assets/markdown.css @@ -10,12 +10,12 @@ padding: 1rem; } -.message-md pre:has(code.hljs) { +.message-md pre:has(code) { padding: 0; } -.message-md pre code.hljs { - font-size: 0.8rem; +.message-md pre code { + font-size: 13px; font-family: 'Menlo', 'Monaco', 'Consolas', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Source Han Sans CN', 'Courier New', monospace; line-height: 1.5; letter-spacing: 0.025em; From 04ee276331202d67e9f62c8889d3e38845ea1cd8 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 1 Apr 2025 22:16:07 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/registry.py | 9 ++ src/routers/chat_router.py | 6 +- web/src/components/AgentChatComponent.vue | 1 + web/src/views/AgentView.vue | 169 +++++++++++++++++++--- 4 files changed, 158 insertions(+), 27 deletions(-) diff --git a/src/agents/registry.py b/src/agents/registry.py index 1db2621d..0da6b8bb 100644 --- a/src/agents/registry.py +++ b/src/agents/registry.py @@ -60,6 +60,15 @@ class BaseAgent(): def __init__(self, **kwargs): self.check_requirements() + @classmethod + def get_info(cls): + return { + "name": cls.name, + "description": cls.description, + "config_schema": cls.config_schema.to_dict(), + "requirements": cls.requirements if hasattr(cls, "requirements") else [], + } + def check_requirements(self): if not hasattr(self, "requirements") or not self.requirements: return diff --git a/src/routers/chat_router.py b/src/routers/chat_router.py index 5c5d8360..e02d49b8 100644 --- a/src/routers/chat_router.py +++ b/src/routers/chat_router.py @@ -124,11 +124,7 @@ async def call(query: str = Body(...), meta: dict = Body(None)): @chat.get("/agent") async def get_agent(): - agents = [{ - "name": agent.name, - "description": agent.description, - "config_schema": agent.config_schema.to_dict() - } for agent in agent_manager.agents.values()] + agents = [agent.get_info() for agent in agent_manager.agents.values()] return {"agents": agents} @chat.post("/agent/{agent_name}") diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index b2ebee56..9a5c5e1a 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -3,6 +3,7 @@
+ diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index 050b917d..72f78ae5 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -1,37 +1,73 @@ @@ -133,6 +317,7 @@ onMounted(async () => { height: 100vh; overflow: hidden; --agent-sidebar-width: 230px; + --config-sidebar-width: 350px; } .sidebar { @@ -150,6 +335,44 @@ onMounted(async () => { } } +// 配置侧边栏样式 +.config-sidebar { + width: 0; + max-width: var(--config-sidebar-width); + border-left: 1px solid var(--main-light-3); + background-color: var(--bg-sider); + box-sizing: content-box; + overflow-y: auto; + transition: width 0.3s ease; + overflow: hidden; + position: relative; + z-index: 100; + + &.is-open { + width: var(--config-sidebar-width); + } + + .config-form { + padding: 16px; + min-width: calc(var(--config-sidebar-width) - 16px); + overflow-y: auto; + max-height: calc(100vh - 100px); + } + + .form-actions { + display: flex; + justify-content: space-between; + margin-top: 20px; + } + + .no-agent-selected { + padding: 16px; + color: var(--gray-500); + text-align: center; + margin-top: 20px; + } +} + .sidebar-title { font-weight: bold; user-select: none; @@ -283,6 +506,21 @@ onMounted(async () => { padding: 16px; } } + + .config-sidebar { + position: absolute; + z-index: 101; + right: 0; + width: 0; + height: 100%; + border-radius: 16px 0 0 16px; + box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.05); + + &.is-open { + width: 90%; + max-width: var(--config-sidebar-width); + } + } } From ff61e4cb48a25c3432a15dfd91426f1067fa6535 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Wed, 2 Apr 2025 13:00:25 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E8=B6=85=E7=BA=A7=E5=A4=A7=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/chatbot/configuration.py | 64 +++----- src/agents/chatbot/graph.py | 30 +++- src/agents/registry.py | 31 +++- src/agents/utils.py | 12 +- src/config/__init__.py | 2 +- src/routers/chat_router.py | 22 ++- web/src/components/AgentChatComponent.vue | 168 ++++++++++--------- web/src/components/MessageComponent.vue | 6 +- web/src/components/RefsComponent.vue | 23 ++- web/src/views/AgentView.vue | 190 ++++++++++++---------- 10 files changed, 303 insertions(+), 245 deletions(-) diff --git a/src/agents/chatbot/configuration.py b/src/agents/chatbot/configuration.py index af94792e..fbdb68bc 100644 --- a/src/agents/chatbot/configuration.py +++ b/src/agents/chatbot/configuration.py @@ -1,62 +1,36 @@ from dataclasses import dataclass, field -from datetime import datetime, timezone - -from langchain_community.tools.tavily_search import TavilySearchResults from src.agents.registry import Configuration -from src.agents.tools_factory import multiply, add, subtract, divide - - - -def get_default_tools(): - return ["TavilySearchResults", "multiply", "add", "subtract", "divide"] @dataclass(kw_only=True) class ChatbotConfiguration(Configuration): - """Chatbot 的配置""" + """Chatbot 的配置 + + 配置说明: + + metadata 中 configurable 为 True 的配置项可以被用户配置, + configurable 为 False 的配置项不能被用户配置,只能由开发者预设。 + """ system_prompt: str = field( - default=f"You are a helpful assistant. Now is {datetime.now(tz=timezone.utc).isoformat()}", + default="You are a helpful assistant.", metadata={ - "description": "The system prompt to use for the agent's interactions. " - "This prompt sets the context and behavior for the agent." + "name": "系统提示词", + "configurable": True, + "description": "用来描述智能体的角色和行为" }, ) 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." + "name": "智能体模型", + "configurable": True, + "options": [ + "zhipu/glm-4-plus", + "siliconflow/Qwen/QwQ-32B", + "siliconflow/deepseek-ai/DeepSeek-V3", + ], + "description": "智能体的驱动模型" }, ) - - tools: list = field( - default_factory=get_default_tools, - metadata={ - "description": "The tools to use for the agent's interactions. " - "Should be in the form: provider/model-name." - }, - ) - - temperature: float = field( - default=0.7, - metadata={ - "description": "控制模型生成结果的随机性,值越大随机性越高,建议范围 0.0-1.0" - }, - ) - - use_tools: bool = field( - default=True, - metadata={ - "description": "是否启用工具调用功能" - }, - ) - - max_iterations: int = field( - default=10, - metadata={ - "description": "智能体最大执行步数,防止无限循环" - }, - ) - diff --git a/src/agents/chatbot/graph.py b/src/agents/chatbot/graph.py index e2df88fe..1d4162ce 100644 --- a/src/agents/chatbot/graph.py +++ b/src/agents/chatbot/graph.py @@ -1,7 +1,7 @@ import asyncio import uuid from typing import Any -from datetime import datetime +from datetime import datetime, timezone from langchain_core.runnables import RunnableConfig from langgraph.graph import StateGraph, START, END @@ -9,8 +9,9 @@ from langgraph.prebuilt import ToolNode, tools_condition from langgraph.checkpoint.memory import MemorySaver # 实际上没有起作用 +from src.utils import logger from src.agents.registry import State, BaseAgent -from src.agents.utils import load_chat_model +from src.agents.utils import load_chat_model, get_cur_time_with_utc from src.agents.chatbot.configuration import ChatbotConfiguration from src.agents.tools_factory import _TOOLS_REGISTRY @@ -18,27 +19,38 @@ class ChatbotAgent(BaseAgent): name = "chatbot" description = "A chatbot that can answer questions and help with tasks." requirements = ["TAVILY_API_KEY", "ZHIPUAI_API_KEY"] - _graph_cache = None + all_tools = ["TavilySearchResults", "multiply", "add", "subtract", "divide"] config_schema = ChatbotConfiguration def __init__(self, **kwargs): super().__init__(**kwargs) def _get_tools(self, config_schema: RunnableConfig): - """根据配置获取工具""" - default_tools_names = config_schema.get("tools", []) - default_tools = [_TOOLS_REGISTRY[tool] for tool in default_tools_names] - return default_tools + """根据配置获取工具,如果配置为空,则使用所有工具,如果配置为列表,则使用列表中的工具, + 如果配置为其他类型,则抛出错误""" + conf_tools = config_schema.get("tools") + if conf_tools == None: + tool_names = self.all_tools + elif isinstance(conf_tools, list): + tool_names = [tool for tool in self.all_tools if tool in conf_tools] + else: + raise ValueError(f"tools 配置错误: {conf_tools}") + + logger.info(f"Tools: {tool_names}") + return [_TOOLS_REGISTRY[tool] for tool in tool_names] def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]: """调用 llm 模型""" config_schema = config or {} conf = self.config_schema.from_runnable_config(config_schema) - model = load_chat_model(conf.model, temperature=conf.temperature) + + system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}" + model = load_chat_model(conf.model) model_with_tools = model.bind_tools(self._get_tools(config_schema)) + logger.info(f"llm_call with config: {conf}, {conf.model}") res = model_with_tools.invoke( - [{"role": "system", "content": conf.system_prompt}, *state["messages"]] + [{"role": "system", "content": system_prompt}, *state["messages"]] ) return {"messages": [res]} diff --git a/src/agents/registry.py b/src/agents/registry.py index 7d3c89bf..dc974bd7 100644 --- a/src/agents/registry.py +++ b/src/agents/registry.py @@ -12,7 +12,7 @@ from langgraph.graph.state import CompiledStateGraph from langgraph.graph.message import add_messages from src.config import SimpleConfig - +from src.utils import logger class State(TypedDict): """ @@ -26,7 +26,7 @@ class State(TypedDict): @dataclass(kw_only=True) -class Configuration(SimpleConfig): +class Configuration(dict): """ 定义一个基础 Configuration 供 各类 graph 继承 """ @@ -44,7 +44,26 @@ class Configuration(SimpleConfig): def to_dict(cls): # 创建一个实例来处理 default_factory instance = cls() - return {f.name: getattr(instance, f.name) for f in fields(cls) if f.init} + confs = {} + configurable_items = {} + for f in fields(cls): + if f.init and not f.metadata.get("hide", False): + value = getattr(instance, f.name) + if callable(value) and hasattr(value, "__call__"): + confs[f.name] = value() + else: + confs[f.name] = value + + if f.metadata.get("configurable"): + configurable_items[f.name] = { + "type": f.type.__name__, + "name": f.metadata.get("name", f.name), + "options": f.metadata.get("options", []), + "default": f.default, + "description": f.metadata.get("description", ""), + } + confs["configurable_items"] = configurable_items + return confs @@ -69,6 +88,7 @@ class BaseAgent(): "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 [], } def check_requirements(self): @@ -85,13 +105,8 @@ class BaseAgent(): def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs): graph = self.get_graph(config_schema=config_schema, **kwargs) - conf = self.config_schema.from_runnable_config(config_schema) for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema): - # msg_type = msg.type - # return_keys = conf.get("return_keys", []) - # if not return_keys or msg_type in return_keys: - # yield msg, metadata yield msg, metadata @abstractmethod diff --git a/src/agents/utils.py b/src/agents/utils.py index b9401860..1d08636d 100644 --- a/src/agents/utils.py +++ b/src/agents/utils.py @@ -1,3 +1,5 @@ +from datetime import datetime, timezone + from src.models import select_model from src.agents.registry import BaseAgent from langchain_core.language_models import BaseChatModel @@ -17,12 +19,6 @@ def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel: provider, model = fully_specified_name.split("/", maxsplit=1) model_instance = select_model(model_name=model, model_provider=provider) - # 配置额外参数,如temperature - if kwargs and hasattr(model_instance, 'chat_open_ai'): - for key, value in kwargs.items(): - if value is not None: - setattr(model_instance.chat_open_ai, key, value) - return model_instance.chat_open_ai @@ -58,3 +54,7 @@ def agent_cli(agent: BaseAgent, config: RunnableConfig = None): if isinstance(msg, ToolMessage): print(f"Tool: {msg.content}") + +def get_cur_time_with_utc(): + return datetime.now(tz=timezone.utc).isoformat() + diff --git a/src/config/__init__.py b/src/config/__init__.py index 372e6305..b9fc17e2 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -28,7 +28,7 @@ class SimpleConfig(dict): return self.get(self.__key(key)) def __getitem__(self, key): - return super().get(self.__key(key)) + return self.get(self.__key(key)) def __setitem__(self, key, value): return super().__setitem__(self.__key(key), value) diff --git a/src/routers/chat_router.py b/src/routers/chat_router.py index 3c7c6d25..d2d0becf 100644 --- a/src/routers/chat_router.py +++ b/src/routers/chat_router.py @@ -131,20 +131,22 @@ async def get_agent(): def chat_agent(agent_name: str, query: str = Body(...), history: list = Body(...), - config: dict = Body({})): + config: dict = Body({}), + meta: dict = Body({})): + + meta.update({ + "query": query, + "agent_name": agent_name, + "server_model_name": config["model"] , + "thread_id": config.get("thread_id"), + }) # 将meta和thread_id整合到config中 def make_chunk(content=None, **kwargs): - chat_metadata = { - "agent_name": agent_name, - "thread_id": config.get("thread_id"), - } - if update_metadata := kwargs.get("chat_metadata"): - chat_metadata.update(update_metadata) return json.dumps({ + "request_id": meta.get("request_id"), "response": content, - "chat_metadata": chat_metadata, **kwargs }, ensure_ascii=False).encode('utf-8') + b"\n" @@ -186,7 +188,9 @@ def chat_agent(agent_name: str, metadata=metadata, status="loading") - yield make_chunk(status="finished", history=history_manager.update_ai(content)) + yield make_chunk(status="finished", + history=history_manager.update_ai(content), + meta=meta) return StreamingResponse(stream_messages(), media_type='application/json') diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 5db16ca7..83c27765 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -26,15 +26,16 @@

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

-
+
-
{{ message }}
+
{{ message }}
@@ -145,6 +169,7 @@ import { } from '@ant-design/icons-vue'; import { message } from 'ant-design-vue'; import AgentChatComponent from '@/components/AgentChatComponent.vue'; +import TokenManagerComponent from '@/components/TokenManagerComponent.vue'; // 状态 const agents = ref({}); @@ -153,6 +178,7 @@ const state = reactive({ debug_mode: false, isSidebarOpen: JSON.parse(localStorage.getItem('agent-sidebar-open') || 'true'), isConfigSidebarOpen: false, + configModalVisible: false, isEmptyConfig: computed(() => !selectedAgentId.value || Object.keys(configurableItems.value).length === 0 @@ -169,6 +195,16 @@ const toggleDebugMode = () => { state.debug_mode = !state.debug_mode; }; +// 打开配置弹窗 +const openConfigModal = () => { + state.configModalVisible = true; +}; + +// 关闭配置弹窗 +const closeConfigModal = () => { + state.configModalVisible = false; +}; + // 根据选中的智能体加载配置 const loadAgentConfig = () => { // BUG: 目前消息重置有问题,需要重置消息 @@ -222,6 +258,7 @@ const saveConfig = () => { // 提示保存成功 message.success('配置已保存'); + closeConfigModal(); }; // 重置配置 @@ -379,18 +416,13 @@ const getPlaceholder = (key, value) => { overflow-y: auto; max-height: calc(100vh - 100px); - .description { - font-size: 12px; - color: var(--gray-700); + .token-section { + margin-top: 1.5rem; + border-top: 1px solid var(--main-light-3); + padding-top: 1rem; } } - .form-actions { - display: flex; - justify-content: space-between; - margin-top: 20px; - } - .no-agent-selected { padding: 16px; color: var(--gray-500); @@ -548,6 +580,23 @@ const getPlaceholder = (key, value) => { } } } + +.config-modal-content { + max-height: 70vh; + overflow-y: auto; + + .description { + font-size: 12px; + color: var(--gray-700); + } + + .form-actions { + display: flex; + justify-content: space-between; + margin-top: 20px; + gap: 10px; + } +} From b51b8de4603508574437e467f3b0f7088b7eab06 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Thu, 3 Apr 2025 20:45:58 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20token=20=E9=AA=8C?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routers/admin_router.py | 22 ++++++++- src/routers/chat_router.py | 32 ------------- web/src/components/TokenManagerComponent.vue | 11 +++-- web/src/router/index.js | 2 +- web/src/views/AgentSingleView.vue | 50 ++++++++++++++++---- web/src/views/AgentView.vue | 36 ++++++++++++-- 6 files changed, 102 insertions(+), 51 deletions(-) diff --git a/src/routers/admin_router.py b/src/routers/admin_router.py index 3f06d46b..725ca49f 100644 --- a/src/routers/admin_router.py +++ b/src/routers/admin_router.py @@ -23,6 +23,10 @@ class TokenCreate(BaseModel): agent_id: str name: str +class TokenVerify(BaseModel): + agent_id: str + token: str + class TokenResponse(BaseModel): id: int agent_id: str @@ -80,4 +84,20 @@ async def delete_token(token_id: int, db: Session = Depends(get_db)): db.delete(token) db.commit() - return {"success": True, "message": "Token deleted"} \ No newline at end of file + return {"success": True, "message": "Token deleted"} + +@admin.post("/verify_token") +async def verify_agent_token( + token_data: TokenVerify, + db: Session = Depends(get_db) +): + """验证智能体访问令牌""" + token = db.query(AgentToken).filter( + AgentToken.agent_id == token_data.agent_id, + AgentToken.token == token_data.token + ).first() + + if not token: + raise HTTPException(status_code=401, detail="Invalid token") + + return {"success": True, "message": "Token verified"} \ No newline at end of file diff --git a/src/routers/chat_router.py b/src/routers/chat_router.py index 8f8bda39..0bd56ca5 100644 --- a/src/routers/chat_router.py +++ b/src/routers/chat_router.py @@ -6,47 +6,15 @@ import uuid from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.responses import StreamingResponse from langchain_core.messages import AIMessageChunk -from pydantic import BaseModel -from sqlalchemy.orm import Session from src import executor, config, retriever from src.core import HistoryManager from src.agents import agent_manager from src.models import select_model from src.utils.logging_config import logger -from src.utils.db_manager import db_manager -from src.models.token_model import AgentToken chat = APIRouter(prefix="/chat") -# 依赖项:获取数据库会话 -def get_db(): - db = db_manager.get_session() - try: - yield db - finally: - db.close() - -class TokenVerify(BaseModel): - agent_id: str - token: str - -@chat.post("/verify_token") -async def verify_agent_token( - token_data: TokenVerify, - db: Session = Depends(get_db) -): - """验证智能体访问令牌""" - token = db.query(AgentToken).filter( - AgentToken.agent_id == token_data.agent_id, - AgentToken.token == token_data.token - ).first() - - if not token: - raise HTTPException(status_code=401, detail="Invalid token") - - return {"success": True, "message": "Token verified"} - @chat.get("/") async def chat_get(): return "Chat Get!" diff --git a/web/src/components/TokenManagerComponent.vue b/web/src/components/TokenManagerComponent.vue index cb84d2e6..bcf6adb7 100644 --- a/web/src/components/TokenManagerComponent.vue +++ b/web/src/components/TokenManagerComponent.vue @@ -1,9 +1,8 @@