← 工具调用

工具为什么要做成数据库驱动的工具中心?停用一个工具怎么保证模型真的调不到?

中等 工具中心与动态注册 · 第 1 / 3 问 更新于 2026/09/29
工具调用工具中心Agent配置管理可观测性
本题落地项目AI Agent 智慧医院智能导诊就诊系统

简化版

把工具写死在代码里,新增工具、调整说明、临时下线都要改代码发版;而工具说明本质上是 Prompt,需要改完马上看效果。所以把工具的编码、说明、入参说明、启用状态、排序登记到一张表里,每次执行时现查表构建工具池,执行逻辑仍然在代码里按编码分发,这就是数据库驱动的工具中心。「停用后模型真的调不到」要靠几处一致生效:构建工具池只取启用的;所有执行入口(Agent 回调、管理端手动执行)执行前再查一次状态,挡住运行中途被停用、模型手里还拿着旧清单的情况;页面预览和 Agent 用同一条查询,页面看到的就是模型能调的。另外要处理表和代码不同步:表里有、代码没实现的工具不交给模型;代码里有实现的工具不许删,只能停用。

详细版

mcp_tool / function_tool(工具注册表)
  tool_code     工具编码:既是发给模型的工具名,也是执行器分发的依据,唯一
  description   发给模型的说明,改它等于调 Prompt
  input_schema  入参说明
  status        启用 / 停用
  sort          顺序

每次执行:
  查 status = 启用 → 构建工具池(名称、说明、Schema)→ 交给模型
  模型调用 → 执行入口再查一次(存在且启用)→ 按 tool_code 分发到代码里的实现 → 写调用日志
生效点做什么挡住什么情况
构建工具池只取启用的工具停用的工具出现在模型面前
执行入口执行前再查一次是否存在且启用运行中途被停用,模型拿着旧清单调用
手动执行入口同一个执行方法管理端调试绕过开关
页面预览和构建工具池共用同一条查询页面显示和模型实际拿到的不一致

完整版教学

一、工具写死在代码里的三个问题

最简单的做法是在代码里声明工具,框架自动生成工具定义。它能跑,但对应不了三个真实需求:

需求写死在代码里放进工具中心
新增一个工具改代码、打包、发布表里加一行,代码里加一个分发分支
调整工具说明改代码、发布,看不到立即效果页面改说明,下一次执行生效
临时下线一个工具删代码或加开关代码页面切「停用」
知道工具被调了多少次没有调用日志表按工具统计

注意工具中心改变的是「模型能看到哪些工具、看到什么说明」,执行逻辑仍然在代码里:表里的编码对应代码里分发器的一个分支。不能把可执行的逻辑也存进数据库,那会引入远比工具选错严重的安全问题。这种进程内的工具中心也不是 MCP 协议:MCP 是模型应用和独立工具服务之间的通信协议,见「MCP 的架构是什么?它在 AI Agent 系统中解决了什么问题?」。

二、工具编码的两个身份

工具编码要加唯一索引,因为它同时承担两个角色:

发给模型的工具名   模型返回 tool_calls 时用它说「我要调哪个」
分发器的路由值     执行器按它找到代码里对应的实现

编码重复,分发器就不知道该用哪一行的定义;编码和代码里的分支对不上,模型调得到、执行器却找不到实现。新增和编辑时都要查重,编辑时要把自己排除掉,否则保存一条记录会被自己拦下。

三、真开关的第一处:构建工具池

每次 Agent 执行前现查表:

select * from mcp_tool where status = '启用' order by sort asc, id asc

把结果包装成工具规格交给模型。模型只知道这几个工具,停用的根本不会出现在请求里。这条查询要成为「模型能调哪些工具」的唯一来源:管理页上的「工具池预览」也调它,页面上看到几个标签,模型就拿到几个工具。

记忆钩子:开关要在所有入口都生效,最好的办法是所有入口共用同一条查询。

四、真开关的第二处:执行时再查一次

只过滤清单还不够。一次 Agent 执行会往返几十轮,工具清单是开始时构建的:

第 1 轮  构建工具池:6 个工具,含 A
第 8 轮  管理员在页面上停用了 A
第 9 轮  模型手里还是第 1 轮的清单,照样调用 A

所以执行入口在真正分发之前再按编码查一次:不存在或已停用,直接拒绝并告诉模型「工具已停用」。管理端的「手动执行」也走同一个执行方法,调试时同样受开关约束。

五、表和代码不同步怎么办

工具中心有两份数据:表里的登记和代码里的实现。它们会不同步,要分情况处理:

情况风险处理
表里有,代码没实现模型调了才报「没有实现」,白浪费一次调用构建工具池时过滤掉,页面上标红提示
代码有实现,表里被删了功能静默消失,页面也看不到这条记录有实现的工具不许删,只能停用
编码拼写有空格比对不上,工具静默失效保存和比对时都去掉首尾空格

「只能停用、不能删」背后的考虑是:停用至少还能在页面上看见,排查时一眼能发现;删掉了,没人会想到去查一张不存在的记录。

六、页面开关要反映真实状态

开关是写库操作,页面上有一个容易忽视的细节:如果用双向绑定,点击瞬间开关就拨过去了,但接口可能失败,界面和数据库就对不上了。更稳的写法是开关的显示值只来自列表数据,点击时调接口,成功后刷新列表才变;失败也刷新一次,把开关拨回数据库里的真实状态。提示文案也要说清语义:「已从工具池移除」「模型下一次执行即可调用」。

七、工具中心还要提供什么

