diff --git a/server/routers/__init__.py b/server/routers/__init__.py index 48dd6cd6..d192b751 100644 --- a/server/routers/__init__.py +++ b/server/routers/__init__.py @@ -4,6 +4,7 @@ from server.routers.auth_router import auth from server.routers.chat_router import chat from server.routers.knowledge_router import knowledge from server.routers.graph_router import graph +from server.routers.tool_router import tool router = APIRouter() @@ -13,3 +14,4 @@ router.include_router(auth) # /api/auth/* router.include_router(chat) # /api/chat/* router.include_router(knowledge) # /api/knowledge/* router.include_router(graph) # /api/graph/* +router.include_router(tool) # /api/tool/* diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index c7bfa90e..f0e81a71 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -15,7 +15,7 @@ from src.knowledge import HistoryManager from src.agents import agent_manager from src.models import select_model from src.utils.logging_config import logger -from src.agents.tools_factory import get_all_tools +from src.agents.tools_factory import get_runnable_tools from server.routers.auth_router import get_admin_user from server.utils.auth_middleware import get_required_user, get_db from server.models.user_model import User @@ -44,9 +44,13 @@ async def get_default_agent(current_user: User = Depends(get_required_user)): raise HTTPException(status_code=500, detail=f"获取默认智能体出错: {str(e)}") @chat.post("/set_default_agent") -async def set_default_agent(agent_id: str = Body(..., embed=True), current_user = Depends(get_admin_user)): +async def set_default_agent(request_data: dict = Body(...), current_user = Depends(get_admin_user)): """设置默认智能体ID (仅管理员)""" try: + agent_id = request_data.get("agent_id") + if not agent_id: + raise HTTPException(status_code=422, detail="缺少必需的 agent_id 字段") + # 验证智能体是否存在 agents = await agent_manager.get_agents_info() agent_ids = [agent.get("id", "") for agent in agents] @@ -70,11 +74,6 @@ async def set_default_agent(agent_id: str = Body(..., embed=True), current_user # > === 对话分组 === # ============================================================================= -@chat.get("/") -async def chat_get(current_user: User = Depends(get_required_user)): - """聊天服务健康检查(需要登录)""" - return "Chat Get!" - @chat.post("/call") async def call(query: str = Body(...), meta: dict = Body(None), current_user: User = Depends(get_required_user)): """调用模型进行简单问答(需要登录)""" @@ -183,7 +182,7 @@ async def update_chat_models(model_provider: str, model_names: list[str], curren @chat.get("/tools") async def get_tools(current_user: User = Depends(get_admin_user)): """获取所有可用工具(需要登录)""" - return {"tools": list(get_all_tools().keys())} + return {"tools": list(get_runnable_tools().keys())} @chat.post("/agent/{agent_id}/config") async def save_agent_config( diff --git a/server/routers/tool_router.py b/server/routers/tool_router.py new file mode 100644 index 00000000..111ea126 --- /dev/null +++ b/server/routers/tool_router.py @@ -0,0 +1,15 @@ +from fastapi import APIRouter, Depends +from src.agents.tools_factory import get_all_tools_info +from server.models.user_model import User +from server.utils.auth_middleware import get_required_user + +tool = chat = APIRouter(prefix="/tool", tags=["tool"]) + +@tool.get("/tools") +async def get_tools(current_user: User = Depends(get_required_user)): + """获取所有可用工具的信息""" + try: + tools_info = get_all_tools_info() + return {"tools": tools_info} + except Exception as e: + return {"error": str(e)} \ No newline at end of file diff --git a/src/agents/chatbot/configuration.py b/src/agents/chatbot/configuration.py index 2b4259f1..795d4f29 100644 --- a/src/agents/chatbot/configuration.py +++ b/src/agents/chatbot/configuration.py @@ -3,7 +3,7 @@ import uuid from dataclasses import dataclass, field from src.agents.registry import Configuration -from src.agents.tools_factory import get_all_tools +from src.agents.tools_factory import get_runnable_tools @dataclass(kw_only=True) class ChatbotConfiguration(Configuration): @@ -37,7 +37,7 @@ class ChatbotConfiguration(Configuration): default_factory=list, metadata={ "name": "工具", - "options": list(get_all_tools().keys()), # 这里的选择是所有的工具 + "options": list(get_runnable_tools().keys()), # 这里的选择是所有的工具 "description": "工具列表" }, ) diff --git a/src/agents/chatbot/graph.py b/src/agents/chatbot/graph.py index da012c06..b1f03f27 100644 --- a/src/agents/chatbot/graph.py +++ b/src/agents/chatbot/graph.py @@ -15,12 +15,11 @@ from src.utils import logger from src.agents.registry import State, BaseAgent 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 get_all_tools +from src.agents.tools_factory import get_runnable_tools class ChatbotAgent(BaseAgent): name = "对话机器人(Chatbot)" description = "基础的对话机器人,可以回答问题,默认不使用任何工具,可在配置中启用需要的工具。" - requirements = ["TAVILY_API_KEY", "ZHIPUAI_API_KEY"] config_schema = ChatbotConfiguration def __init__(self, **kwargs): @@ -34,7 +33,7 @@ class ChatbotAgent(BaseAgent): 默认不使用任何工具。 如果配置为列表,则使用列表中的工具。 """ - platform_tools = get_all_tools() + platform_tools = get_runnable_tools() if tools is None or not isinstance(tools, list) or len(tools) == 0: # 默认不使用任何工具 logger.info("未配置工具或配置为空,不使用任何工具") @@ -68,7 +67,7 @@ class ChatbotAgent(BaseAgent): workflow = StateGraph(State, config_schema=self.config_schema) workflow.add_node("chatbot", self.llm_call) - workflow.add_node("tools", ToolNode(tools=list(get_all_tools().values()))) + workflow.add_node("tools", ToolNode(tools=list(get_runnable_tools().values()))) workflow.add_edge(START, "chatbot") workflow.add_conditional_edges( "chatbot", diff --git a/web/src/apis/index.js b/web/src/apis/index.js index 1214ec0f..4ec3b5b4 100644 --- a/web/src/apis/index.js +++ b/web/src/apis/index.js @@ -8,6 +8,7 @@ export * from './system_api' // 系统管理API export * from './knowledge_api' // 知识库管理API export * from './auth_api' // 认证API export * from './graph_api' // 图谱API +export * from './tools.js' // 工具API // 导出基础工具函数 export { apiRequest, apiGet, apiPost, apiPut, apiDelete } from './base' @@ -29,5 +30,8 @@ export { apiRequest, apiGet, apiPost, apiPut, apiDelete } from './base' * 4. graph_api.js: 图谱API * - 知识图谱相关功能 * + * 5. tools.js: 工具API + * - 工具信息获取 + * * 注意:API模块已处理权限验证和请求头,使用时无需再手动添加认证头 */ \ No newline at end of file diff --git a/web/src/apis/tools.js b/web/src/apis/tools.js new file mode 100644 index 00000000..9ec8377f --- /dev/null +++ b/web/src/apis/tools.js @@ -0,0 +1,18 @@ +import { apiGet } from './base' + +/** + * 工具管理API模块 + * 包含工具信息获取等功能 + */ + +// ============================================================================= +// === 工具信息分组 === +// ============================================================================= + +export const toolsApi = { + /** + * 获取所有可用工具的信息 + * @returns {Promise} - 工具信息列表 + */ + getTools: () => apiGet('/api/tool/tools', {}, true) +} \ No newline at end of file diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index 13d95a66..09d649fb 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -69,20 +69,10 @@ >
-

