news 2026/10/1 20:03:56

【Spring AI 实战 · 阶段二·篇2】联网搜索(LLM-as-Search-Tool)与工具预算治理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Spring AI 实战 · 阶段二·篇2】联网搜索(LLM-as-Search-Tool)与工具预算治理

📌系列说明:一个 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.yml(route-tools / web-search 段)。


一、两个新问题

篇1 让模型会算、会查本地库,但带来两个新问题:

  1. 知识停在训练截止日:2026 年新颁布/修订的政策、最新社平工资,模型不知道——需要联网;
  2. 工具一多就烧钱:模型可能"先查本地库、再联网",一轮里检索工具调好几次,各管各的预算会翻倍——需要统一预算。

二、联网搜索:LLM-as-Search-Tool

给 AI 应用加联网,直觉是接一个搜索 API(Tavily / 博查)。本项目走了另一条路——让一个自带联网能力的子模型去搜:主模型调webSearch工具,工具内部再调一个开了enable_search的模型,用"法律资料检索员"提示词约束它,返回带来源标注的摘要。

为什么这么选?一笔账:

对比项搜索 API联网子模型
价格博查约 0.03 元/次按 token 计费,一次检索不到一分
账号要单独申请、单独计费复用已有的模型 Key,零新增账号
开发量对接搜索 API + 自己解析结果一个 HTTP POST(body 加个开关)

代价(知情选择):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: true:

Map<String,Object>body=Map.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(){booleanok=used.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
本系列全套代码皆开源,觉得这篇有帮助,欢迎顺手点颗 ⭐

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 20:03:12

linux设备串口输出开机信息

使用创建虚拟串口COM1<–>COM2vmware虚拟机添加串行口&#xff0c;选择COM2串口调试工具连接COM1设置串口服务 执行dmesg | grep tty确认串口在系统中的名字&#xff0c;记下你想用来登录的串口&#xff0c;例如 ttyS1。设置 ttyS1 这个串口免密自动登录 创建覆盖配置&am…

作者头像 李华
网站建设 2026/10/1 20:01:18

工业上位机把PLC数据推上MQTT_三_离线队列与QoS1可靠投递

工业上位机把 PLC 数据推上 MQTT&#xff08;三&#xff09;&#xff1a;离线队列 QoS1&#xff0c;断网不丢、连上补发 系列说明&#xff1a;这是《工业上位机把 PLC 数据推上 MQTT》三部曲第 3 篇&#xff0c;也是收尾篇。 第 1 篇讲了整体架构和发布链路&#xff1b;第 2 篇…

作者头像 李华
网站建设 2026/10/1 19:58:16

街机模拟器WinKawaks完全上手指南:ROM、BIOS与游戏配置详解

1. 为什么2024年了&#xff0c;我还在用WinKawaks上个月整理硬盘&#xff0c;翻出一个放了快十年的文件夹&#xff0c;里面是WinKawaks 1.65和四百多MB的街机ROM。当时的第一反应是&#xff1a;这东西还能跑吗&#xff1f;结果双击exe、载入拳皇97&#xff0c;三分钟之后就坐下…

作者头像 李华
网站建设 2026/10/1 19:56:18

SPSS数据合并实战:纵向追加与横向匹配的完整指南

去年我帮一个学生处理消费者调研数据&#xff0c;他把线上问卷和线下问卷分别录成了两个SPSS文件&#xff0c;问我要怎么做成一整份。我说这还不简单&#xff0c;菜单里点几下就完了&#xff0c;结果他跑回去折腾了半天&#xff0c;回来跟我说合并出来的数据要么行数不对&#…

作者头像 李华