From da351df193ca3f923207223e32937dc449856e3c Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Sun, 28 Dec 2025 22:55:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E6=B7=BB=E5=8A=A0=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E4=BD=93=E9=85=8D=E7=BD=AE=E4=BF=9D=E5=AD=98=E6=97=B6?= =?UTF-8?q?=E7=9A=84graph=E9=87=8D=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在保存智能体配置时新增reload_graph选项,用于强制清空graph缓存并重新构建 修改前后端接口以支持该功能,并在UI中添加相应提示 为DeepAgent添加subagents_model配置项,优化子智能体模型选择 --- server/routers/chat_router.py | 11 ++++++++-- src/agents/__init__.py | 6 +++++- src/agents/common/base.py | 5 +++++ src/agents/deep_agent/context.py | 8 +++++++ src/agents/deep_agent/graph.py | 9 +++++--- web/src/apis/agent_api.js | 7 +++++-- web/src/components/AgentConfigSidebar.vue | 21 ++++++++++++++----- .../ToolCallingResult/ToolCallRenderer.vue | 1 + web/src/stores/agent.js | 7 ++++--- 9 files changed, 59 insertions(+), 16 deletions(-) diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index 5ac3b2de..dfaa9cc6 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -3,7 +3,7 @@ import json import traceback import uuid -from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, File +from fastapi import APIRouter, Body, Depends, HTTPException, Query, UploadFile, File from fastapi.responses import StreamingResponse from langchain.messages import AIMessageChunk, HumanMessage from langgraph.types import Command @@ -849,7 +849,12 @@ async def resume_agent_chat( @chat.post("/agent/{agent_id}/config") -async def save_agent_config(agent_id: str, config: dict = Body(...), current_user: User = Depends(get_required_user)): +async def save_agent_config( + agent_id: str, + config: dict = Body(...), + reload_graph: bool = Query(False), + current_user: User = Depends(get_required_user), +): """保存智能体配置到YAML文件(需要登录)""" try: # 获取Agent实例和配置类 @@ -860,6 +865,8 @@ async def save_agent_config(agent_id: str, config: dict = Body(...), current_use result = agent.context_schema.save_to_file(config, agent.module_name) if result: + if reload_graph: + agent_manager.get_agent(agent_id, reload_graph=True) return {"success": True, "message": f"智能体 {agent.name} 配置已保存"} else: raise HTTPException(status_code=500, detail="保存智能体配置失败") diff --git a/src/agents/__init__.py b/src/agents/__init__.py index 210ed251..89bbe0ab 100644 --- a/src/agents/__init__.py +++ b/src/agents/__init__.py @@ -20,12 +20,16 @@ class AgentManager(metaclass=SingletonMeta): for agent_id in self._classes.keys(): self.get_agent(agent_id) - def get_agent(self, agent_id, reload=False, **kwargs): + def get_agent(self, agent_id, reload=False, reload_graph=False, **kwargs): # 检查是否已经创建了该 agent 的实例 if reload or agent_id not in self._instances: agent_class = self._classes[agent_id] self._instances[agent_id] = agent_class() + # 如果仅需要重新加载 graph,则清空 graph 缓存 + if reload_graph and agent_id in self._instances: + self._instances[agent_id].reload_graph() + return self._instances[agent_id] def get_agents(self): diff --git a/src/agents/common/base.py b/src/agents/common/base.py index d67fe8c2..25a61b9a 100644 --- a/src/agents/common/base.py +++ b/src/agents/common/base.py @@ -132,6 +132,11 @@ class BaseAgent: logger.error(f"获取智能体 {self.name} 历史消息出错: {e}") return [] + def reload_graph(self): + """重置 graph 缓存,强制下次调用 get_graph 时重新构建""" + self.graph = None + logger.info(f"{self.name} graph 缓存已清空,将在下次调用时重新构建") + @abstractmethod async def get_graph(self, **kwargs) -> CompiledStateGraph: """ diff --git a/src/agents/deep_agent/context.py b/src/agents/deep_agent/context.py index 8fa18959..34e957cf 100644 --- a/src/agents/deep_agent/context.py +++ b/src/agents/deep_agent/context.py @@ -1,6 +1,7 @@ """Deep Agent Context - 基于BaseContext的深度分析上下文配置""" from dataclasses import dataclass, field +from typing import Annotated from src.agents.common.context import BaseContext @@ -102,3 +103,10 @@ class DeepContext(BaseContext): default=DEEP_PROMPT, metadata={"name": "系统提示词", "description": "Deep智能体的角色和行为指导"}, ) + subagents_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field( + default="siliconflow/deepseek-ai/DeepSeek-V3.2", + metadata={ + "name": "Sub-agent Model", + "description": "The model used by sub-agents (e.g., critique-agent, research-agent).", + }, + ) diff --git a/src/agents/deep_agent/graph.py b/src/agents/deep_agent/graph.py index f9c5847d..d59f1482 100644 --- a/src/agents/deep_agent/graph.py +++ b/src/agents/deep_agent/graph.py @@ -10,6 +10,7 @@ from src.agents.common import BaseAgent, load_chat_model from src.agents.common.middlewares import context_based_model, inject_attachment_context from src.agents.common.tools import search +from .context import DeepContext from .prompts import DEEP_PROMPT search_tools = [search] @@ -57,10 +58,12 @@ def context_aware_prompt(request: ModelRequest) -> str: class DeepAgent(BaseAgent): name = "深度分析智能体" description = "具备规划、深度分析和子智能体协作能力的智能体,可以处理复杂的多步骤任务" + context_schema = DeepContext capabilities = [ "file_upload", "todo", "files", + "reload_graph", ] def __init__(self, **kwargs): @@ -82,6 +85,7 @@ class DeepAgent(BaseAgent): context = self.context_schema.from_file(module_name=self.module_name) model = load_chat_model(context.model) + sub_model = load_chat_model(context.subagents_model) tools = await self.get_tools() # 使用 create_deep_agent 创建深度智能体 @@ -95,15 +99,14 @@ class DeepAgent(BaseAgent): TodoListMiddleware(), FilesystemMiddleware(), SubAgentMiddleware( - default_model=load_chat_model(context.model), + default_model=sub_model, default_tools=tools, subagents=[critique_sub_agent, research_sub_agent], default_middleware=[ - context_based_model, # 动态模型选择 TodoListMiddleware(), FilesystemMiddleware(), SummarizationMiddleware( - model=model, + model=sub_model, trigger=("tokens", 110000), keep=("messages", 10), trim_tokens_to_summarize=None, diff --git a/web/src/apis/agent_api.js b/web/src/apis/agent_api.js index 089ad9e1..ef89ca6e 100644 --- a/web/src/apis/agent_api.js +++ b/web/src/apis/agent_api.js @@ -126,10 +126,13 @@ export const agentApi = { * 保存智能体配置 * @param {string} agentName - 智能体名称 * @param {Object} config - 配置对象 + * @param {Object} options - 额外参数 (e.g., { reload_graph: true }) * @returns {Promise} - 保存结果 */ - saveAgentConfig: async (agentName, config) => { - return apiAdminPost(`/api/chat/agent/${agentName}/config`, config) + saveAgentConfig: async (agentName, config, options = {}) => { + const queryParams = new URLSearchParams(options).toString(); + const url = `/api/chat/agent/${agentName}/config` + (queryParams ? `?${queryParams}` : ''); + return apiAdminPost(url, config) }, /** diff --git a/web/src/components/AgentConfigSidebar.vue b/web/src/components/AgentConfigSidebar.vue index d742ff8e..4e8bdd16 100644 --- a/web/src/components/AgentConfigSidebar.vue +++ b/web/src/components/AgentConfigSidebar.vue @@ -55,7 +55,7 @@
@@ -219,7 +219,7 @@ @@ -333,6 +333,12 @@ const isEmptyConfig = computed(() => { return !selectedAgentId.value || Object.keys(configurableItems.value).length === 0; }); +const needsReload = computed(() => { + return selectedAgent.value && + selectedAgent.value.capabilities && + selectedAgent.value.capabilities.includes('reload_graph'); +}); + const filteredTools = computed(() => { const toolsList = availableTools.value ? Object.values(availableTools.value) : []; if (!toolsSearchText.value) { @@ -363,10 +369,10 @@ const getPlaceholder = (key, value) => { return `(默认: ${value.default})`; }; -const handleModelChange = (spec) => { +const handleModelChange = (key, spec) => { if (typeof spec !== 'string' || !spec) return; agentStore.updateAgentConfig({ - model: spec + [key]: spec }); }; @@ -529,7 +535,12 @@ const saveConfig = async () => { message.info('检测到无效配置项,已自动过滤'); } - await agentStore.saveAgentConfig(); + const options = {}; + if (selectedAgent.value && selectedAgent.value.capabilities && selectedAgent.value.capabilities.includes('reload_graph')) { + options.reload_graph = true; + } + + await agentStore.saveAgentConfig(options); message.success('配置已保存到服务器'); } catch (error) { console.error('保存配置到服务器出错:', error); diff --git a/web/src/components/ToolCallingResult/ToolCallRenderer.vue b/web/src/components/ToolCallingResult/ToolCallRenderer.vue index 717bb254..aed9301a 100644 --- a/web/src/components/ToolCallingResult/ToolCallRenderer.vue +++ b/web/src/components/ToolCallingResult/ToolCallRenderer.vue @@ -88,6 +88,7 @@ const isTaskResult = computed(() => { const isKnowledgeBaseResult = computed(() => { const currentTool = tool.value; + if (currentTool && currentTool.metadata) { const metadata = currentTool.metadata; const hasKnowledgebaseTag = metadata.tag && metadata.tag.includes('knowledgebase'); diff --git a/web/src/stores/agent.js b/web/src/stores/agent.js index 61c92ff6..3e55469e 100644 --- a/web/src/stores/agent.js +++ b/web/src/stores/agent.js @@ -249,13 +249,14 @@ export const useAgentStore = defineStore('agent', () => { /** * 保存智能体配置 + * @param {Object} options - 额外参数 (e.g., { reload_graph: true }) */ - async function saveAgentConfig(agentId = null) { - const targetAgentId = agentId || selectedAgentId.value + async function saveAgentConfig(options = {}) { + const targetAgentId = selectedAgentId.value if (!targetAgentId) return try { - await agentApi.saveAgentConfig(targetAgentId, agentConfig.value) + await agentApi.saveAgentConfig(targetAgentId, agentConfig.value, options) originalAgentConfig.value = { ...agentConfig.value } } catch (err) { console.error('Failed to save agent config:', err)