feat: Agent配置可以同步在用户侧了,用户侧会使用 config file 中的配置
This commit is contained in:
parent
d0ded4d30f
commit
b01abfcf71
@ -49,6 +49,7 @@ SILICONFLOW_API_KEY=sk-270ea********8bfa97.e3XOMd****Q1Sk
|
||||
OPENAI_API_KEY=<API_KEY> # 如果需要配置 openai 则添加此行,并替换 API_KEY
|
||||
DEEPSEEK_API_KEY=<API_KEY> # 如果配置 DeepSeek 添加此行,并替换 API_KEY
|
||||
ZHIPUAI_API_KEY=<API_KEY> # 如果配置 智谱清言 添加此行,并替换 API_KEY
|
||||
TAVILY_API_KEY=<TAVILY_API_KEY> # 联网搜索需要配置
|
||||
```
|
||||
|
||||
需要确保账户有一点点额度供调用,或使用这个链接注册[SiliconFlow 注册(含邀请码)](https://cloud.siliconflow.cn/i/Eo5yTHGJ)获得 14 元的赠送额度。
|
||||
|
||||
@ -184,8 +184,6 @@ def chat_agent(agent_name: str,
|
||||
**kwargs
|
||||
}, ensure_ascii=False).encode('utf-8') + b"\n"
|
||||
|
||||
|
||||
|
||||
def stream_messages():
|
||||
|
||||
# 代表服务端已经收到了请求
|
||||
@ -251,3 +249,47 @@ async def update_chat_models(model_provider: str, model_names: list[str], curren
|
||||
async def get_tools(current_user: User = Depends(get_admin_user)):
|
||||
"""获取所有可用工具(需要登录)"""
|
||||
return {"tools": list(get_all_tools().keys())}
|
||||
|
||||
@chat.post("/agent/{agent_name}/config")
|
||||
async def save_agent_config(
|
||||
agent_name: str,
|
||||
config: dict = Body(...),
|
||||
current_user: User = Depends(get_admin_user)
|
||||
):
|
||||
"""保存智能体配置到YAML文件(需要管理员权限)"""
|
||||
try:
|
||||
# 获取Agent实例和配置类
|
||||
agent = agent_manager.get_agent(agent_name)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_name} 不存在")
|
||||
|
||||
# 使用配置类的save_to_file方法保存配置
|
||||
config_cls = agent.config_schema
|
||||
result = config_cls.save_to_file(config, agent_name)
|
||||
|
||||
if result:
|
||||
return {"success": True, "message": f"智能体 {agent_name} 配置已保存"}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail=f"保存智能体配置失败")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"保存智能体配置出错: {e}, {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"保存智能体配置出错: {str(e)}")
|
||||
|
||||
@chat.get("/agent/{agent_name}/config")
|
||||
async def get_agent_config(
|
||||
agent_name: str,
|
||||
current_user: User = Depends(get_admin_user)
|
||||
):
|
||||
"""从YAML文件加载智能体配置(需要管理员权限)"""
|
||||
try:
|
||||
# 检查智能体是否存在
|
||||
if not (agent := agent_manager.get_agent(agent_name)):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_name} 不存在")
|
||||
|
||||
config = agent.config_schema.from_runnable_config(config={}, agent_name=agent_name)
|
||||
return {"success": True, "config": config}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"加载智能体配置出错: {e}, {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"加载智能体配置出错: {str(e)}")
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from src.agents.registry import Configuration
|
||||
from src.agents.tools_factory import get_all_tools
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ChatbotConfiguration(Configuration):
|
||||
@ -39,7 +40,8 @@ class ChatbotConfiguration(Configuration):
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "工具",
|
||||
"configurable": False,
|
||||
"configurable": True,
|
||||
"options": list(get_all_tools().keys()), # 这里的选择是所有的工具
|
||||
"description": "工具列表"
|
||||
},
|
||||
)
|
||||
|
||||
@ -42,8 +42,7 @@ class ChatbotAgent(BaseAgent):
|
||||
|
||||
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)
|
||||
conf = self.config_schema.from_runnable_config(config, agent_name=self.name)
|
||||
|
||||
system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}"
|
||||
model = load_chat_model(conf.model)
|
||||
@ -57,7 +56,7 @@ class ChatbotAgent(BaseAgent):
|
||||
|
||||
def get_graph(self, config_schema: RunnableConfig = None, **kwargs):
|
||||
"""构建图"""
|
||||
conf = self.config_schema.from_runnable_config(config_schema)
|
||||
conf = self.config_schema.from_runnable_config(config_schema, agent_name=self.name)
|
||||
workflow = StateGraph(State, config_schema=self.config_schema)
|
||||
workflow.add_node("chatbot", self.llm_call)
|
||||
workflow.add_node("tools", ToolNode(tools=self._get_tools(conf.tools)))
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import Type, Annotated, Optional, TypedDict
|
||||
from enum import Enum
|
||||
from abc import abstractmethod
|
||||
@ -30,16 +31,95 @@ class State(TypedDict):
|
||||
class Configuration(dict):
|
||||
"""
|
||||
定义一个基础 Configuration 供 各类 graph 继承
|
||||
|
||||
配置优先级:
|
||||
1. 运行时配置(RunnableConfig):最高优先级,直接从函数参数传入
|
||||
2. 文件配置(config.private.yaml):中等优先级,从文件加载
|
||||
3. 类默认配置:最低优先级,类中定义的默认值
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_runnable_config(
|
||||
cls, config: Optional[RunnableConfig] = None
|
||||
cls, config: Optional[RunnableConfig] = None, agent_name: str = None
|
||||
) -> Configuration:
|
||||
"""Create a Configuration instance from a RunnableConfig object."""
|
||||
configurable = (config.get("configurable") or {}) if config else {}
|
||||
"""Create a Configuration instance from a RunnableConfig object.
|
||||
|
||||
Args:
|
||||
config: RunnableConfig object with highest priority
|
||||
agent_name: Name of the agent to load config file for
|
||||
|
||||
Returns:
|
||||
Configuration instance with merged config values
|
||||
"""
|
||||
# 获取类默认配置:创建一个实例获取所有默认值
|
||||
instance = cls()
|
||||
_fields = {f.name for f in fields(cls) if f.init}
|
||||
return cls(**{k: v for k, v in configurable.items() if k in _fields})
|
||||
|
||||
# 尝试加载文件配置(中等优先级)
|
||||
file_config = {}
|
||||
if agent_name:
|
||||
file_config = cls.from_file(agent_name)
|
||||
|
||||
# 获取运行时配置(最高优先级)
|
||||
configurable = (config.get("configurable") or {}) if config else {}
|
||||
|
||||
# 合并三级配置,注意优先级
|
||||
merged_config = {}
|
||||
for field in _fields:
|
||||
# 1. 默认使用类默认值
|
||||
if hasattr(instance, field):
|
||||
merged_config[field] = getattr(instance, field)
|
||||
|
||||
# 2. 如果文件配置中有此字段,则覆盖
|
||||
if field in file_config:
|
||||
merged_config[field] = file_config[field]
|
||||
|
||||
# 3. 如果运行时配置中有此字段,则覆盖
|
||||
if field in configurable:
|
||||
merged_config[field] = configurable[field]
|
||||
|
||||
# 创建并返回配置实例
|
||||
# logger.debug(f"合并配置: {merged_config}")
|
||||
return cls(**merged_config)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, agent_name: str) -> Configuration:
|
||||
"""从文件加载配置"""
|
||||
config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml")
|
||||
file_config = {}
|
||||
if os.path.exists(config_file_path):
|
||||
try:
|
||||
with open(config_file_path, 'r', encoding='utf-8') as f:
|
||||
file_config = yaml.safe_load(f) or {}
|
||||
# logger.info(f"从文件加载智能体 {agent_name} 配置: {file_config}")
|
||||
except Exception as e:
|
||||
logger.error(f"加载智能体配置文件出错: {e}")
|
||||
|
||||
return file_config
|
||||
|
||||
@classmethod
|
||||
def save_to_file(cls, config: dict, agent_name: str) -> bool:
|
||||
"""Save configuration to a YAML file
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary to save
|
||||
agent_name: Name of the agent to save config for
|
||||
|
||||
Returns:
|
||||
True if saving was successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml")
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(config_file_path), exist_ok=True)
|
||||
with open(config_file_path, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(config, f, indent=2, allow_unicode=True)
|
||||
|
||||
# logger.info(f"智能体 {agent_name} 配置已保存到 {config_file_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"保存智能体配置文件出错: {e}")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def to_dict(cls):
|
||||
|
||||
@ -120,7 +120,7 @@ class BaseToolOutput:
|
||||
|
||||
@tool
|
||||
def calculator(a: float, b: float, operation: str) -> float:
|
||||
"""Calculate two numbers."""
|
||||
"""Calculate two numbers. operation: add, subtract, multiply, divide"""
|
||||
if operation == "add":
|
||||
return a + b
|
||||
elif operation == "subtract":
|
||||
|
||||
@ -289,6 +289,26 @@ export const systemConfigApi = {
|
||||
return apiGet('/api/config', {}, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取智能体配置
|
||||
* @param {string} agentId - 智能体ID
|
||||
* @returns {Promise} - 智能体配置
|
||||
*/
|
||||
getAgentConfig: async (agentId) => {
|
||||
checkAdminPermission()
|
||||
return apiGet(`/api/chat/agent/${agentId}/config`, {}, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存智能体配置
|
||||
* @param {string} agentId - 智能体ID
|
||||
* @param {Object} config - 配置内容
|
||||
* @returns {Promise} - 保存结果
|
||||
*/
|
||||
saveAgentConfig: async (agentId, config) => {
|
||||
checkAdminPermission()
|
||||
return apiPost(`/api/chat/agent/${agentId}/config`, config, {}, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新某个配置
|
||||
@ -322,4 +342,27 @@ export const logApi = {
|
||||
checkAdminPermission()
|
||||
return apiGet('/api/log', { params }, true)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 通用admin
|
||||
export const adminApi = {
|
||||
/**
|
||||
* 获取所有智能体
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} - 查询结果
|
||||
*/
|
||||
adminGet: async (params, url) => {
|
||||
checkAdminPermission()
|
||||
return apiGet(url, { params }, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新某个配置
|
||||
* @param {Object} items - 配置项
|
||||
* @returns {Promise} - 更新结果
|
||||
*/
|
||||
adminPost: async (data, url) => {
|
||||
checkAdminPermission()
|
||||
return apiPost(url, data, {}, true)
|
||||
},
|
||||
}
|
||||
|
||||
@ -156,11 +156,18 @@
|
||||
:placeholder="getPlaceholder(key, value)"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="value?.options"
|
||||
v-else-if="value?.options && value?.type === 'str'"
|
||||
v-model:value="agentConfig[key]"
|
||||
>
|
||||
<a-select-option v-for="option in value.options" :key="option" :value="option"></a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-else-if="value?.options && value?.type === 'list'"
|
||||
v-model:value="agentConfig[key]"
|
||||
mode="multiple"
|
||||
>
|
||||
<a-select-option v-for="option in value.options" :key="option" :value="option"></a-select-option>
|
||||
</a-select>
|
||||
<a-input
|
||||
v-else
|
||||
v-model:value="agentConfig[key]"
|
||||
@ -169,28 +176,15 @@
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 添加工具选择部分 -->
|
||||
<a-form-item label="可用工具" name="tools" class="config-item">
|
||||
<p class="description">选择要启用的工具(注:retrieve 工具仅展现了与当前向量模型匹配的知识库,详情请查看 docker 日志。)</p>
|
||||
<a-form-item-rest>
|
||||
<div class="tools-switches">
|
||||
<div v-for="tool in availableTools" :key="tool" class="tool-switch-item">
|
||||
<span class="tool-name">{{ tool }}</span>
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="isToolActive(tool)"
|
||||
@change="(checked) => toggleTool(tool, checked)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item-rest>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 弹窗底部按钮 -->
|
||||
<div class="form-actions" v-if="!state.isEmptyConfig">
|
||||
<a-button type="primary" @click="saveConfig">保存配置</a-button>
|
||||
<a-button @click="resetConfig">重置</a-button>
|
||||
<a-button @click="closeConfigModal">取消</a-button>
|
||||
<div class="form-actions-left">
|
||||
<a-button type="primary" @click="saveConfig">保存并发布配置</a-button>
|
||||
<a-button @click="resetConfig">重置</a-button>
|
||||
</div>
|
||||
<div class="form-actions-right">
|
||||
<a-button @click="closeConfigModal">关闭</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
@ -203,12 +197,8 @@
|
||||
import { ref, onMounted, reactive, watch, computed, h } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
RobotOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
CloseOutlined,
|
||||
SettingOutlined,
|
||||
KeyOutlined,
|
||||
LinkOutlined,
|
||||
StarOutlined,
|
||||
StarFilled
|
||||
@ -226,7 +216,6 @@ const userStore = useUserStore();
|
||||
// 状态
|
||||
const agents = ref({});
|
||||
const selectedAgentId = ref(null);
|
||||
const availableTools = ref([]); // 存储所有可用的工具列表
|
||||
const defaultAgentId = ref(null); // 存储默认智能体ID
|
||||
const state = reactive({
|
||||
debug_mode: false,
|
||||
@ -294,19 +283,8 @@ const fetchAgents = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取所有可用工具
|
||||
const fetchTools = async () => {
|
||||
try {
|
||||
const data = await chatApi.getTools();
|
||||
availableTools.value = data.tools;
|
||||
console.log("Available tools:", availableTools.value);
|
||||
} catch (error) {
|
||||
console.error('获取工具列表错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 根据选中的智能体加载配置
|
||||
const loadAgentConfig = () => {
|
||||
const loadAgentConfig = async () => {
|
||||
// BUG: 目前消息重置有问题,需要重置消息
|
||||
if (!selectedAgentId.value || !agents.value[selectedAgentId.value]) return;
|
||||
|
||||
@ -326,7 +304,7 @@ const loadAgentConfig = () => {
|
||||
agentConfig.value.model = schema.model;
|
||||
}
|
||||
|
||||
if (schema.tools) {
|
||||
if (schema.tools && schema.tools.length > 0 && schema.tools[0] != 'undefined') {
|
||||
agentConfig.value.tools = schema.tools;
|
||||
}
|
||||
|
||||
@ -342,37 +320,82 @@ const loadAgentConfig = () => {
|
||||
}
|
||||
});
|
||||
|
||||
// 加载存储的配置
|
||||
const savedConfig = JSON.parse(localStorage.getItem(`agent-config-${selectedAgentId.value}`) || '{}');
|
||||
try {
|
||||
// 从服务器加载配置
|
||||
const response = await systemConfigApi.getAgentConfig(selectedAgentId.value);
|
||||
if (response.success && response.config) {
|
||||
// 合并服务器配置
|
||||
Object.keys(response.config).forEach(key => {
|
||||
if (key in agentConfig.value) {
|
||||
agentConfig.value[key] = response.config[key];
|
||||
}
|
||||
});
|
||||
console.log(`从服务器加载 ${selectedAgentId.value} 配置成功, ${JSON.stringify(agentConfig.value)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从服务器加载配置出错:', error);
|
||||
message.error('从服务器加载配置失败,将使用默认配置');
|
||||
|
||||
// 合并已保存的配置
|
||||
if (savedConfig) {
|
||||
Object.keys(savedConfig).forEach(key => {
|
||||
if (key in agentConfig.value) {
|
||||
agentConfig.value[key] = savedConfig[key];
|
||||
}
|
||||
});
|
||||
// 如果服务器配置加载失败,尝试从本地存储加载
|
||||
// 这是为了兼容之前的配置方式
|
||||
const savedConfig = JSON.parse(localStorage.getItem(`agent-config-${selectedAgentId.value}`) || '{}');
|
||||
|
||||
// 合并已保存的配置
|
||||
if (savedConfig) {
|
||||
Object.keys(savedConfig).forEach(key => {
|
||||
if (key in agentConfig.value) {
|
||||
agentConfig.value[key] = savedConfig[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = () => {
|
||||
// 保存配置到本地存储
|
||||
localStorage.setItem(`agent-config-${selectedAgentId.value}`, JSON.stringify(agentConfig.value));
|
||||
const saveConfig = async () => {
|
||||
if (!selectedAgentId.value) {
|
||||
message.error('没有选择智能体');
|
||||
return;
|
||||
}
|
||||
|
||||
// 提示保存成功
|
||||
message.success('配置已保存');
|
||||
console.log("agentConfig.value", agentConfig.value);
|
||||
closeConfigModal();
|
||||
try {
|
||||
// 保存配置到服务器
|
||||
await systemConfigApi.saveAgentConfig(selectedAgentId.value, agentConfig.value);
|
||||
|
||||
// 同时保存到本地存储(可选,为了兼容)
|
||||
localStorage.setItem(`agent-config-${selectedAgentId.value}`, JSON.stringify(agentConfig.value));
|
||||
|
||||
// 提示保存成功
|
||||
message.success('配置已保存到服务器');
|
||||
console.log("保存配置:", agentConfig.value);
|
||||
closeConfigModal();
|
||||
} catch (error) {
|
||||
console.error('保存配置到服务器出错:', error);
|
||||
message.error('保存配置到服务器失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 重置配置
|
||||
const resetConfig = () => {
|
||||
// 清除本地存储中的配置
|
||||
localStorage.removeItem(`agent-config-${selectedAgentId.value}`);
|
||||
// 重新加载默认配置
|
||||
loadAgentConfig();
|
||||
message.info('配置已重置');
|
||||
const resetConfig = async () => {
|
||||
if (!selectedAgentId.value) {
|
||||
message.error('没有选择智能体');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 保存空配置到服务器,相当于重置
|
||||
await systemConfigApi.saveAgentConfig(selectedAgentId.value, {});
|
||||
|
||||
// 清除本地存储中的配置
|
||||
localStorage.removeItem(`agent-config-${selectedAgentId.value}`);
|
||||
|
||||
// 重新加载默认配置
|
||||
await loadAgentConfig();
|
||||
message.info('配置已重置');
|
||||
} catch (error) {
|
||||
console.error('重置配置出错:', error);
|
||||
message.error('重置配置失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 监听侧边栏状态变化并保存到localStorage
|
||||
@ -420,8 +443,6 @@ onMounted(async () => {
|
||||
await fetchDefaultAgent();
|
||||
// 获取智能体列表
|
||||
await fetchAgents();
|
||||
// 获取工具列表
|
||||
await fetchTools();
|
||||
|
||||
// 恢复上次选择的智能体
|
||||
const lastSelectedAgent = localStorage.getItem('last-selected-agent');
|
||||
@ -461,31 +482,6 @@ const goToAgentPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 检查工具是否激活
|
||||
const isToolActive = (tool) => {
|
||||
if (!agentConfig.value.tools) {
|
||||
agentConfig.value.tools = [];
|
||||
}
|
||||
return agentConfig.value.tools.includes(tool);
|
||||
};
|
||||
|
||||
// 切换工具状态
|
||||
const toggleTool = (tool, checked) => {
|
||||
if (!agentConfig.value.tools) {
|
||||
agentConfig.value.tools = [];
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
// 添加工具到列表
|
||||
if (!agentConfig.value.tools.includes(tool)) {
|
||||
agentConfig.value.tools.push(tool);
|
||||
}
|
||||
} else {
|
||||
// 从列表中移除工具
|
||||
agentConfig.value.tools = agentConfig.value.tools.filter(item => item !== tool);
|
||||
}
|
||||
};
|
||||
|
||||
// 调试模式
|
||||
const toggleDebugMode = () => {
|
||||
state.debug_mode = !state.debug_mode;
|
||||
@ -740,6 +736,12 @@ const closeConfigModal = () => {
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
gap: 10px;
|
||||
|
||||
.form-actions-left,
|
||||
.form-actions-right {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -789,22 +791,6 @@ const closeConfigModal = () => {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.tools-switches {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.tool-switch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.tool-name {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user