ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring AI实战:构建会调用工具的智能助手,优化延迟与幻觉

2026/9/2 20:45:53 拓冰建站 浏览量
Spring AI实战:构建会调用工具的智能助手,优化延迟与幻觉 当产品经理半夜发来一条“这个AI效果太好了我们把它提上正式排期”的消息时真正让AI变得丝滑的往往不是某个神秘Prompt而是背后一整套工程决策。“顶级AI该有的样子”在企业项目里可以被拆成四个可验证的指标响应是否够快、回答是否靠谱、能不能真正调用业务工具、出了问题能不能快速定位。这篇博客会从一个基于Spring AI构建的内部智能服务助手出发把实现一个“会干活”的AI Agent、接入流式输出、处理延迟与幻觉、理解credits消耗、完成生产化排障的过程完整讲清楚。下面所有代码都用于说明工程思路落地时请结合你使用的Spring AI版本、模型平台和公司内部框架做调整。1. “丝滑”不是感觉是四个可以被测量的工程指标1.1 首字延迟AI体验最容易被感知的数字用户在对话框里按下回车后看到第一个字出现的耗时通常决定了产品经理对AI的第一印象。大模型推理本身需要时间完整的回答可能长达几秒甚至十几秒。如果前端一直转圈等模型把整段内容生成完再一次性展示用户会下意识觉得卡顿。流式输出Streaming把“等待完整答案”变成“边生成边展示”让首字延迟从整个回答时长缩小到几百毫秒级别体验立刻不同。因此“丝滑”不是玄学而是一个可测量的指标首字延迟、生成间隔、完整响应时间、超时率。这些指标背后关联模型选型、网络链路、提示词长度、工具调用次数和前端渲染策略。1.2 上下文和提示词决定回答质量同样一个模型在A团队手里是“笨蛋AI”在B团队手里却很聪明差别通常不在模型能力而在于给模型喂了什么上下文。模型回答问题时只能基于系统提示词、用户消息、历史记录和工具返回结果。如果你想让AI回答公司内部政策却只传入一句“请回答员工问题”模型只能靠通用知识猜测。正确的做法是把业务背景写进系统提示词。把相关文档或知识库结果拼到上下文里。把历史对话按时间顺序截断到合理长度。把工具返回数据明确标记为“事实依据”要求模型不得编造。这套流程决定了AI是“像内部助手一样回答问题”还是“像一个不通业务的聊天机器人”。1.3 工具调用决定AI能不能“干活”如果AI只能聊天产品经理的兴奋感通常维持不了太久。真正让人眼前一亮的AI是能查订单、算价格、写周报、读数据库、调工单系统的那一种。工具调用Function Calling / Tool Calling是把自然语言问题转成结构化动作的关键。例如用户说“帮我看看订单2001现在到哪了”AI需要从这句话里提取出“2001”这个订单号并决定调用查询订单接口。模型本身不直接访问系统它只负责生成“应该调用哪个工具、传什么参数”的指令真正执行还是由后端代码完成。工程上要处理三件事把工具描述和参数结构告诉模型。让模型在合适时机返回工具调用指令。后端执行工具返回结果再把结果交给模型组织成用户答案。只有这一步做扎实AI才从“会聊天”升级成“会干活”这也是标题里“PM连夜找我”的核心原因。1.4 稳定性与可观测性决定信任感功能上线后产品经理会在群里转发AI回答如果其中一条答案明显错误负面观感会被放大。稳定性和可观测性不是锦上添花而是AI应用能不能被信任的前提。你需要明确知道每次请求用了哪个模型、消耗了多少token、调用了哪些工具、生成了什么内容、哪一步耗时最长。没有日志和链路追踪时AI回答错误几乎无法复现因为模型输出有随机性同一个问题在不同时间可能得到不同回答。这里有一个很实际的工程判断在项目初期就把日志规范、traceId、耗时统计、成本统计建好而不是等上线出问题后再补。否则团队会对AI能力失去信心产品经理的热情也会被一次线上事故浇灭。2. 准备开发环境Spring AI与大模型调用的最小配置2.1 为什么选Spring AI在Java技术栈里Spring AI是一个面向AI应用开发的集成框架。它做的事情很像当年Spring Boot对Web开发做的事把反复出现的模型调用、提示词管理、工具调用、结构化输出、向量检索封装成一套统一API。如果你所在团队已经有Spring Boot项目引入Spring AI的成本很低不需要为了接入大模型单独写一套HTTP客户端和重试逻辑。Spring AI的另一个价值是抽象了模型平台差异通过OpenAI兼容接口可以对接多家云服务商也可以切到本地部署的模型服务业务代码改动较小。当然Spring AI本身还在快速演进不同小版本之间的API变化并不少。实际项目中要固定版本并在升级时重点回归“模型调用、工具调用、流式输出”三个核心链路。2.2 Maven依赖与版本检查下面是一个基于Maven的依赖管理示例使用Spring AI BOM统一版本避免多个包版本不一致。properties java.version17/java.version spring-boot.version3.3.1/spring-boot.version spring-ai.version1.0.0/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency /dependencies这段配置里的版本号只是示例写到这里是想说明一个原则Spring AI和Spring Boot之间有版本兼容要求不要随意选最新版本。你在实际操作时要去查看当前Spring AI官方文档确认对应版本。2.3 application.yml 模型参数说明把模型配置放到环境变量里是生产环境的基本要求。API Key不要写死在代码或直接提交到Git仓库。spring: application: name: ai-service ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL} chat: options: model: ${AI_MODEL:gpt-4o-mini} temperature: 0.3 max-tokens: 800这些参数的含义可以整理成一张表方便日常调优配置项含义常用建议错误配置的表现api-key平台访问凭证用环境变量注入调用直接401或403base-url接口地址云端或私有化网关地址连接超时、unknown hostmodel模型名称按任务复杂度选择模型不存在或响应格式不同temperature采样随机性工具类任务0.2-0.4过高容易乱调用工具max-tokens最大输出token数普通问答500-1000回答被截断、结尾不完整model参数的选择会影响延迟和成本而temperature对工具调用可靠性影响最大。工具类场景建议把temperature调低让模型更倾向于稳定的结构化输出而不是发挥想象力。2.4 本地模型与云端模型的取舍本地部署AI是很多企业内部项目关注的方向。用Ollama等工具可以在本机快速拉起一个模型服务也支持OpenAI兼容接口适合开发阶段调试。例如本地服务地址可能是http://localhost:11434/v1。本地部署的优势是数据不出内网、没有按次调用的费用、可以离线调试劣势是GPU资源有限模型规模通常不如云端模型复杂推理能力和生成质量会有差距。选型建议也很直接学习环境优先使用云端小模型或本地小模型跑通链路最重要。开发环境可以指向测试模型的独立服务方便联调。生产环境要结合数据合规要求选择云端服务或私有化部署而且至少要准备主备两套通道避免单点故障。3. 实现一个可用的AI Agent从聊天到工具编排3.1 项目结构为了便于理解项目结构按分层组织ai-service ├── pom.xml ├── src/main/java/com/example/ai │ ├── AiApplication.java │ ├── config/ChatConfig.java │ ├── controller/ChatController.java │ ├── service/OrderQueryTools.java │ └── mapper/OrderMapper.java └── src/main/resources ├── application.yml └── prompts/system-prompt.txt一个关键设计是把“模型能力”和“业务能力”分开。OrderQueryTools只负责查询订单ChatConfig只负责配置模型Controller只负责接收用户请求并转发到ChatClient。这样当订单逻辑变化时不需要改动AI调用代码。3.2 注册业务工具在一个真实项目中AI要执行的业务操作很多但并不是所有操作都适合开放给模型。以查询订单为例实现的工具方法如下Service public class OrderQueryTools { private final OrderMapper orderMapper; public OrderQueryTools(OrderMapper orderMapper) { this.orderMapper orderMapper; } Tool(description 根据订单号查询订单状态返回可读的订单信息) public String queryOrder( ToolParam(description 订单号例如20250101001) String orderId) { Order order orderMapper.selectByOrderId(orderId); if (order null) { return 未找到订单请确认订单号; } return 订单 orderId 当前状态是 order.getStatus() 金额是 order.getAmount(); } }这里需要注意几点Tool描述要尽量明确模型会根据描述决定何时调用。参数描述要给出格式示例减少模型传错参数的概率。工具返回字符串要语义完整为了让模型能直接组织成用户答案。如果工具执行失败不要直接抛异常而是返回一段描述性文字让模型可以基于结果做兜底回答。3.3 提示词模板提示词是用户和模型之间的“操作手册”。以查询订单助手为例系统提示词可以写成下面这样你是企业内部的智能服务助手。 回答规则 1. 如果用户想查询订单状态必须调用queryOrder工具不得猜测订单状态。 2. 当工具返回“未找到订单”时直接告诉用户没有查到该订单不要编造订单信息。 3. 语气简洁使用中文回答。 4. 如果用户问题与业务无关礼貌说明自己只处理订单相关咨询。这段提示词的作用是给模型划出明确边界。尤其在工具调用场景必须强调“必须调用工具不得猜测”。否则模型很可能根据训练数据里的常见订单号编造一个看似合理的结果。提示词文件可以放在prompts/system-prompt.txt启动时加载到字符串。这样做的好处是后续运营人员可以独立调整提示词不需要修改代码重新发布。3.4 ChatClient配置与多轮对话Spring AI中的ChatClient是统一入口。可以通过Builder注入系统提示词和工具Configuration public class ChatConfig { Bean ChatClient chatClient(ChatClient.Builder builder, OrderQueryTools tools) { return builder .defaultSystem( 你是企业内部的智能服务助手。 查询订单时必须使用queryOrder工具不得猜测订单状态。 如果工具返回结果包含“未找到”就明确告诉用户没有这个订单。 ) .defaultTools(tools) .build(); } }多轮对话需要注意上下文管理。常见做法是给每个用户会话绑定一个sessionId把最近N条消息存入Redis。每次调用模型时把系统提示词、历史消息、当前问题和工具结果一起发送给模型。不要把所有历史消息无限制塞进上下文因为大模型的上下文窗口有限而且历史消息越长每次请求的token成本越高、首字延迟越大。生产环境建议限制历史消息条数或总token大小超过后只保留最近几条摘要。3.5 工具调用的执行链路用户问“订单20250101001到哪了”后实际执行链路是这样的用户消息发送到后端。Spring AI把系统提示词、工具定义、用户消息一起发给模型。模型判断需要调用queryOrder返回工具调用指令。Spring AI自动找到对应Java方法传入参数并执行。工具返回订单状态字符串。模型拿到工具结果生成最终回答“您的订单正在配送中”。这个过程看似简单但有个常见坑模型可能会连续调用两个工具或者第一次调用参数不完整。生产环境下要捕捉整个调用链路的日志看清模型到底选择了哪个工具、传了什么参数、工具返回了什么内容。4. 流式输出接入把“等待”变成“对话”4.1 SSE工作原理流式输出在Web前端最常用的实现方式是SSEServer-Sent Events服务器发送事件。它基于HTTP长连接服务器可以持续向客户端推送消息适合AI生成内容的场景。WebSocket虽然也支持双向通信但AI对话场景主要方向是服务器向客户端推送tokenSSE实现更轻量后端只需返回一个text/event-stream响应前端用原生EventSource就能接收。4.2 后端返回Flux在Spring AI中ChatClient提供了流式方法。下面代码演示了一个带消息参数的GET接口返回类型是FluxSpring会按SSE格式推送RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(value /api/chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); } }这个接口把用户问题传给模型模型每生成一小段文本Flux就推送一次。前端不需要等待完整内容生成而是实时渲染。实际项目中接口还会有sessionId、userId、来源渠道等参数。生产环境不要把敏感信息放到URL查询参数里至少要使用POST请求或加密签名并将流式接口放到网关鉴权后面。4.3 前端接收逐字内容前端使用EventSource接收后可以把内容增量添加到页面上const source new EventSource(/api/chat/stream?message${encodeURIComponent(input)}); source.onmessage (event) { const part event.data; // 把part追加到当前回答区域 answerElement.textContent part; }; source.onerror () { source.close(); // 显示重试入口 };这里要注意编码问题如果接口返回的SSE事件里有换行符可能被解析成多个事件。实际项目中通常不使用Spring的SseEmitter默认格式而是统一约定返回JSON结构例如每个事件都是data: {delta:你好}前端再自行解析。这样对中文换行、特殊字符更可控。4.4 超时、取消和异常处理流式接口并不是一开到底会遇到三类常见问题用户中途关闭页面服务端还在生成内容。模型生成时间过长前端连接被网关断开。生成过程中模型平台报错。解决思路是前端监听页面关闭主动中断EventSource。后端给流式接口设置响应超时并捕获流错误后发送一个错误事件。网关层需要同步调整超时配置不能只调后端代码否则数据还没推完就被网关切断。重要会话要记录“已生成内容”的断点用户重试时可以继续而不是从头开始。流式输出不是简单替换一个返回类型而是要把前后端超时、异常、重连一起设计好才算真正完成。5. 生产级优化延迟、幻觉、成本与排查5.1 延迟优化首字延迟和完整响应时间会影响用户体验。常见的优化手段如下优化项做法注意事项模型选型简单任务用小模型复杂任务用大模型小模型对复杂指令理解可能不足精简提示词删除多余描述保留必要规则不要为了提速去除工具调用约束减少历史消息只传最近N轮丢失太早的上下文开启流式边生成边推送给前端需要处理中断和超时结果缓存相同问题和上下文直接返回缓存只适用于可复用问答不适合实时查询并发控制给模型接口做信号量限流避免调用打爆模型服务这里最容易忽视的是提示词长度。系统提示词和工具描述都会作为输入token计算工具比较多时一次请求的输入token可能比用户问题本身大很多。建议每增加一个工具都检查一下它会增加多少token以及是否真的有必要让模型知道。5.2 幻觉治理幻觉是AI应用进入生产阶段最容易被挑战的问题。模型本质上是在预测下一个词它对不确定的事实会给出“看起来合理但实际错误”的回答。治理幻觉不能完全消灭但可以明显降低概率接入RAG检索增强生成把知识库中相关内容作为上下文传给模型。要求模型在回答中引用来源例如“根据内部文档《考勤制度》第三章”。当检索结果为空时要求模型回答“知识库中没有找到相关信息”而不是编造。克制系统提示词里的引导性内容不要把不确定的信息写成事实。对高价值业务问题设置一个后置校验步骤用另一条提示词检查回答是否存在矛盾。RAG的流程可以简化成三步用户问题向量化从向量库召回TopK文档把文档拼接到系统提示词中再请求模型。它的核心价值是给模型提供事实依据让模型从“凭记忆回答”变成“读资料回答”。5.3 理解credits与成本配额在AI平台中credits通常指平台提供的调用额度实际扣费取决于token数、图片张数、视频时长或按次调用。不同平台对credits的定义不一样但核心逻辑都围绕“模型计算量”展开。对开发者的提醒是不要只关心“一次调用多少credits”而要计算一次对话完整生命周期的总消耗。一次工具调用场景往往包含多轮请求模型解析用户意图并决定调用工具的请求。工具执行后模型整理最终回答的请求。如果还带历史消息、RAG检索结果这些都会作为输入token算一次费用。因此控制成本的常用手段是限制上下文长度。给每次请求设置max-tokens上限。对非关键任务使用更便宜的小模型。在后台统计每个用户、每个会话的消耗量。credits和token的换算关系建议以你实际使用的平台文档为准上线前要有一张成本预估表避免月底账单超出预期。5.4 可观测性与日志追踪AI应用的可观测性比普通Web接口更复杂因为一次用户请求可能对应多次模型调用每次调用都可能有不同的输入输出。发布到生产前至少要记录以下字段字段示例值用途traceId8f3a2c9e串联用户请求和分析日志userId10023按用户维度分析问题sessionIdchat_20250101_001按会话维度复现上下文message用户输入确认输入是否正常modelgpt-4o-mini定位模型版本问题toolCallsqueryOrder确认工具调用是否正确inputTokens1240成本分析与限流决策outputTokens356成本分析与生成长度latency2350ms延迟分析errorMessagetimeout定位异常日志打印时要注意隐私合规不要记录用户身份证号、手机号、地址等敏感字段。如果必须记录需要做脱敏处理。6. 常见问题排查从现象到根因6.1 响应慢首字迟迟不出来可能原因往往是模型复杂、输入token过多、网络链路慢、网关缓冲导致流式没有生效。排查顺序查看后端日志中模型调用的开始时间戳和第一次返回时间戳计算首字延迟。统计本次请求的输入token数看是否因为历史消息太长导致预处理慢。检查是否真的走了流式接口前端是否在等待后端把完整响应写完后才从缓存读取。检查网络代理、负载均衡、网关是否开启缓冲导致第一批token没有及时推给前端。解决方向精简提示词、缩短历史消息、更换更快模型、关闭网关对SSE响应的缓冲。6.2 模型没有调用工具直接编造答案这是工具调用最经典的坑。现象是用户问订单状态模型回答“您的订单正在路上请耐心等待”但后端日志里没有出现任何工具调用记录。原因通常是系统提示词里没有强制要求调用工具只写了“可以调用”。temperature设置过高模型随机性增加没有走稳定路径。工具描述不清晰模型没有把该问题映射到工具功能。模型版本本身不支持复杂工具调用。解决方式在系统提示词中写“查询订单时必须使用queryOrder工具如果查询不到明确告知用户未找到”并把temperature降到0.2-0.3。同时增加一个后置校验如果模型回答中涉及订单状态但本次请求没有工具调用记录就返回“无法确认订单状态”。6.3 工具调用成功了但回答与工具返回结果不一致比如工具返回“订单已签收”模型却回答“订单还在配送中”。这种情况往往不是工具问题而是模型在组织语言时没有严格基于工具输出。处理思路提示词要求“只能基于工具返回结果回答问题不要补充工具结果之外的信息”。把工具返回结果包装成明显的结构化引用例如“工具结果订单已签收”。在代码里做简单校验如果模型回答不包含工具结果中的关键状态字段可以降级为固定模板返回。6.4 回答被截断最后一句不完整如果设置了max-tokens模型生成到上限后会被强制截断。现象是内容讲得很顺但结尾突然中断。排查方式在日志里记录outputTokens如果每次截断时输出token都接近上限说明max-tokens不够或者提示词要求模型输出过长内容。解决方式是提高max-tokens或者把长文本生成拆成多步任务不要指望一次调用完成。6.5 流式输出中断或重复前端收到完整内容后又重推了一次通常是因为EventSource意外重连服务端重新执行了请求。后端要对同一会话做幂等控制例如记录请求的唯一requestId前端重连时带上同一个requestId服务端判断是否已经处理过。中断还可能是生产环境代理服务器的空闲超时时间太短长期没有数据推送时连接被断开。解决方式是在SSE流中添加心跳事件每隔一段时间发送一个注释或空事件保持连接活跃。下表汇总了以上问题问题现象可能原因检查路径处理建议首字延迟高输入token多、模型复杂、网关缓冲日志记录开始时间与首个token时间精简提示词、开流式、换小模型模型编造答案工具调用约束弱、temperature过高日志查看未产生工具调用强制工具调用、调低temperature回答与工具结果冲突模型没有严格遵循工具结果对比工具返回与最终回答增加“只能基于工具结果”约束回答截断max-tokens过小日志观察输出token接近上限提高上限或拆分任务流式重复EventSource重连查看请求ID和服务端日志增加幂等控制7. 实战检查清单与扩展方向7.1 从Demo到生产的检查清单在把AI功能从本地Demo推到生产环境之前可以对照下面的清单逐项检查是否配置流式输出前端是否实时渲染。是否设置了用户会话隔离A用户不能看到B用户的历史消息。历史消息是否有长度限制是否做了token预算。工具调用是否有权限控制模型不能调用未授权的内部接口。工具方法是否做了参数校验和异常兜底。是否记录请求、响应、工具调用、token消耗、耗时日志。是否对敏感数据脱敏是否满足公司安全规范。是否设置了成本预警当每日调用量或credits消耗超过阈值时能否告警。是否准备了兜底回答当模型服务不可用时用户看到什么。是否建立了评测集用固定问题回归核心功能是否被破坏。这些检查项里最容易忽略的是评测集。没有评测集你很难判断一次提示词改动是在变好还是变坏。建议维护一个包含20到50条典型问题的小集合每次改动后自动或半自动跑一遍把回答结果存下来进行对比。7.2 扩展方向RAG、多Agent与评估当单Agent的对话与工具调用稳定后可以继续往三个方向扩展。RAG方向是给AI增加企业知识库能力。把内部文档分块、向量化、存入向量数据库用户提问时先检索相关内容再交给模型。它适合FAQ、政策问答、产品手册等场景。落地的重点不是向量数据库选型而是文档切分质量、召回阈值和引用展示。多Agent方向是把单一AI助手拆成多个专职角色。比如一个Agent负责订单查询一个Agent负责售后处理一个Agent负责数据分析由主Agent统一路由用户问题。这个方向复杂度明显上升要处理Agent之间的上下文共享、结果冲突和任务状态同步建议在单Agent已经稳定后再做。评估方向是为AI回答质量建立自动化指标。最基础的是人工评估让产品经理和业务人员对固定问题打分进阶做法是使用模型作为裁判对回答的相关性、准确性和完整性打分。这里的核心是要持续维护评测集否则无法判断工程改动的效果。真正让PM“连夜找你”的AI一定不是靠某个神奇Prompt而是靠工程上把延迟、幻觉、工具调用和可观测性这些基础问题逐个解决。建议从一个小范围业务场景开始先跑通流式聊天和工具调用再加入知识库与成本监控最后用评测集守住质量底线。每一步都做扎实AI应用的体验自然会被业务方看见。