工具池预览:   实时列出启用工具,看到的就是模型能调的
定义预览:     查看真正发给模型的工具 JSON,改完说明立即验证
手动执行:     填入参 JSON 直接执行一次,调试工具本身
调用日志:     每次调用的入参、返回、成败、耗时,按工具统计成功率

手动执行和 Agent 调用最好共用一个执行入口,这样校验、分发、日志只有一套,两边行为完全一致。

八、常见误区与追问

  • 误区:工具中心就是把工具逻辑也存进数据库。 表里只存定义和开关,执行逻辑必须在代码里,按编码分发。
  • 误区:构建工具池时过滤了停用工具,开关就生效了。 运行中途停用的工具,模型还拿着旧清单,执行入口要再查一次。
  • 误区:页面预览单独写一条查询。 预览和 Agent 必须共用同一条查询,否则页面显示和模型实际拿到的会不一致。
  • 误区:不用的工具直接删掉最干净。 代码里还有实现的工具被删后功能静默消失,应该停用。
  • 误区:页面开关用双向绑定就行。 接口失败时界面和数据库会不一致,显示值应以刷新后的列表为准。
  • 追问:表里登记了但代码没实现的工具怎么处理? 构建工具池时过滤掉,并在页面上标出,避免模型白白调用一次。
  • 追问:工具编码为什么要唯一? 它既是模型看到的工具名,也是分发器的路由值,重复就无法确定执行哪一行的定义。

九、加强记忆

工具中心把编码、说明、入参说明、状态、排序存进表,每次执行现查表构建工具池,执行逻辑仍在代码里按编码分发;编码唯一,因为它既是工具名又是路由值。真开关要多处一致:构建工具池只取启用的,执行入口再查一次挡住中途停用,手动执行走同一入口,页面预览和 Agent 共用同一条查询。表和代码不同步时,没实现的不给模型,有实现的不许删只能停用。页面开关以真实状态为准,失败要拨回。

项目实战落地

项目里怎么做的

《AI Agent 智慧医院智能导诊就诊系统》的分诊 Agent 能调哪些工具,完全由 mcp_tool 表说了算:

  • 注册表:6 个工具,tool_code 加唯一索引,description 原样发给模型,input_schema 告诉模型要传哪些参数,status 是「启用 / 停用」;
  • 一条查询两处用:selectEnabled 固定是 status = '启用' 按排序取,页面的「工具池预览」和分诊 Agent 构建工具池共用它,页面上看到的标签就是模型能调的工具;
  • 统一执行入口:McpToolExecutorService.execute 是手动执行和 Agent 调用共用的唯一入口:按编码查定义,不存在或未启用直接拒绝;解析参数(必须是 JSON 对象);按 tool_code 分发;成功失败都写 mcp_tool_call_log;
  • 开关写库:行上的开关调 updateStatus 只更新状态和更新时间两列,更新失败时重新拉列表,让开关回到数据库里的真实状态。

《AI Agentic RAG高级企业知识库平台》多做了两道处理:构建工具清单时同时要求「表里启用」和「代码里有实现」;执行时再查一次启用状态,挡住模型拿着旧清单调用已停用工具;代码里有实现的工具不允许删除,页面提示改用停用。

《AI 多Agent智能相亲交友匹配平台》的 function_tool 表按分组组织八个工具,每个 Agent 角色绑一个分组,执行时只拿到自己分组里启用中的工具。它把核对提前到了保存这一步:

  • 保存时就核对实现。 登记工具时,编码必须能在代码的实现对照表里找到,找不到就拒绝保存,报错里把当前能登记的编码全部列出来。
  • 编码登记后不能改。 编码是模型点名、步骤记录和调用日志找这个工具的依据,改了之后历史记录就对不上;要换编码,就新增一条、把旧的停用。
  • 参数 Schema 保存时校验。 必须是合法 JSON、顶层是对象、type 为 object、properties 是对象,每一项不过都给一句不同的提示。

为什么这样取舍

  • 执行入口也拦:手动执行和 Agent 调用共用一个入口,即使工具池构建之后、执行之前被停用,也会在真正分发前拦一道。
  • 开关以数据库为准:页面开关状态和数据库不一致时,管理员会以为工具已经停了,实际上模型还在调。

面试官还会追问

  • 执行器失败分支取错误信息时,为什么要用 getMsg() 而不是 getMessage()?
  • 在停用状态下点某个工具的「执行」按钮,这次调用会写进调用日志吗?为什么?

学完《AI Agent 智慧医院智能导诊就诊系统》,上面这些追问你都会迎刃而解。

本题落地项目地狱锤炼AI Agent 智慧医院智能导诊就诊系统项目简介:基于医学知识库 RAG、向量检索、数据库驱动工具中心、Spring Boot + LangChain4j、Function Calling,实现文档切分与 Embedding 向量化、按库收窄的内存余弦 topK 检索、SSE 真流式多轮预问诊、数据库驱动 ReAct 分诊 Agent 多轮自主取证、工具注册表真开关与调用日志、科室医生查库核验、挂号乐观锁扣减、Agent 执行时间线与 AI 调用观测、就诊运营,覆盖从预问诊、智能分诊、在线挂号到接诊写病历与运营复盘的完整就诊闭环。SpringbootLangChain4JAgentMCPRAG源码+SQL喂饭学习教程配套面试文档环境安装文档项目运行文档 学习这个项目 也可以学AI Agentic RAG高级企业知识库平台地狱锤炼 查看项目 也可以学AI 多Agent智能相亲交友匹配平台地狱锤炼 查看项目