与工具预算治理)
系列说明一个 Java 后端视角的 Spring AI 渐进式实战教程载体为开源项目「劳小司 · 智能法律助手」。序章技术栈全景与 AI 学习指南阶段一 · 流式对话内核篇1 SSE 流式·停止·思考可见化 / 篇2 会话记忆压缩与滚动体验阶段二 · 工具调用篇1 Function Calling 与法律计算器 /篇2 联网搜索与工具预算本文阶段三 · RAG 知识库篇1 起步与底账化 / 篇2 Agentic RAG 与引用可信度 / 篇3 检索质量与体验阶段四 · 多模型路由篇1 五路级联路由阶段五 · 安全与质量门篇1 安全层与强制检索 / 篇2 质量门与评估门禁 / 篇3 指代消解与阻塞隔离阶段六 · 产品化与用户体系篇1 认证·配额·门禁 / 篇2 前端·移动端·身份 / 篇3 劳动法专精与多模态阶段七 · 存储演进与部署篇1 存储迁移 / 篇2 部署契约本篇涉及tool/WebSearchTool.java、tool/ToolBudget.java、application.ymlroute-tools / web-search 段。一、两个新问题篇1 让模型会算、会查本地库但带来两个新问题知识停在训练截止日2026 年新颁布/修订的政策、最新社平工资模型不知道——需要联网工具一多就烧钱模型可能先查本地库、再联网一轮里检索工具调好几次各管各的预算会翻倍——需要统一预算。二、联网搜索LLM-as-Search-Tool给 AI 应用加联网直觉是接一个搜索 APITavily / 博查。本项目走了另一条路——让一个自带联网能力的子模型去搜主模型调webSearch工具工具内部再调一个开了enable_search的模型用法律资料检索员提示词约束它返回带来源标注的摘要。为什么这么选一笔账对比项搜索 API联网子模型价格博查约 0.03 元/次按 token 计费一次检索不到一分账号要单独申请、单独计费复用已有的模型 Key零新增账号开发量对接搜索 API 自己解析结果一个 HTTP POSTbody 加个开关代价知情选择enable_search是全网搜索无法像搜索 API 那样在 API 层硬限定域名。于是只采信官方来源从硬约束降级为提示词软约束——靠子模型的系统提示词要求它只把 gov.cn、法院官网、裁判文书网等作为结论依据且逐条标注来源网址供用户核验。联网在本项目里定位是辅助参考这个精度够用。privatestaticfinalStringSEARCH_ASSISTANT_PROMPT你是法律信息联网检索员……1) 只采信官方权威来源gov.cn 及其子站、法院官网、裁判文书网……自媒体仅作线索不得作结论依据2) 以条目返回摘要 来源网站 来源网址 日期4) 官方渠道未检索到就直说禁止编造来源。;三、为什么手写 HTTP而不走 Spring AI ChatModel这是本篇最硬的一个坑enable_search是厂商的私有 body 参数Spring AI 的OpenAiChatOptions不透传这类扩展字段。你要是只在 ChatClient 上找联网开关永远找不到。解决用RestClient直调 OpenAI 兼容端点在请求体顶层手动加enable_search: trueMapString,ObjectbodyMap.of(model,properties.getLlm().getModel(),enable_search,true,// 私有参数Spring AI 不透传只能手写messages,List.of(Map.of(role,system,content,resolvePrompt()),Map.of(role,user,content,query)));restClient.post().uri(/chat/completions).body(body).retrieve().body(Map.class);顺带辟谣网上不少教程里的spring-ai-tavily-search坐标是虚构的Spring AI 官方核心库并没有内置联网搜索工具。别照着 mvn 依赖半天找不到。降级不阻断联网失败超时 / 子模型异常不阻断回答返回一句联网搜索暂时不可用请基于本地知识回答并提醒用户——与全链路降级原则一致。四、工具预算多工具共享一个池引入联网后一轮里searchLaw查本地和webSearch查互联网可能都被调。若各管各的额度模型本地查一遍、网上再查一遍成本直接翻倍。ToolBudget的设计是共享预算两个检索工具从同一个ToolBudget扣减route-tools.max-tool-calls就是本轮所有检索类工具的总调用上限。publicbooleantryAcquire(){booleanokused.incrementAndGet()maxCalls.get();if(!ok)denials.incrementAndGet();returnok;}预算耗尽时工具不再执行检索而是返回一段劝退文案让模型基于已得信息作答连续被拒 ≥3 次还会升级为强制收敛指令熔断检索成瘾的空转returnbudget.denialCount()ToolBudget.HARD_DENY_AFTER?检索次数已达上限且连续被拒。【强制】立即停止一切工具调用基于已检索到的信息直接生成完整回答。:本轮检索预算已用完不要再调用 searchLaw 或 webSearch请基于已有信息作答。;预算的生命周期是请求级每次对话 new 一个经ToolContext传给工具天然隔离用AtomicInteger兜一层线程安全。审校重试时阶段五·篇2系统会grant()追加预算——那是质量门触发的系统行为不算用户头上。五、路线差异化挂载闲聊零工具预算之外还有一层更省的设计不同路线挂不同的工具集。配置在route-tools里route-tools:default:{tools:[searchLawTool,webSearchTool],max-tool-calls:6}cheap:{tools:[],max-tool-calls:0}# 闲聊不挂任何检索工具legal:{tools:[searchLawTool,webSearchTool,laborTools,generalLegalTools],max-tool-calls:12}闲聊路线cheap一个检索工具都不挂——模型想查也无工具可调从源头保证零额外成本法律专业路线才全量挂载、给更高预算。max-tool-calls同时也是 ReAct 循环的停止条件。六、踩坑备忘① 私有参数不透传。enable_search、thinking_budget这类厂商扩展字段Spring AI 的 Options 不认。要透传就得手写 HTTP本篇或换用支持的扩展库。这是接入国产模型私有能力时的通用陷阱。② 多工具各管各的预算 双重烧钱。一定让同类检索工具共享一个ToolBudget别一个工具一个计数器。③ 预算拒绝文案要可执行。光返回预算用完没用模型可能换个说法接着调要给出明确指令“基于已有信息直接作答”连续拒绝时升级为【强制】收敛。④ 联网范围无法硬限定域名。用 LLM-as-Search 换低成本就要接受来源过滤靠提示词软约束输出必须带来源网址让用户可核验——这是取舍不是缺陷但要知情。七、小结机制一句话LLM-as-Search-Tool用联网子模型代替搜索 API便宜且零新增账号手写 HTTP私有参数 enable_search 不被 Spring AI 透传ToolBudget多检索工具共享一个预算池防双重烧钱差异化挂载闲聊路线零工具专业路线全量 高预算八、下篇预告工具能联网、能查本地了但本地知识库本身还是黑盒——法条怎么向量化、怎么增量同步、检索准不准都是下一篇开始的正题。阶段三我们进入 RAG 知识库。源码与体验Gitee国内快https://gitee.com/spaserby/laoxiaosi.git GitHub https://github.com/spaserby/laoxiaosi.git 在线演示https://laoxiaosi.noctisblue.com本系列全套代码皆开源觉得这篇有帮助欢迎顺手点颗 ⭐