feat(agent): 添加智能体配置保存时的graph重载功能

在保存智能体配置时新增reload_graph选项,用于强制清空graph缓存并重新构建
修改前后端接口以支持该功能,并在UI中添加相应提示
为DeepAgent添加subagents_model配置项,优化子智能体模型选择
This commit is contained in:
Wenjie Zhang 2025-12-28 22:55:42 +08:00
parent 0d87c1755e
commit da351df193
9 changed files with 59 additions and 16 deletions

View File

@ -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="保存智能体配置失败")

View File

@ -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):

View File

@ -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:
"""

View File

@ -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).",
},
)

View File

@ -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,

View File

@ -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)
},
/**

View File

@ -55,7 +55,7 @@
<!-- 模型选择 -->
<div v-if="value.template_metadata.kind === 'llm'" class="model-selector">
<ModelSelectorComponent
@select-model="handleModelChange"
@select-model="(spec) => handleModelChange(key, spec)"
:model_spec="agentConfig[key] || ''"
/>
</div>
@ -219,7 +219,7 @@
<div class="sidebar-footer" v-if="!isEmptyConfig">
<div class="form-actions">
<a-button @click="saveConfig" class="save-btn" :class="{'changed': agentStore.hasConfigChanges}">
保存配置
{{ needsReload ? '保存配置并重新加载' : '保存配置' }}
</a-button>
</div>
</div>
@ -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);

View File

@ -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');

View File

@ -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)