这篇重点学什么
本篇重点:先规定允许调用哪些工具(白名单),再记录调用 ID,处理重复与冲突,并设置执行预算和停止条件。
开始读正文 ↓学过相近内容?查看哪些可以跳过
两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。
工具调用与轨迹
共同基础模型、工具、观察与行动的交互结构相通。
另一条路线补充什么?
按步骤记录观察、动作、奖励和结束原因,区分任务真正结束与预算用尽的截断,再决定哪些内容作为监督训练标签。
读完这篇,你能做什么
能实现只读查询闭环,并通过未知工具、非法参数和反复调用验证停止条件。
学习理由、准备要求与知识点
为什么学它
模型提出工具请求后,应用还要真正执行、接回结果并判断何时停止,任务才有机会完成。
先读这些内容
知识点清单
- 工具名称和参数
- 调用 ID 与结果关联
- 应用分派和执行
- 参数与身份校验
- 工具结果回传
- 多轮模型调用
- 总轮数和工具次数预算
- 可恢复与终止错误
运行前先确认Spring AI源码固定2.0.1;Java17完整算法仅运行内存纯函数工具,演示单批分派,不实现远端幂等或完整模型循环。
01先弄懂它是什么
模型提出请求,应用负责执行
模型需要查询笔记等外部信息时,可以先提出一个工具请求。请求通常包含工具名称、参数和调用 ID。应用检查这个请求,决定是否允许执行,再真正发起网络或数据库操作。
一次完整的工具流程,可以分成五步:
- 模型选择工具:提出工具名和参数。
- 应用检查并分派:判断操作是否允许,把请求交给对应的执行函数。
- 工具实际执行:完成查询等具体操作。
- 应用匹配结果:用调用 ID 将返回结果与请求配对,再把真实结果交回模型。
- 判断下一步:模型根据结果继续回答;程序在任务完成或达到限制时停止。
模型输出一段带函数名的
JSON,还不能说明笔记已经查到。提出调用、完成执行、解决问题,是不同的进度。
运行记录要能看出卡在哪一步
工具返回过一次,也不代表整个问题已经解决。记录运行过程时,应把以下状态分开:
| 记录的状态 | 它说明了什么 |
|---|---|
| 请求提出了 | 模型已经提出工具调用 |
| 开始执行了 | 应用已开始运行对应操作 |
| 结果回来了 | 工具已有返回结果,接下来还要匹配请求并判断下一步 |
| 最终答案完成了 | 模型已经完成面向用户的回答 |
这样才能判断问题出在请求、分派、执行、结果匹配,还是后续回答。
限制要落实在应用里
工具应只开放明确、受限制的操作。例如,searchNotes 可以查询笔记,但不能因此获得执行任意 SQL 或命令的能力。
- 只读工具:限制可以读取哪些资料,以及最多返回多少内容。
- 写入工具:处理重复请求,避免产生重复效果,也就是做好幂等;同时需要确认和审计记录,记清谁在何时做了什么。
- 停止条件:程序根据任务预算和业务状态决定何时结束,不能只靠提示里写一句“不要无限循环”。
调用 ID 用来配对请求与结果,不会自动保证同一笔业务只执行一次。 幂等仍需由应用处理。
文中这几个词,先认一认
- 调用ID
- 用于把工具请求和返回结果配对的标识。它不会自动保证同一笔业务只执行一次。
- 回调
- 应用注册的实际执行函数。模型选择公开的工具名并提供参数,具体操作由这个函数完成。
- 闭环
- 把工具的真实结果交回模型,作为下一步判断的依据,并在完成或达到限制时停止的过程。
02看看它怎么工作
一次响应可能包含多个工具请求,每个结果都必须对应正确ID。模型看到结果后可能直接回答,也可能继续提出调用。外层循环应在每次执行前检查剩余预算,并区分可恢复错误、需要用户补充和必须终止的拒绝,不能把异常堆栈原样当普通资料返回。
本题实现一个受限批次执行器:只允许length和upper两个纯函数工具,按调用ID缓存结果。同ID同内容重复时复用,同ID不同内容则拒绝;不同ID即使参数相同,也被视为不同协议请求。这个策略避免把ID冲突误当作安全重试。
执行次数按实际首次调用计数,缓存命中不增加执行数;但外层仍需限制总请求数量与模型轮数,否则无限重复也会消耗CPU和上下文。算法只验证内存调度机制,不产生远程副作用,跨重启幂等与结果核对留给持久执行专题。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
M["模型提出调用"] --> V["白名单 参数 身份 预算"]
V --> D["重放或冲突判断"]
D --> T["实际工具执行"]
T --> R["结果与调用 ID 关联"]
R --> N{"继续决策"}
N -->|"仍有需要且有预算"| M
N -->|"完成或终止"| E["明确任务状态"]按顺序看,每一步做了什么
- 从模型响应读取工具名称、参数及调用ID。
- 校验工具白名单、参数、可信身份和剩余预算。
- 识别同ID重放或冲突,再执行实际工具。
- 检查结果DTO并按原调用ID回传。
- 进入下一次受限决策或以明确终态结束。
03对照实现代码
Spring AI 2.0.1,Git tag v2.0.1 · DefaultToolCallingManager.executeToolCalls
原创工具分派片段,registry和trustedContext由应用提供,需要Spring AI 2.0.1。不是完整管理器源码;完整算法使用纯Java只读工具模拟本地去重及预算。
ToolCallback callback = registry.get(toolName); // registry只含当前允许调用的能力。
if (callback == null) throw new IllegalArgumentException("unknown tool");
ToolContext context = new ToolContext(trustedContext); // 身份来自应用,不从模型参数取。
String result = callback.call(argumentsJson, context); // 真实副作用在工具实现中发生。
// 把result与原调用ID关联回传;仍需应用执行权限、预算和结果契约检查。executeToolCalls处理模型响应中的工具请求,解析对应工具回调并组织结果消息。模型给出名称和参数,真正的方法执行仍发生在应用进程的回调中。
ToolCallback.call接收参数文本及上下文,工具实现负责将输入变成业务调用。找到同名工具只证明路由成功,不证明当前用户有权操作参数指向的资源。
该版本提供每工具和回合总调用限制,并统计已有历史工具调用。任务跨回合的截止时间、总用量与业务预算仍需外层统一控制,不能仅依赖一次管理器执行的局部上限。
本篇缓存仅在一次内存批次内根据调用ID复用结果。它不覆盖进程崩溃或远程写操作的结果未知,正式幂等应使用业务操作键与持久记录。
代码先调用谁,再调用谁
- 模型返回工具名称、参数及调用ID
- 管理器定位已注册回调
- 应用校验可信身份和输入
ToolCallback.call执行工具- 结果按调用ID回到后续模型上下文
04动手写一个例子
受限工具批次分派与冲突检测
输入调用列表和首次执行预算,只允许length/upper纯函数;同ID同内容复用结果,不同内容拒绝。返回每条请求结果和实际执行数。Java17 main运行固定自测。
- 输入 1
同ID两次length(Java),预算1- 输出 1
结果4,4;执行1次
- 输入 2
同ID参数Java和SQL- 输出 2
拒绝冲突
建立ID到原请求与结果的缓存,先校验请求内容,再判断是否重放。保存原请求是为了比较名称和参数,不能只凭ID命中就返回旧结果。
首次调用必须在预算允许时执行,并在成功得到结果后记入缓存。纯函数执行失败不写成功缓存;远程写工具的失败窗口更复杂,不能直接复用本题顺序解决。
结果列表保持请求顺序,包括重复请求的返回结果,便于核对每个调用ID。执行数只统计真正进入工具的次数,协议请求数由外层另行限制。
为什么这样写是对的处理前i条请求后,缓存每个ID对应唯一请求内容和已完成结果;executed等于首次成功执行的不同ID数且不超过预算,输出与输入前缀一一对应。
n为请求数,C为参数及输出总字符量。哈希查找平均常数,length按码点扫描、upper转换读取字符串;缓存与输出保留相关文本。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
record Call(String id,String name,String argument){}
record Saved(Call call,String output){}
record Run(List<String> outputs,int executed){}
static Run run(List<Call> calls,int budget){
if(budget<0)throw new IllegalArgumentException("budget");
Map<String,Saved> cache=new HashMap<>();List<String> out=new ArrayList<>();int executed=0;
for(Call call:calls){
if(call.id()==null||call.id().isBlank()||call.argument()==null||!Set.of("length","upper").contains(call.name()))throw new IllegalArgumentException("contract");
Saved old=cache.get(call.id());
if(old!=null){
if(!old.call().equals(call))throw new IllegalArgumentException("id conflict"); // 同ID必须同内容。
out.add(old.output());continue; // 仅复用本批次已完成结果。
}
if(executed>=budget)throw new IllegalArgumentException("budget exhausted"); // 副作用前检查预算。
String value=call.name().equals("length")?Integer.toString(call.argument().codePointCount(0,call.argument().length())):call.argument().toUpperCase(Locale.ROOT);
executed++;cache.put(call.id(),new Saved(call,value));out.add(value);
}
return new Run(List.copyOf(out),executed);
}
static void eq(Run e,Run a){if(!e.equals(a))throw new AssertionError(a);}
static void bad(List<Call> calls,int b){try{run(calls,b);throw new AssertionError();}catch(IllegalArgumentException expected){}}
public static void main(String[] args){
Call a=new Call("1","length","Java");
eq(new Run(List.of("4","4"),1),run(List.of(a,a),1));
eq(new Run(List.of("JAVA"),1),run(List.of(new Call("2","upper","java")),1));
eq(new Run(List.of(),0),run(List.of(),0));
bad(List.of(a,new Call("1","length","SQL")),2);
bad(List.of(a),0);bad(List.of(new Call("3","shell","anything")),1);
System.out.println("PASS 6 tests");
}
}这些特殊情况也要试一试
- 调用ID冲突不能返回旧结果冒充当前请求结果。
- 预算零允许空批次,不允许首次工具执行。
- 内存缓存不跨重启,也不证明远端恰好执行一次。
进阶选读 · 后续再读:多轮调用的持久状态机
场景工具包含写操作,进程可能在远端成功后、本地记录前崩溃。
做法持久化操作意图、业务幂等键和确认版本,恢复时先查外部结果或幂等记录,再决定重试;把结果未知作为正式状态。
复杂度本地查找通常由索引支持,端到端成本受网络与存储约束;无法用一个HashMap证明跨系统恰好一次。
好处与代价持久记录增加存储和恢复复杂度,但让重放可核对;纯只读演示不能替代生产故障演练。
05项目里怎么用
工具返回稳定DTO,包括成功、未找到、拒绝与执行失败等可区分状态,限制敏感字段和结果长度。把数据库实体整体序列化给模型可能泄露内部字段,即使查询本身已经授权,也仍需控制返回范围。
记录运行ID、调用ID、工具版本、参数摘要、耗时和结果状态,敏感值脱敏。模型可能选错工具,工具也可能实现错误,分别测试:直接调用工具检查契约,再用固定假模型脚本检查闭环路由,最后做真实集成。
预算放在应用可强制执行的位置。每轮检查总截止时间,工具调用前预留次数与成本,取消时传播到仍在执行的任务。对写操作,停止展示不等于外部动作已经取消,结果未知必须后续核对。
容易踩的坑
- 模型返回工具JSON就宣称工具实际执行成功。
- 把调用ID直接当成全局业务幂等键。
- 重复调用复用缓存时不比较请求内容。
- 仅靠提示约束循环次数和资源权限。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 固定工具工作流 | 行为可预测,容易验收 | 动态适应性有限 | 已知业务步骤 |
| 模型选工具 | 能根据问题决定下一步 | 需要白名单、预算和轨迹 | 受控探索任务 |
| 持久操作账本 | 可恢复并核对副作用 | 增加事务和状态管理 | 包含写操作或长任务 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01模型与应用谁真正执行工具?
展开参考答案与推导
模型提出名称和参数,应用回调执行真实操作,因此参数、身份与副作用边界必须由应用控制。
02调用ID为何不等于幂等键?
展开参考答案与推导
它通常关联协议请求和结果,可能只在会话内唯一;业务幂等需要绑定具体操作与持久作用域。
03工具成功是否说明任务成功?
展开参考答案与推导
不一定。结果可能未回答问题,模型还需继续决策或整理;最终任务状态应满足独立验收条件。
换个情况,还会处理吗
01同ID再次请求不同资源怎么办?
展开参考答案与推导
视为冲突并拒绝,记录安全摘要;不能返回旧资源结果,也不能以相同ID执行新副作用。
02模型反复调用同一只读工具,怎样停止?
展开参考答案与推导
共享总轮数、请求数、时间和用量预算;缓存减少重复执行但不能替代循环停止条件。
03取消页面后订单仍创建了,如何解释?
展开参考答案与推导
客户端取消不保证远端回滚;查询业务幂等记录和外部状态,标记结果未知并核对,不能直接重发。
写代码试一试
01不同ID同参数是否复用?
- 输入 / 条件
1:length(Java),2:length(Java)- 输出 / 预期
执行2次
提示 协议身份与参数相同不是同一概念。
展开参考答案与推导
本题按调用ID去重,因此不同ID各执行一次;若业务要按操作键去重,应另定义作用域和内容摘要。
02重复请求应有几个返回项?
- 输入 / 条件
同ID同内容两次- 输出 / 预期
两个结果、一次执行
提示 输入输出关联与执行次数分开。
展开参考答案与推导
每条请求仍得到对应结果,缓存命中只是避免再次进入工具,不能让结果数量与请求关联丢失。
03预算应在何处检查?
- 输入 / 条件
预算1,第二个新ID到来- 输出 / 预期
执行前拒绝
提示 先检查再调用。
展开参考答案与推导
若先执行后发现超限,预算只成为统计提醒;对真实写工具这时副作用已经发生。
商户项目 · 本篇加练
从商户只读查询开始,逐步接入 FAQ
核查日期:2026-10-11。状态:新增教学练习未运行。
目标与前置知识
用同一组商户数据走通模型提出请求、应用校验并执行、模型使用真实结果的闭环。
前置:能用 REST 查询商户并区分成功、参数错误、未找到和服务故障;会阅读 Java DTO 和 Service 方法。
按顺序完成
-
先读 Java 集合、泛型与 Stream,做商户筛选和排序;再完成只读 REST 查询与 Redis 缓存实验,最后接只读工具、FAQ RAG、评测、安全和可观测性。每一步沿用同一组假数据,前一步未验收时不继续增加框架。
-
基线数据中,租户 A 只能访问商户 1001,名为“测试茶铺”,营业时间 09:00–18:00;B 只能访问 1002;9999 不存在。1002 未指定的字段保持空白,不能补出店名或营业时间。身份由测试认证上下文注入,模型只提供 merchantId。
-
应用依次检查工具名、参数类型与范围、身份和商户权限,再调用只读服务。方法权限检查 merchant:read,数据查询强制带服务端 tenantId 和 merchantId。定义 FOUND、NOT_FOUND_OR_NOT_ACCESSIBLE、TEMPORARY_FAILURE 等结果;A 查 1002 与 A 查 9999 对外统一为 404“商户不存在或当前身份无权访问”,故障不能伪装成不存在。只返回所需 DTO,不返回数据库实体、凭证或堆栈。
-
先用固定工具请求测试执行器,再让模型选择工具。每次模型请求、授权决定、真实服务调用和结果分别留证;工具名单、生成的参数或模型说“已查到”都不能证明调用成功。整个练习不连接真实订单、支付或写接口。
验收与交付
- 交付请求→参数校验→授权→服务→工具结果→回答的调用图
- 证明 1001 的名称和时间来自成功工具结果,工具异常时回答明确说本次查询失败
- 非法、越权和缺标识路径分别验收;没有执行证据时标记未知,不宣称成功
本节是练习方案;尚未运行工具或模型。
原始来源
商户项目 · 延伸练习 · 未运行
工具错误送回模型前,先固定分类与调用预算
核查日期:2026-10-13 06:10 JST;2026-10-16复核相关文档。来源为 Spring Framework 7.0.9、Spring AI 2.0.1 与 RFC 9457(2023-07);Spring 页面发布日期未标明。异常适配、泄露负例和重试计数练习均未运行。
前置:REST 错误约定与重试预算。准备可控假下游和固定模型响应替身,不要求把真实秘密放进日志。
Tool Calling 异常处理允许把异常转换成模型可见消息,或重新抛出交给调用方;该版本默认处理器会回传 RuntimeException 的消息,检查型异常及 Error 则重新抛出。自定义 ToolCallback 的 call() 发生工具执行错误时,需按该 API 约定抛出 ToolExecutionException,才能交给这条异常处理路径。只配置 ControllerAdvice 不足以覆盖工具路径。异常变成消息后,外层重试器未必还能看到原异常;模型重新请求工具又是另一层调用。
共享稳定 code、安全 message、requestId 和服务端决定的 nextAction。HTTP 可用 ProblemDetail传输,工具采用可解析的安全结果;不要直接序列化异常及 cause 链。RFC 9457也不建议从自然语言 detail 提取机器判断。
| 本练习业务分类 | 建议动作 | 需要保持的边界 |
|---|---|---|
| INVALID_ARGUMENT | FIX_INPUT | 不自动原样重放;新参数须重新校验 |
| NOT_FOUND_OR_NOT_ACCESSIBLE | STOP | 沿用本站不暴露资源存在性的约定;RAG 正常无命中可以是空结果 |
| DEPENDENCY_TEMPORARY | RETRY_WITH_BUDGET | 仅限服务端明确识别、语义和剩余预算都允许的暂时故障 |
| INTERNAL_ERROR | STOP | 安全通用说明及关联标识;未知异常不能为了重试而改分类 |
这些动作名是教学契约,不是 Spring 或 RFC 标准。对应 HTTP 可按本页既定约定采用 400、404、503、500;返回值约束失败也不能一律归为客户输入 400。
- 在假异常里放入
INTERNAL_CANARY和假 SQL,分别经 HTTP 与真实工具适配器取得输出,核对 HTTP 正文、模型可见工具消息及最终回答没有内部标记、堆栈或查询。受控诊断只需能按 requestId 关联。 - 改变同一错误的语言和安全说明,核对机器仍按 code/类型分支;覆盖非法输入、正常无命中、受控暂时故障和未知异常。
- 用固定替身重复请求非法参数,检查服务端预算或去重真正阻止失控重放。再安排暂时故障后成功,分别记录模型工具请求、实际工具执行、下游尝试三种次数,核对重试没有相乘,预算耗尽时稳定安全失败。
提交分类表、两个入口的实际安全结果、三层计数与标记断言。选择重新抛出时验调用方收口,选择回传消息时验模型可见正文及服务端强制限制。所有结果目前留空;Spring AI 2.0 的循环行为不能直接当作 1.x 的运行事实。
商户项目 · 延伸练习 · 未运行
检查真正送出的工具字符串,而不只看 DTO 名称
核查日期:2026-10-14 06:08 JST;2026-10-16复核相关文档。来源为 OWASP API Security Top 10 2023 与 Spring AI 2.0.1,具体发布日期未标明。DTO、序列化、工具和模型测试均未运行。
先完成输入输出分层及对象授权。本节继续检查获准商户结果中哪些字段可以离开服务端;得到对象的读取许可,不自动获得它全部属性的展示许可。
OWASP API3建议明确选取返回属性,并把响应结构校验作为额外防线。Spring AI 结果转换会把工具结果变成字符串,默认使用 Jackson;因此应在真实转换器之后检查实际内容。把完整实体交给模型再让它删减,已经越过了要保护的边界。
自行为本次查询列出获准字段,例如商户编号、显示名称、城市与营业摘要;保持项目已有字段名,不为了练习随意改名。仓储为排序或授权读取的内部字段,不必进入输出 DTO。可选 fields 只能选择已授权集合的子集;摘要也只能从获准输入生成。
- 在假商户中同时放入正常公开内容与
INTERNAL_FIELD_CANARY内部标记,通过真实工具适配器及转换器取得最终字符串。递归检查对象、列表、metadata 和分页包装;同时证明正常名称和营业摘要没有被误删。 - 给底层假实体增加内部属性,再请求“返回全部字段”或
fields=*。按预定策略拒绝或忽略未知请求,但结果字段不得因此扩大;若使用响应 schema,记录它确实被调用。 - 分别检查普通模型回传与直接返回调用者两条路径。若支持同轮混合工具,再检查实际去向;文档中的
returnDirect在同轮所有工具都开启时才直接返回,它本身不是脱敏开关。
提交调用者与任务范围、投影前后字段名、转换器、结果去向及递归断言;使用假标记,不保留真实私密值。验收位置是模型输入或直接交付的最终内容,不能只查最终回答有没有复述。
实际 Jackson 可见性、代理对象、自定义转换器和依赖版本仍待项目核对。字段白名单不替代对象授权、检索范围或日志保护;本练习没有证明任一真实业务字段已经安全。
商户项目 · 延伸练习 · 未运行
取消请求后,还要观察任务、资源和迟到结果
核查日期:2026-10-16 00:07 JST;2026-10-16复核相关文档。OpenJDK 来源为未固定版本的 master;TimeLimiter 原资料标注 v2.2.0,本轮可读页未能确认固定版本。取消、并发、模型和数据库练习均未运行,没有停止延迟或资源泄漏率结论。
先读Future 的状态边界、超时预算与终态转换。用应用层执行器包装固定只读服务,并准备屏障及资源计数替身;不假定 @Tool 可以直接返回 Future。Spring AI 2.0.1 的方法工具限制明确限制这类异步参数或返回值。
CompletableFuture 类级契约把取消视为异常完成;TimeLimiter提供发出取消请求的配置。二者都不足以证明任意工作线程或 JDBC 查询立即停止。本练习分别记录调用者停止等待、底层收到取消、工作真正结束和资源归还,并选择已终止请求不再回填结果的教学政策。
- 用屏障把任务停在“已开始、未返回”,外层取消后先记录 Future 状态,再放行底层。分别安排能响应取消与仍会继续的替身,独立记录取消请求、底层结束和资源归还;
isCancelled为真不能单独充当停止证据。 - 旧请求 A 结束后,在同会话开始 B,再放行 A 的迟到结果。按 requestId、工具调用 ID 和本轮代次核对:A 不覆盖 B,不续接 A 的模型轮次,不写入 B 的记忆。公共缓存若需要另一种回填规则,应另立契约。
- 让成功完成和取消在屏障附近竞争,检验一次请求如何取得唯一终态及交付资格。不能用“先看未取消,再写入”两步冒充原子保证。已交付成功之后的取消只记录请求已经结束,不声称撤回已显示的信息。
提交带事件序号的提交、开始、取消、终态仲裁、底层结束、资源归还顺序,迟到结果去向及后续模型调用数;未知耗时留空。必须能分别表达“等待结束”“已请求取消”“底层仍在运行”和“资源已归还”。
这项练习不撤销已发生的业务副作用,也不替代撤权后的身份检查。实际 Future 实现、CompletionStage 装饰路径、驱动和回调需按项目版本验证;屏障用于控制先后,不等于已经证明所有并发交错。
商户项目 · 延伸练习 · 未运行
同名分店的区域约束与外部地点 ID 变化
先完成同名资源的候选收窄与明确选择,以及本页按商户 ID 调用只读工具的练习。适用阶段:名称搜索已经能返回候选,准备接外部地点详情。前置项目已覆盖名字不能作唯一键、候选视图和按确定编号取详情;本节只补搜索区域及供应商 ID 生命周期。
资料原核查:2026 年 10 月 17 日 18:04–18:05 JST。官方来源为 Google Place IDs与 Text Search New,均为 Places API New 在线指南,没有独立发行版本。原核查记录两页最后更新为 2026 年 10 月 15 日 UTC;2026 年 10 月 20 日复核时,两页显示 2026 年 10 月 16 日 UTC。本节假商户、供应商适配及模型验收均未运行,不需要调用收费 API。
搜索排名和位置偏好不决定商户身份
同一地点可能有多个 Place ID,ID 也可能变化;这是已有消歧文章采用的边界。旧 ID 可因搬迁、关闭或数据库更新而失效,不能由一次失效就判定现实商户倒闭,更不能自动改绑另一家同名门店。
Text Search 不保证相同请求每次返回一致列表。locationBias 只是偏好,结果可位于区域外,文本中的明确地点还可能覆盖该偏好。locationRestriction 只适用于该文档规定的类别查询,不能当成任意按店名搜索的强约束。省略两者时可能采用 IP 偏好,执行服务的默认位置不能代表用户选定地区。
适配层保存供应商和外部地点 ID,并另行维护与内部商户 ID 的映射。候选中只展示获准的分店、城市和地址等区分信息。多个合理候选时沿用前置流程请用户补条件或选择;只有一个搜索结果也仍需核对已给地址和城市。已确认分店后按该 ID 取详情,最终回答保留分店标识,历史选择不能覆盖本次选择。
建议练习:扰动搜索结果,再核对详情目标
- 同名分店接到真实适配边界。 用“青叶咖啡东京站店”和“青叶咖啡上野店”作假候选。只搜“青叶咖啡”时不能直接采用第一项营业时间;明确上野店之后,详情请求和最终引用均对应上野 ID。此处验 HTTP/工具与回答链路,前置离线解析器的通过记录不能代替它。
- 顺序与区域扰动。 交换同一候选集合的顺序,再加入区域外同名候选;已确认 ID 不随排名改变。有歧义时不能以“第一条”或“最近”代选。用户指定东京时,检查实际候选信息及参数适用条件,不能只凭设置 locationBias 就声称全部结果在东京。
- 旧 ID 与多 ID。 模拟旧 ID 失效及同一地点返回两个 ID。失效后重新暴露待确认候选,不静默换绑;多 ID 不能只凭编号不同断言是两店,也不能仅凭同名合并。记录匹配依据、未解决冲突与采用映射,证据不足时保持不确定,不拼接两店的地址与营业时间。
交付原查询、用户明确条件、候选身份与区分字段、选择依据、详情目标 ID、映射冲突及最终回答。内部编号和外部 ID 保持各自含义,名称搜索不会扩大服务端授权范围。
限制:没有调用模型、实际供应商或修改映射,真实结构与项目映射规则尚待验证。只保存必要候选信息;Place ID 的可保存说明不等于其他供应商内容可无限缓存,使用内容时仍需另行遵守其适用条款。
商户项目 · 延伸练习 · 未运行
工具限额触发后,保留结果并说明尚未完成
原核查:2026 年 10 月 18 日 12:08–12:09 JST;2026 年 10 月 20 日复核 Tool Call Limits 与 AIP-158 的结束条件。Spring AI 页面标 2.0.1,发布日期未标明;AIP-158 状态为 Approved,创建日期 2019-02-18,正文最新可见变更记录为 2025-07-08。所有限额、分页与模型练习均未运行,也未编译相关代码。
适用:商户 Agent 已支持连续分页工具调用,准备设置框架限额时。前置是工具错误与分层预算、游标与终止契约及完整与部分分页报告。先准备固定模型响应替身与假分页接口;这里重点检查同一批工具调用中途越界后的返回行为。
Spring AI Tool Call Limits说明,DefaultToolCallingManager 的单工具和全部工具默认上限分别为 40、150。计数只覆盖最后一条 UserMessage 之后的 ToolResponseMessage,属于当前用户轮次。默认 THROW 由 ToolCallingAdvisor 捕获,返回带明确结束原因的单个 Generation;同批先前成功的结果保存在部分工具响应元数据中。RETURN_ERROR_RESPONSE 则跳过超限调用、生成错误工具响应,并让对话继续。因此,工具业务没有继续执行,并不能证明后续模型调用已经停止。这些是文档默认值,不是本项目建议预算。
沿用已有部分报告契约,分别保留已确认结果、调用关联、停止原因和覆盖状态。例如 stopReason 与 coverageStatus 是可选的教学字段,不是框架自动生成的业务保证。限额结束时应说“当前取得这些结果,遍历未完成”。按 AIP-158,集合末尾由空 next_page_token 表达;应用不能因预算耗尽而伪造这个信号。模型不再提议调用,也不能代替服务端的结束证据。
独立练习:同批越界与两种终止策略
- 在隔离替身中设置单工具上限 2、总上限 3,先完成一次调用,再安排一条模型响应提出同一工具的两个有效调用。逐项记录是否进入业务接口、关联结果、超限原因及部分响应,检查已完成结果未丢失,被拒调用未执行。还要让调用方只读取首个生成结果,确认它仍能识别限额终止。一次模型响应可能提出多个工具调用,不能按响应条数代替调用次数,也不能假定整批全部成功或回滚。若测试分页工具,使用预先确定的有效请求,不让后一个调用依赖尚未取得的游标。
- 对同一固定请求序列分别配置
THROW与RETURN_ERROR_RESPONSE。后一模式中让假模型继续请求工具,检查应用的轮次或总时间上限能否安全收口;除业务执行数,还要记录后续模型调用。再对照“正常末页且令牌为空”与“仍有令牌但预算耗尽”,只有前者可以标记该遍历已到末尾。新的用户轮次单独计数,不能把这项机制宣传成跨轮防滥用或长期费用控制。
交付配置、请求与工具调用关联、实际执行数、被拒数、模型轮数、部分结果数、停止原因及覆盖状态。真实游标只记录必要状态,不保存全文;未知计数留空。框架工具计数、下游 HTTP 重试、数据库调用和 token 用量分别记录,不能相互代替。
实施时仍需核对项目依赖、advisor 顺序、实际返回 API 及业务接口的结束契约;2.0.1 的机制不能直接当作 1.x 行为。这里只复核文档,没有修改项目配置、测量费用或证明真实模型会自行停止。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能实现只读查询闭环,并通过未知工具、非法参数和反复调用验证停止条件。
本篇重点加练
参数身份校验与执行预算共同约束工具行动,必须覆盖拒绝请求与边界时刻。
- 参数与身份校验
- 总轮数和工具次数预算
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
为查询笔记与修改计划定义两个工具,说明名称、参数与调用ID分别承担什么职责;设计重复名称或缺少ID时的处理契约。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点工具名称和参数 · 调用 ID 与结果关联
- 作业 02代码推演
画出模型提出工具请求到应用真正执行的逐步时序,标出身份、参数、资源权限各在何处检查,以及每一步的责任主体。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点应用分派和执行 · 参数与身份校验
- 作业 03原理问答
设计一份工具尝试账本,明确参数无效、权限拒绝、工具启动和执行成功分别是否消耗各类预算,写出规则间需要满足的一致性要求。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点参数与身份校验 · 总轮数和工具次数预算
综合应用
- 作业 04面试场景
假模型先要求查笔记,再根据返回结果要求查另一篇,最后输出总结。写出每轮完整消息账本,并设计错配工具结果的检测用例。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点工具结果回传 · 多轮模型调用
- 作业 05编程实践
用Java实现可注入假模型的闭环,设置3轮和4次工具预算;测试连续要工具、不可恢复异常和中途取消,提交停止状态及实际执行次数。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点总轮数和工具次数预算 · 可恢复与终止错误
- 作业 06排错实验
让同一调用ID先携带参数A,随后携带不同参数B,再收到重复结果。建立事件时间线,检验现有实现是否会重复执行或错误复用结果。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点调用 ID 与结果关联 · 应用分派和执行 · 工具结果回传
- 作业 07编程实践
实现假工具分派器,输入混合非法参数、越权请求与合法请求;用自选的小预算测试“多次拒绝后继续请求”是否符合你声明的计数与停止契约。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点参数与身份校验 · 总轮数和工具次数预算
- 作业 08面试场景
授权通过后、工具真正启动前,全局剩余预算被另一个任务改变。画出可发生的事件顺序,提出本地模拟方案检验授权与预算检查的边界。
- 提交内容
- 场景评审记录,包含约束、备选方案、未决信息和验证计划。
- 怎样算完成
- 方案回应题目约束,取舍能对应到可核验的事实。
- 给出失败或条件变化时的检查方式。
练到的知识点参数与身份校验 · 总轮数和工具次数预算
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
将单工具执行扩展为一轮多个工具,先界定独立读操作与有依赖写操作的并发条件,再比较吞吐、顺序、失败传播和预算核算。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点应用分派和执行 · 多轮模型调用 · 总轮数和工具次数预算
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 反例构造
审阅“参数通过Schema校验且单轮工具数量有限,整个任务就一定可控”的主张。构造一个具体反例,并提出需要补充验证的条件。
- 提交内容
- 具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
- 怎样算完成
- 反例满足原主张所声称的前提,确实针对其结论。
- 说明一个反例能推翻什么、不能进一步证明什么。
练到的知识点参数与身份校验 · 总轮数和工具次数预算
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
Spring AI:Tool Calling (新标签页)↗
重点阅读工具定义、
ToolContext、结果转换与错误处理,验证模型请求到 Java 执行的完整链路。 - 实践规范英文
MCP 2025-11-25:Tools (新标签页)↗
阅读 tools
/list、tools/call、输入 Schema、结果与错误表示,对照实际消息做契约验收。 - 深入实践指南英文
OWASP:授权检查指南 (新标签页)↗
重点阅读最小权限、默认拒绝和每次请求检查权限,落实到工具服务和资源归属查询。
- 深入参考文档英文
Spring AI 2.0.1 DefaultToolCallingManager 源码 (新标签页)↗
按指定版本定位
DefaultToolCallingManager.executeToolCalls;对照源码分析理解实际调用边界。