摘要大模型只能生成文本无法天然知道订单状态、库存数量、用户权限或企业内部系统数据。要让 AI 应用完成真实业务任务需要让模型提出结构化的工具调用请求由服务端完成参数校验、权限判断和业务执行再把工具结果交回模型生成最终回答。Spring AI 提供了 Tool Calling 抽象可以将 Java 方法、函数或ToolCallback暴露为模型可调用的工具。本文围绕“订单查询与售后客服”场景介绍如何设计工具契约、注册工具、执行调用、处理多轮工具链并重点讨论Tool Calling 与普通 Prompt 的区别Tool、MethodToolCallback和ToolCallback的使用方式工具参数的结构化描述和校验用户权限、租户隔离和高风险操作审批模型连续调用多个工具时的状态管理流式输出、超时、重试、幂等和审计如何避免让模型直接获得数据库或 Shell 权限。一、背景与问题1. 仅靠 Prompt 无法访问业务系统下面的 Prompt 只能让模型根据用户输入生成文字用户查询订单 O1001 当前状态。 模型我无法访问你的订单系统。如果把订单数据直接写进 Prompt又会出现数据过期、权限绕过和上下文泄露问题。更合理的流程是用户问题 ↓ 模型判断需要查询订单 ↓ 模型生成结构化工具调用 ↓ 服务端校验并执行订单查询 ↓ 工具结果返回模型 ↓ 模型生成用户可读答案2. Tool Calling 的基本结构工具调用包含四个角色角色作用工具名称标识要调用的业务能力参数 Schema描述参数名称、类型和约束服务端执行器校验权限并执行真实业务逻辑工具结果将执行结果返回给模型或前端模型只负责提出调用意图不能直接绕过服务端执行器访问数据库。3. 为什么不能把所有方法都暴露成工具暴露工具意味着模型可能在满足条件时请求调用。以下能力需要特别谨慎修改订单关闭工单发送邮件和短信删除文件修改权限执行退款访问生产数据库执行 Shell 命令。建议按风险分级只读查询 ↓ 草稿和预览 ↓ 用户确认 ↓ 执行写操作二、核心概念1. Function Calling 与 Tool CallingFunction Calling 侧重模型返回一个结构化函数名和参数Tool Calling 是更宽泛的能力工具可以是 Java 方法、HTTP API、数据库查询、搜索服务或人工审批流程。应用层可以统一成publicinterfaceBusinessTool{Stringname();ToolResultexecute(ToolContextcontext,JsonNodearguments);}2. Spring AI 的工具抽象Spring AI 支持通过Tool注解、ToolCallback、ToolCallbackProvider或Function等方式提供工具。工具定义最终会转换为模型能够理解的名称、描述和参数 Schema。业务服务通常使用ChatClient ↓ ToolCallback ↓ 业务方法 ↓ 业务结果3. 工具描述比方法名更重要工具描述会参与模型决策。下面的描述过于模糊ToolpublicObjectquery(Stringid){...}更好的描述应说明什么时候使用参数代表什么返回结果包含什么不应该用于什么是否只读是否需要用户确认。4. 工具调用不是权限模型即使模型决定调用queryOrder服务端仍然要校验当前用户 ↓ 是否属于当前租户 ↓ 是否拥有订单访问权限 ↓ 订单是否属于当前用户 ↓ 是否允许当前 Agent 使用该工具 ↓ 执行查询模型输出的参数、用户输入的订单号和客户端传入的租户 ID 都不能直接作为最终授权依据。三、工作原理1. 一次工具调用的完整流程1. 服务端向模型发送工具定义 2. 模型返回 tool_call 3. 应用解析工具名称和 JSON 参数 4. 校验工具是否在白名单 5. 校验参数格式和业务权限 6. 执行 Java 业务方法 7. 保存工具调用记录 8. 将工具结果加入对话消息 9. 再次调用模型 10. 返回最终答案2. 多工具调用一个问题可能需要多个工具用户我的订单什么时候到如果超过承诺日期能否申请补偿 ↓ 查询订单 ↓ 查询物流 ↓ 查询售后政策 ↓ 模型整合结果每个工具调用都应该有独立的toolCallId、状态、参数摘要和结果摘要。3. 工具调用状态REQUESTED ↓ VALIDATING ├─ REJECTED └─ APPROVED ↓ RUNNING ├─ COMPLETED ├─ FAILED ├─ TIMEOUT └─ CANCELLED4. 工具结果不等于最终答案工具结果可能是内部结构化数据{orderId:O1001,status:SHIPPED,internalRiskScore:0.81}最终返回给用户时不应该自动暴露internalRiskScore。工具执行结果需要经过输出策略或 DTO 转换。四、实战示例1. 定义只读订单工具importorg.springframework.ai.tool.annotation.Tool;importorg.springframework.stereotype.Component;ComponentpublicclassOrderTools{privatefinalOrderQueryServiceorderQueryService;publicOrderTools(OrderQueryServiceorderQueryService){this.orderQueryServiceorderQueryService;}Tool(description 查询当前用户有权访问的订单状态。 只用于订单查询不执行修改、取消或退款。 当用户没有提供明确订单号时不要猜测订单号。 )publicOrderSummaryqueryOrder(OrderToolContextcontext,StringorderId){returnorderQueryService.queryOwnedOrder(context.tenantId(),context.userId(),orderId);}}OrderToolContext不应该由模型填写应该由服务端从认证上下文生成publicrecordOrderToolContext(UUIDtenantId,UUIDuserId,StringrequestId){}2. 使用 ToolCallbackimportorg.springframework.ai.tool.ToolCallbacks;importorg.springframework.ai.tool.ToolCallback;ToolCallback[]callbacksToolCallbacks.from(orderTools);StringanswerchatClient.prompt().user(question).tools(callbacks).call().content();不同 Spring AI 版本中工具 API 的包名和方法名可能变化项目应以锁定版本的官方 Tool Calling 文档为准。3. 使用 ChatClient 注册工具ServicepublicclassCustomerAssistant{privatefinalChatClientchatClient;privatefinalToolCallback[]orderToolCallbacks;publicCustomerAssistant(ChatClient.Builderbuilder,OrderToolsorderTools){this.chatClientbuilder.build();this.orderToolCallbacksToolCallbacks.from(orderTools);}publicStringanswer(Stringquestion){returnchatClient.prompt().system( 你是企业客服助手。 查询订单时只能使用工具返回的事实。 工具调用失败时说明暂时无法查询 不要编造订单状态。 ).user(question).tools(orderToolCallbacks).call().content();}}4. 工具参数校验模型返回的 JSON 仍然是不可信输入publicrecordQueryOrderRequest(NotBlankPattern(regexp^[A-Z0-9-]{4,32}$)StringorderId){}在执行前还需要if(!orderIdBelongsToUser(context.tenantId(),context.userId(),request.orderId())){thrownewAccessDeniedException(order is not accessible);}5. 高风险工具增加确认退款工具不应该和查询工具一样自动执行publicrecordToolApproval(StringapprovalId,StringuserId,StringtoolName,StringargumentsHash,InstantexpiresAt){}流程模型提出退款请求 ↓ 服务端校验订单和退款金额 ↓ 返回 approval_required ↓ 用户确认具体订单和金额 ↓ 服务端校验 approvalId ↓ 执行退款确认必须绑定具体参数不能只确认“允许退款”这一抽象动作。6. 工具调用审计CREATETABLEai_tool_call(id BIGSERIALPRIMARYKEY,tenant_idBIGINTNOTNULL,conversation_idBIGINTNOTNULL,message_idBIGINTNOTNULL,tool_call_idVARCHAR(128)NOTNULL,tool_nameVARCHAR(128)NOTNULL,arguments_json JSONB,arguments_hashVARCHAR(128),statusVARCHAR(32)NOTNULL,result_summaryTEXT,error_codeVARCHAR(64),created_at TIMESTAMPTZNOTNULLDEFAULTCURRENT_TIMESTAMP,completed_at TIMESTAMPTZ);日志中不要保存完整密码、Token、银行卡号和未脱敏工具结果。7. 工具调用超时和取消returnMono.fromCallable(()-tool.execute(context,arguments)).subscribeOn(Schedulers.boundedElastic()).timeout(Duration.ofSeconds(5)).onErrorMap(TimeoutException.class,error-newToolTimeoutException(tool.name()));阻塞式数据库或 HTTP 客户端不能直接占用 WebFlux 事件线程。工具超时后要更新调用状态并阻止迟到结果覆盖已经失败或取消的任务。8. 限制工具调用次数publicrecordToolPolicy(intmaxCallsPerTurn,SetStringallowedTools,booleanrequireApprovalForWrites){}调用策略应限制单轮最大工具次数单个工具最大重试次数工具参数大小工具结果大小工具链最长深度单次任务总耗时。9. 流式 Tool Calling流式模型可能先返回工具调用片段再返回工具参数。不要在收到第一个片段时立即执行接收工具名称和参数片段 ↓ 持续拼接并验证 JSON ↓ 确认参数完整 ↓ 执行权限检查 ↓ 执行工具 ↓ 发送 tool_call 状态事件高风险工具还要暂停流等待用户确认。五、常见问题与实践建议1. 模型为什么不调用工具可能原因工具描述不清楚当前模型不支持工具调用工具没有注册到本次请求用户问题不需要工具参数 Schema 不完整模型被系统 Prompt 要求直接回答Provider 兼容层丢失了工具字段。排查时记录工具列表、模型能力、请求模式和模型原始 tool call但注意脱敏。2. 模型调用了错误工具可以通过缩小每次请求的工具集合改善工具名称和描述将只读和写入工具分开在系统 Prompt 中明确决策边界增加服务端意图和权限校验对高风险工具强制人工确认。不要只依赖 Prompt 让模型“永远不要调用某工具”。3. 工具结果太长工具结果过长会消耗上下文。应返回摘要或分页结果{items:[{id:O1001,status:SHIPPED}],nextCursor:...}模型需要详细数据时再通过下一次工具调用获取指定内容。4. 工具失败后是否重试查询类工具可以有限重试写操作必须使用幂等键避免重复执行tool_name tenant_id business_request_id写入业务系统前服务端先检查请求是否已经成功执行。5. 工具调用和事务怎么配合不要把长时间模型调用放在数据库事务中。推荐创建任务和消息 ↓ 提交事务 执行工具和模型 ↓ 保存结果 ↓ 短事务更新状态6. 是否可以让模型直接执行 SQL不建议让模型直接连接生产数据库。更安全的方式是暴露固定业务查询工具使用只读数据库账号限制表和字段限制查询耗时和返回行数做 SQL 审计对高敏感字段脱敏。六、进阶思考1. 工具目录和动态注册当工具数量增加后可以设计工具目录Tool Catalog ├─ tool metadata ├─ required scopes ├─ risk level ├─ timeout ├─ rate limit └─ approval policyAgent 根据当前用户和任务只获得一部分工具避免把全量工具描述发送给模型。2. Tool Calling 与 Agent 状态机多步任务可以用状态机管理UNDERSTAND ↓ PLAN ↓ CALL_TOOL ↓ CHECK_RESULT ├─ NEED_MORE_TOOL ├─ NEED_APPROVAL └─ ANSWER比起让模型无限循环调用工具状态机更容易设置预算、超时和人工接管。3. 工具结果可信度工具返回的内容也可能来自外部系统或用户可编辑字段。模型不能把所有工具结果都当成系统规则业务工具结果 事实数据 用户备注和网页文本 不可信内容 系统授权和策略 服务端规则4. 评估 Tool Calling测试集应包括应该调用哪个工具参数是否正确无权限时是否拒绝工具失败时是否正确处理需要确认的动作是否暂停是否出现重复调用是否在预算和次数限制内完成。指标包括工具选择准确率、参数准确率、成功率、平均调用次数和越权拒绝率。结论Spring AI Tool Calling 的核心不是给模型增加几个 Java 方法而是建立一条受控的业务执行链路模型提出结构化调用意图服务端验证参数和权限业务工具执行真实操作工具结果经过脱敏和摘要模型根据结果生成最终回答每次调用都可追踪、可取消、可审计。查询工具可以先从只读、低风险场景开始涉及修改、发送、删除和资金操作时必须增加幂等、审批和人工确认。模型能力可以帮助系统理解用户意图但最终业务权限必须由服务端掌握。参考资料Spring AI Tool CallingSpring AI ChatClientSpring AI Advisors
企业数字化 ERP 产品动态
相关推荐
6+1+3混合模型与四层智能体架构:编排与安全策略实战 这套生态内部叫 55873,我维护它已经有好几个迭代了。这个标题看着很长,其实就三件事:613 混合模型怎么选、智能体编排层管哪些东西、安全策略编排为什么必须从一开始就参与设计,而不是系统跑通了再打补丁。很多人以为做 AI 应用就… · 2026/9/26 6:02:03
六矩阵 · 公司对应表(屿刃修订版) 六矩阵 公司对应表(屿刃修订版)
说明:本表格属于角色假设推演,不是官方合作邀约。完全基于各家公开产品、技术文档、已发布能力做匹配,不强行套架构;重点输出:该厂商天然适合承担哪一类角色&am… · 2026/9/26 6:02:03
编程Agent的Harness工程:从能思考到会干活 最近半年我一直在帮团队搭编程Agent,被问得最多的不是“模型怎么选”,而是“为什么我的Agent看起来能思考,实际用起来却像个傻子”。同一个模型,别人做的Agent能乖乖改完Bug、跑通测试、把结果整理成报告;自己做的Agen… · 2026/9/26 6:02:03
SSM+Vue就医预约挂号系统毕设复盘:数据库设计、并发扣减与论文答辩要点 每年三四月份,各大毕业设计群里总有人反复问“有没有好做的选题”“有没有现成的源码”。就医预约挂号系统是这类问题里出现频率最高的题目之一,它经典到每个导师都见过,也正因为经典,如果你只是交一个增删改查的CRUD,… · 2026/9/26 6:37:02
金融服务系统架构实战:账户、交易、对账与风控设计 金融服务这个赛道,我前前后后做过交易、清结算、账户侧的项目,也算踩过不少坑。很多时候新同学一听"financial-services",第一反应是高大上的量化交易、投资组合那一套,但实际业务里,最核心、最容易翻车的地… · 2026/9/26 6:37:02
变压器电感线圈设计实战:从磁芯气隙到漏感控制的完整经验 1. 变压器电感线圈在能量转换系统中的真实地位我得先坦白一件事:在电子行业里摸爬滚打这些年,见过太多工程师把变压器当成"铁疙瘩"来用——仿真里放个理想模型,板子上按封装画个库,只要输出电压对了就万事大吉。直到你真… · 2026/9/26 6:37:02
微信图片查流向:从存储去重到内容溯源,一文拆透 前几天一个朋友在群里问我:你有没有遇到过那种图,自己发出去之后被人转了一大圈,又回到你面前?我说这不就是绕圈吗?他说不是,我是想查到底是谁传出去的。巧了,微信最近就悄悄上了这么个功能——… · 2026/9/26 6:37:02
从套壳到原生:Agent-Native架构设计与落地实践 最近圈子里一直在刷 agent-native 这个词,我一开始以为又是哪个团队造的新概念,直到自己动手把一个基于大模型的业务系统从“套壳问答”重写成“原生智能体”之后,才真正明白这四个字的分量。它不是指给现有应用挂一个聊天入口,而… · 2026/9/26 6:37:02
Atlas 300V 24G推理加速卡部署YOLOv5全流程解析 上个月我们组评估边缘视觉识别方案,硬件采购清单里放了一张 Atlas 300V 24G。团队第一个问题就抛给我:这卡到底是不是运算加速卡?我当时也觉得奇怪,24G显存听着挺唬人,怎么有人连这都要问。等我真正把驱动装好、用 YOL… · 2026/9/26 6:36:56
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46