详细配置信

+

详细配置信

{{ selectedAgent.description }}

{{ selectedAgent }}
- -
-

所需环境变量:

-
- - {{ req }} - -
-
-
@@ -111,7 +101,7 @@ @@ -129,6 +119,25 @@ {{ option.label || option }} + +
+
+
+ 已选择 {{ getSelectedCount(key) }} 个工具 + + 清空 + +
+ + 选择工具 + +
+
+ + {{ getToolNameById(toolId) }} + +
+
@@ -149,7 +158,7 @@ @click="toggleOption(key, option)" >
- {{ option }} + {{ getToolNameById(toolId) }}
@@ -198,6 +207,56 @@
+ + +
+ +
+
+
+
+ {{ tool.name }} +
+ + +
+
+
{{ tool.description }}
+ +
+
+
+ +
+
+
!selectedAgentId.value || Object.keys(configurableItems.value).length === 0 ) }); +// 工具相关状态 +const availableTools = ref([]); +const selectedTools = ref([]); +const toolsSearchText = ref(''); + +// 过滤后的工具列表 +const filteredTools = computed(() => { + if (!toolsSearchText.value) { + return availableTools.value; + } + const searchLower = toolsSearchText.value.toLowerCase(); + return availableTools.value.filter(tool => + tool.name.toLowerCase().includes(searchLower) || + tool.description.toLowerCase().includes(searchLower) + ); +}); + +// 根据工具ID获取工具名称 +const getToolNameById = (toolId) => { + const tool = availableTools.value.find(t => t.id === toolId); + return tool ? tool.name : toolId; +}; + const selectedAgent = computed(() => agents.value[selectedAgentId.value] || {}); const configSchema = computed(() => selectedAgent.value.config_schema || {}); const configurableItems = computed(() => { @@ -321,6 +405,53 @@ const clearSelection = (key) => { agentConfig.value[key] = []; }; +// 工具选择相关方法 +const openToolsModal = async () => { + try { + // 加载可用工具列表 + const response = await toolsApi.getTools(); + // 将工具对象转换为数组格式,保留id和name信息 + availableTools.value = Object.values(response.tools || {}); + + // 初始化已选择的工具 + selectedTools.value = [...(agentConfig.value.tools || [])]; + + // 打开弹窗 + state.toolsModalOpen = true; + } catch (error) { + console.error('加载工具列表失败:', error); + message.error('加载工具列表失败'); + } +}; + +const toggleToolSelection = (toolId) => { + const index = selectedTools.value.indexOf(toolId); + if (index > -1) { + selectedTools.value.splice(index, 1); + } else { + selectedTools.value.push(toolId); + } +}; + +const removeSelectedTool = (toolId) => { + const index = agentConfig.value.tools.indexOf(toolId); + if (index > -1) { + agentConfig.value.tools.splice(index, 1); + } +}; + +const confirmToolsSelection = () => { + agentConfig.value.tools = [...selectedTools.value]; + state.toolsModalOpen = false; + toolsSearchText.value = ''; +}; + +const cancelToolsSelection = () => { + state.toolsModalOpen = false; + toolsSearchText.value = ''; + selectedTools.value = []; +}; + // 获取默认智能体ID const fetchDefaultAgent = async () => { try { @@ -536,7 +667,7 @@ const toggleConf = () => { width: 100%; height: 100vh; overflow: hidden; - --agent-view-header-height: 60px; + --agent-view-header-height: 45px; } .agent-view-header { @@ -597,27 +728,7 @@ const toggleConf = () => { } } -// 添加requirements相关样式 -.info-section { - margin-top: 16px; - padding-top: 12px; - h3 { - font-size: 14px; - margin-bottom: 8px; - font-weight: 500; - } -} - -.requirements-list { - display: flex; - flex-wrap: wrap; - gap: 8px; - - .ant-tag { - user-select: all; - } -} .agent-model { width: 100%; @@ -712,6 +823,211 @@ const toggleConf = () => { margin-left: 4px; } } +// 工具选择器样式(与项目风格一致) +.tools-selector { + .tools-summary { + display: flex; + justify-content: space-between; + align-items: center; + // margin-bottom: 8px; + padding: 8px 12px; + background: var(--gray-50); + border-radius: 8px; + border: 1px solid var(--gray-200); + font-size: 14px; + color: var(--gray-700); + transition: border-color 0.2s ease; + + .tools-summary-left { + display: flex; + align-items: center; + gap: 8px; + + .tools-count { + color: var(--gray-900); + } + } + + .select-tools-btn { + background: var(--main-color); + border: none; + color: #fff; + border-radius: 6px; + padding: 4px 12px; + font-size: 13px; + font-weight: 500; + height: 28px; + transition: all 0.2s ease; + + &:hover { + background: var(--main-600); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + } + + &:active { + transform: translateY(0); + } + } + } + + .selected-tools-preview { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 8px 0; + background: none; + border: none; + min-height: 32px; + :deep(.ant-tag) { + margin: 0; + padding: 4px 10px; + border-radius: 6px; + background: var(--gray-100); + border: 1px solid var(--gray-300); + color: var(--gray-900); + font-size: 13px; + font-weight: 400; + .anticon-close { + color: var(--gray-600); + margin-left: 4px; + &:hover { + color: var(--gray-900); + } + } + } + } +} + +// 工具选择弹窗样式(与项目风格一致) +.tools-modal { + :deep(.ant-modal-content) { + border-radius: 8px; + box-shadow: 0 4px 24px rgba(0,0,0,0.08); + overflow: hidden; + } + :deep(.ant-modal-header) { + background: #fff; + border-bottom: 1px solid var(--gray-200); + padding: 16px 20px; + .ant-modal-title { + font-size: 16px; + font-weight: 600; + color: var(--gray-900); + } + } + :deep(.ant-modal-body) { + padding: 20px; + background: #fff; + } + .tools-modal-content { + .tools-search { + margin-bottom: 16px; + :deep(.ant-input) { + border-radius: 8px; + border: 1px solid var(--gray-300); + padding: 8px 12px; + font-size: 14px; + &:focus { + border-color: var(--main-color); + box-shadow: none; + } + } + } + .tools-list { + max-height: 350px; + overflow-y: auto; + border: 1px solid var(--gray-200); + border-radius: 8px; + margin-bottom: 16px; + background: #fff; + .tool-item { + padding: 14px 16px; + border-bottom: 1px solid var(--gray-100); + cursor: pointer; + transition: background 0.2s, border 0.2s; + border-left: 3px solid transparent; + &:last-child { border-bottom: none; } + &:hover { + background: var(--gray-50); + } + &.selected { + background: var(--main-10); + border-left: 3px solid var(--main-color); + } + .tool-content { + .tool-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + .tool-name { + font-weight: 500; + color: var(--gray-900); + font-size: 14px; + } + .tool-indicator { display: none; } + } + .tool-description { + font-size: 13px; + color: var(--gray-700); + margin-bottom: 6px; + line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + } + } + } + .tools-modal-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0 0 0; + border-top: 1px solid var(--gray-200); + .selected-count { + font-size: 13px; + color: var(--gray-700); + background: none; + padding: 0; + border: none; + } + .modal-actions { + display: flex; + gap: 10px; + :deep(.ant-btn) { + border-radius: 8px; + font-weight: 500; + padding: 6px 18px; + height: 36px; + font-size: 14px; + &.ant-btn-default { + border: 1px solid var(--gray-300); + color: var(--gray-900); + background: #fff; + &:hover { + border-color: var(--main-color); + color: var(--main-color); + background: var(--main-10); + } + } + &.ant-btn-primary { + background: var(--main-color); + border: none; + color: #fff; + &:hover { + background: var(--main-600); + } + } + } + } + } + } +} // 多选卡片样式 .multi-select-cards {