
摘要Agent 能否完成复杂任务取决于它能调用哪些工具、工具如何被发现、参数如何校验以及执行结果如何返回。把所有工具硬编码在一个 Service 中只适合早期 Demo进入生产环境后需要支持按租户、角色、应用和任务动态加载工具同时保证权限、超时、审批和审计。本文继续企业智能客服与知识库助手项目设计一套 Agent 工具注册与动态调用机制重点介绍工具目录、工具定义和工具执行器的职责如何从静态注册演进到动态注册工具 Schema、版本和能力标签按租户、用户、Agent 和任务筛选工具HTTP、Java 方法和 MCP 工具的统一封装动态调用时的参数校验、审批、超时和幂等如何保存工具调用记录并支持灰度和回滚。一、背景与问题1. 静态工具注册的局限Demo 可能直接写chatClient.prompt().tools(orderTools,knowledgeTools).call();随着工具增加会出现每个请求都暴露全部工具模型看到无关工具后选择错误不同租户工具权限混在一起工具描述和代码版本难以管理工具下线需要重新发布服务无法统一设置超时、限流和审批。2. 工具注册中心的目标工具注册中心负责保存工具名称 工具描述 参数 Schema 版本 风险等级 所需权限 支持的 Agent 执行器地址 状态运行时只把当前任务允许的工具提供给模型。3. 工具动态调用链路用户请求 ↓ 确定租户、角色和 Agent ↓ 查询可用工具 ↓ 生成工具 Schema ↓ 模型选择工具 ↓ 解析 tool_call ↓ 再次检查工具权限和版本 ↓ 执行工具 ↓ 返回 Tool Result二、核心概念1. Tool Definition工具定义描述模型能看到的契约publicrecordToolDefinition(Stringname,Stringdescription,JsonNodeinputSchema,Stringversion,ToolRiskLevelriskLevel,SetStringrequiredScopes){}2. Tool ExecutorTool Executor 负责真实执行publicinterfaceToolExecutor{ToolResultexecute(ToolExecutionContextcontext,JsonNodearguments);}定义和执行器分离后可以让工具 Schema 由注册中心管理让执行逻辑保持在业务服务中。3. 工具来源来源说明Java Bean本地方法或 Spring ServiceHTTP内部业务接口Queue异步任务MCP外部工具服务器Script受控脚本或 Sandbox4. 风险等级READ_ONLY 查询和检索 WRITE_DRAFT 创建草稿、预览变更 WRITE_CONFIRMED 用户确认后写入 DESTRUCTIVE 删除、发布和权限变更风险等级决定是否需要审批和人工接管。5. 工具版本工具版本应绑定名称输入 Schema输出 Schema执行器权限超时版本状态。模型看到的 Schema 和服务端实际执行器必须是同一版本避免参数不一致。三、工作原理1. 工具发现Agent Task ↓ ToolResolver ├─ tenant ├─ user scopes ├─ app ├─ risk policy └─ feature flags ↓ Allowed Tools2. Schema 生成工具 Schema 应只包含当前模型需要的信息{name:query_ticket,description:查询当前用户有权限访问的工单,parameters:{type:object,properties:{ticketId:{type:string}},required:[ticketId]}}不要把内部数据库字段、管理员字段和实现细节暴露给模型。3. 动态调用安全检查工具调用到达服务端后重新检查工具是否存在 ↓ 版本是否可用 ↓ 当前 Agent 是否允许 ↓ 当前用户是否有 Scope ↓ 参数是否符合 Schema ↓ 租户和资源归属是否正确 ↓ 是否需要审批 ↓ 执行不能因为模型在请求中选择了某个工具就跳过服务端授权。4. 结果标准化不同工具返回结果应统一publicrecordToolResult(StringcallId,Stringstatus,JsonNodedata,StringerrorCode,booleanretryable,booleanuserVisible){}四、实战示例1. 注册表数据库CREATETABLEai_tool_definition(id UUIDPRIMARYKEY,tool_nameVARCHAR(128)NOTNULL,versionVARCHAR(32)NOTNULL,descriptionTEXTNOTNULL,input_schema JSONBNOTNULL,output_schema JSONB,executor_typeVARCHAR(32)NOTNULL,executor_config JSONB,risk_levelVARCHAR(32)NOTNULL,statusVARCHAR(32)NOTNULL,timeout_msINTEGERNOTNULLDEFAULT5000,created_at TIMESTAMPTZNOTNULLDEFAULTCURRENT_TIMESTAMP,UNIQUE(tool_name,version));CREATETABLEai_tool_permission(id BIGSERIALPRIMARYKEY,tool_id UUIDNOTNULLREFERENCESai_tool_definition(id),subject_typeVARCHAR(32)NOTNULL,subject_idVARCHAR(128)NOTNULL,scopeVARCHAR(128)NOTNULL,UNIQUE(tool_id,subject_type,subject_id,scope));2. 静态注册 Java 工具ComponentpublicclassTicketToolExecutorimplementsToolExecutor{OverridepublicToolResultexecute(ToolExecutionContextcontext,JsonNodearguments){StringticketIdarguments.path(ticketId).asText(null);if(ticketIdnull||ticketId.isBlank()){returnToolResult.error(context.callId(),INVALID_ARGUMENT);}TicketSummaryticketticketService.query(context.tenantId(),context.userId(),ticketId);returnToolResult.success(context.callId(),objectMapper.valueToTree(ticket));}}3. 动态解析工具publicListResolvedToolresolve(AgentContextcontext){returnregistry.findEnabled().stream().filter(tool-permissionService.allowed(context.userId(),context.tenantId(),tool)).filter(tool-policyService.allowed(context.agentId(),context.taskType(),tool.riskLevel())).map(this::bindExecutor).toList();}4. 将工具转换为模型 SchemapublicListToolCallbackcallbacks(ListResolvedTooltools){returntools.stream().map(tool-ToolCallback.from(tool.definition().name(),tool.definition().description(),tool.definition().inputSchema(),args-tool.execute(args))).toList();}不同 Spring AI 版本创建ToolCallback的 API 可能不同实际项目要使用目标版本提供的 Builder 或MethodToolCallback。5. 动态调用入口publicMonoToolResultinvoke(ToolExecutionContextcontext,StringtoolName,Stringversion,JsonNodearguments){returnMono.defer(()-{ToolDefinitiondefinitionregistry.getRequired(toolName,version);schemaValidator.validate(definition.inputSchema(),arguments);permissionService.check(context,definition);if(definition.riskLevel().requiresApproval()){returnapprovalService.require(context,definition,arguments);}returnexecutorFactory.get(definition).execute(context,arguments).timeout(Duration.ofMillis(definition.timeoutMs()));});}6. HTTP 工具执行器publicToolResultexecuteHttp(ToolExecutionContextcontext,ToolDefinitiondefinition,JsonNodearguments){URIendpointendpointResolver.resolve(definition,context.tenantId());returnwebClient.post().uri(endpoint).header(X-Request-Id,context.requestId()).bodyValue(arguments).retrieve().bodyToMono(JsonNode.class).map(data-ToolResult.success(context.callId(),data)).block(Duration.ofMillis(definition.timeoutMs()));}生产环境不要允许注册中心存储任意 URL。HTTP 工具的目标地址应该来自服务端白名单防止 SSRF。7. MCP 工具适配MCP 工具适合通过统一协议接入外部工具但仍然需要Server 白名单工具名称和版本OAuth 或短期凭据调用超时结果大小限制审计用户和租户权限。不要因为 MCP 提供了工具 Schema就把它当成业务授权。8. 工具审批publicMonoToolResultrequireApproval(ToolExecutionContextcontext,ToolDefinitiondefinition,JsonNodearguments){StringhashhashArguments(arguments);ApprovalapprovalapprovalRepository.create(context.runId(),definition.name(),definition.version(),hash,Instant.now().plus(Duration.ofMinutes(5)));returnMono.just(ToolResult.approvalRequired(context.callId(),approval.id()));}审批通过时重新计算参数哈希防止模型在等待期间改变调用参数。9. 工具灰度Tool v1ACTIVE Tool v2CANARY ↓ 少量租户使用 v2 ↓ 比较成功率、延迟和错误 ↓ 扩大范围 ↓ v1INACTIVE10. 工具调用记录CREATETABLEai_tool_invocation(id BIGSERIALPRIMARYKEY,run_id UUIDNOTNULL,call_idVARCHAR(128)NOTNULL,tool_nameVARCHAR(128)NOTNULL,tool_versionVARCHAR(32)NOTNULL,arguments_hashVARCHAR(128),statusVARCHAR(32)NOTNULL,latency_msBIGINT,result_summaryTEXT,error_codeVARCHAR(64),created_at TIMESTAMPTZNOTNULLDEFAULTCURRENT_TIMESTAMP,completed_at TIMESTAMPTZ);五、常见问题与实践建议1. 动态工具是否需要热更新只读工具可以相对安全地动态启停写工具和高风险工具建议通过审批流程发布并支持版本回滚。2. 为什么模型总是选错工具检查工具描述是否重叠工具名称是否清晰参数 Schema 是否完整当前请求是否暴露了太多工具是否给了模型无关工具是否需要先做意图分类。3. 工具版本和模型缓存工具 Schema 发生变化后旧的 Prompt 或模型缓存可能继续使用旧契约。请求中应绑定工具目录版本。4. 动态工具如何限流按工具和租户配置query_ticket每分钟 1000 次 create_ticket每分钟 50 次 refund_order每分钟 5 次 人工审批5. 如何防止 SSRF禁止模型直接传 URL。目标服务从工具定义和服务端注册配置中获取执行前校验SchemeHost端口IP 解析结果是否属于内网禁止网段是否属于服务白名单。6. 工具执行超时后怎么办将状态标记为TIMEOUT不要立即假设业务一定没有执行成功。对于写操作要通过业务查询或幂等状态确认实际结果。六、进阶思考1. 工具目录即平台能力目录工具目录可以进一步管理所属业务域权限 Scope风险等级SLA版本维护人依赖服务费用数据级别。2. 工具选择和任务规划分离复杂 Agent 可以先做工具规划再执行用户目标 ↓ 工具规划 ↓ 权限和预算审批 ↓ 执行计划 ↓ 逐步调用3. 工具质量评估指标包括选择准确率参数正确率执行成功率平均延迟超时率重试率越权拒绝率人工审批率。4. 多租户工具市场企业平台可以让不同团队发布工具但必须经过Schema 检查安全扫描权限评审测试环境验证灰度回滚。结论Agent 工具注册与动态调用机制的核心是让“模型能看见什么工具”和“服务端实际允许执行什么工具”保持一致同时保留最后的权限和审批控制。建议从静态注册开始逐步增加工具目录、Schema 版本、按租户筛选、审批、灰度、MCP 适配和审计。高风险工具始终由服务端策略控制不能交给模型或客户端决定。参考资料Spring AI Tool CallingSpring AI MCPModel Context Protocol