news 2026/10/1 5:13:18

Spring AI工具调用实战:从订单查询到库存校验的完整落地指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI工具调用实战:从订单查询到库存校验的完整落地指南

Spring AI 的工具调用(Tool Calling)这块,我前后踩了小半个月的坑才算是真正玩明白。网上现在的资料基本都停在“Hello World”级别的示例:定义一个加法工具、让模型算一下 1+1,然后就没有然后了。但真实项目里压根不是这么回事——工具怎么描述才不会被模型忽略、参数怎么设计才不容易被解析错、多个工具之间怎么编排、调用失败怎么兜底,这些才是真正决定能不能上生产环境的细节。

这篇文章我不打算给你复述官方文档,而是把我在一个餐饮 SaaS 项目里把 Spring AI 工具调用落地到订单查询和库存校验的完整过程拆开讲。你会看到工具调用的原理本质、为什么这么设计、实际编码时的要点,以及我踩过的一堆坑。如果你正准备在自己的 Spring Boot 项目里接入 AI Agent,或者正在纠结“到底用 Spring AI 还是 LangGraph4j”,这篇文章应该能帮你省下不少时间。

1. 工具调用是什么:先把这块拼图的位置搞清楚

1.1 从“对话模型”到“能动手的模型”

先说个最基础的问题:为什么需要工具调用?

大语言模型本质上是一个概率预测器,它只能根据输入的 token 预测下一个 token。这意味着它有两个天然缺陷:第一,训练数据有截止时间,它不知道当下这一刻的天气、库存、订单状态;第二,它没有权限执行任何操作,不能真的去查数据库、调接口、发消息。早期要做 AI 应用,只能靠提示词让模型“假装”知道,或者把数据一股脑塞进上下文——前者是胡编,后者是浪费 token,而且上下文窗口有限。

工具调用(Tool Calling / Function Calling)解决的就是这个问题:让模型在推理过程中识别出“此刻需要外部信息或外部操作”,然后输出一个结构化的调用请求,由我们的代码去真正执行,再把执行结果回传给模型,让模型基于真实结果继续生成回答。

用生活化的类比来说:你请了一个知识渊博的助理,但这个助理没有电话、没有电脑、不能出门。工具调用就是给他配上了这些“手脚”——他遇到不知道的事,会写一张纸条递给你,你去查完再把结果告诉他,他基于结果给你最终答复。

1.2 Spring AI 在这块做了什么

Spring AI 是 Spring 生态官方推出的 AI 应用开发框架,它做了一件很重要的抽象:把各家大模型厂商的差异尽量抹平。你写一套工具定义,OpenAI、通义千问、DeepSeek、Ollama 本地模型都能用,只是底层适配不同。

具体到工具调用,Spring AI 的核心封装包括两层:

  • 把 Java 方法自动转换成模型可识别的 JSON Schema 描述(包括方法名、参数名、参数类型、注释说明);
  • 在对话循环中自动处理模型的工具调用请求,由框架调用你的方法,再把结果放回对话上下文。

你不需要手写繁琐的 JSON Schema,也不需要自己维护多轮调用的消息历史拼接,框架把这些脏活都干了。这点非常关键,因为我一开始手写过 OpenAI 格式的 tools 定义,那会儿光维护参数格式就够呛,模型一换格式就变,Spring AI 至少在格式适配层帮你把活揽下来了。

1.3 为什么说它是 Agent 的基石

没有工具调用的模型,再聪明也只是个“键盘侠”;有了工具调用,模型才真正具备了执行能力。你看现在所有的 Agent 架构——无论是 LangChain 系的、LangGraph 系的、还是 Spring AI 自己的 Agent——底层核心都是这个模式:

用户输入 → 模型判断是否需要工具 → 发起工具调用请求 → 代码执行工具 → 结果回传 → 模型继续推理 → 循环直到给出最终答案

这个“判断-调用-回传-再判断”的循环,英文叫 Agent Loop。Spring AI 的工具调用就是 Agent Loop 的最小闭环。你先把这一层玩透了,后面再去理解多工具编排、子 Agent、多步规划,都会变得顺理成章。

2. 设计思路:什么场景用工具,什么场景别硬凑

2.1 工具调用、RAG、提示词,三者的边界

我见过不少团队一上来就堆工具,把什么都塞给模型去调,结果效果稀烂还费钱。其实工具调用不是万能的,在动手之前,你得先判断目标场景到底适合哪种方案。

RAG(检索增强生成)解决的是“知识不在模型脑子里”的问题。比如企业内部的制度文档、产品手册、历史工单,这些内容体量大、需要溯源,应该走向量检索,把相关片段检索出来塞进上下文,而不是做成工具。因为把整本手册塞给工具参数显然不现实,让模型“翻书”找答案也远不如向量检索精准。

提示词解决的是“规则明确、不需要外部数据”的问题。比如让模型帮你润色文案、提取关键词、做情感分类,这些纯粹靠模型自身能力就能完成,工具调用反而是画蛇添足。

工具调用适合的是“信息在系统里、操作由代码执行”的场景。典型的有查订单状态、查实时库存、查天气、发审批通知、调用第三方 API、执行数据库查询。一句话总结:凡是需要实时数据或真实操作的,才考虑用工具;凡是知识性的、规则性的,优先用提示词或 RAG。

2.2 工具粒度的设计:粗一点还是细一点

工具设计是个典型的“度”的把握。粒度太细,工具数量爆炸,模型在多个工具之间犹豫不决,还容易调错;粒度太粗,一个工具啥都能干,参数一堆,模型反而不知道该怎么填。

我在餐饮 SaaS 项目里的经验是:按用户的业务意图来划分工具,而不是按后端接口来划分。比如后端可能有selectOrderById、selectOrderByPhone、selectOrderByShopId三个接口,但如果做成三个工具,模型就会纠结用户说的“帮我查一下订单”到底该调哪个。

我更建议合并成一个工具叫queryOrder,参数里给一个灵活的条件结构。同时在工具描述里写清楚:当用户提到订单号时填写 orderId,提到手机号时填写 phone,提到店铺时填写 shopId。这样模型面对的是“业务动作”,而不是“接口实现”,调用准确率会明显提升。

反过来也有反面案例。我最初做过一个工具叫executeSql,意图是让模型直接写 SQL 查询数据库。听起来很灵活,但实际用起来非常可怕——模型生成的 SQL 经常语法有问题,甚至有超时风险。后来改成queryOrder、queryDish、queryInventory等几个语义明确的工具后,稳定性大幅提升。

2.3 工具描述的写作诀窍

工具调用能不能成功,很大程度上取决于你怎么写工具描述。模型看不到你的 Java 代码,它只能看到你写在注解里的描述文字和参数说明。这段描述是模型做决策的唯一依据。

我的写法参考了一个原则:站在模型的角度描述触发条件。不要写“查询订单信息”这种干巴巴的话,而要写“当用户询问任何与订单相关的问题(包括订单状态、订单金额、配送进度、订单详情)时,使用此工具。如果用户提供了订单号,请填入 orderId 参数;如果提供了手机号,请填入 phone 参数”。

参数说明同样重要。比如status参数是枚举值,你要把每个枚举值对应的含义写清楚。比如status=0 表示待支付,1 表示已支付,2 表示配送中,不然模型极有可能填一个它自己“认为”合理的值。

我还习惯在工具描述里加上“不要使用此工具的场景”,比如“当用户只是想要下单操作说明时,不要调用此工具,直接回复使用步骤”。这个负面排除项对降低误调用率非常有效。实测下来,加上负面描述后,工具误调用率能降低至少三成。

3. 核心原理:模型是怎么知道该调哪个工具的

3.1 工具描述如何变成上下文

抛开黑盒焦虑,工具调用的内部机制其实不神秘。在每次请求模型之前,Spring AI 会把所有已注册工具的信息——方法名、功能描述、参数名、参数类型、参数描述、是否必填——转换成 JSON Schema 数组,混入系统消息里发送给模型。

模型的注意力机制会阅读这些工具定义,然后在生成回答时做两件事的判断:当前用户的这个问题需不需要调用工具?如果需要,该调用哪一个、参数应该填什么?当模型判断需要调用时,它不会生成正常的文本回答,而是输出一个结构化的工具调用指令,格式类似{"name": "queryOrder", "arguments": "{\"orderId\": \"12345\"}"}。

Spring AI 的底层适配器会解析这个输出,识别出工具名和参数 JSON,然后通过注册中心的映射找到对应的 Java 方法,用反射把参数绑定进去执行。执行完毕后的返回值会被包装成一条ToolResponseMessage,附加到对话历史里,再次发给模型。模型看到“工具结果”后,生成面向用户的最终答复。

3.2@Tool注解背后发生了什么

Spring AI 最常用的方式是在方法上打@Tool注解。这个注解的妙处在于它会读取方法签名、参数注解、以及参数对象里的字段描述,自动生成 JSON Schema。

比如这样写:

@Tool(name = "queryOrder", description = "查询订单信息,当用户询问订单状态、金额、配送进度时使用") public String queryOrder(@ToolParam(description = "订单号,用户提供时必填") String orderId, @ToolParam(description = "下单手机号") String phone) { // 业务逻辑 }

Spring AI 会把name作为工具名,description作为工具描述,@ToolParam的 description 作为参数描述。如果你的参数是一个对象类型,Spring AI 会递归解析对象字段,同样按照字段名和@ToolParam描述生成嵌套的 JSON Schema。这一点对复杂参数的建模非常友好,你不需要手动写任何 JSON 结构定义。

这里有个容易被忽略的细节:@Tool也可以标注在类上,表示这个类里所有带@Tool注解的方法都注册为该类的工具集。我习惯把同一业务域的工具放到同一个类里,比如OrderTools里放queryOrder和cancelOrder,InventoryTools里放queryInventory和updateInventory,这样注册和管理都清晰。

3.3 注册方式与模型指定

注册工具有两种常用方式。第一种是基于 Spring Bean 的自动发现:只要@Tool标注的方法所在的类是一个 Spring Bean,并且你在ChatClient构建时调用了.defaultTools()或.tools()方法,Spring AI 就会自动找出这些方法注册为工具。

推荐用这种方式,因为它把工具的发现和管理交给 Spring 容器,依赖注入也方便。比如你的工具方法里需要注入OrderService、JdbcTemplate,直接@Autowired或构造器注入就好了,模型每次调用都会走完整的 Spring Bean 生命周期。

还有一点值得注意:不同模型的工具调用能力有差异。OpenAI 系的模型(gpt-4o、gpt-4o-mini)工具调用非常稳定;通义千问的 qwen-plus 也支持;DeepSeek 的 Function Calling 也不错;但是小参数量的本地模型(比如 7B 以下的量化模型)工具调用效果就很不稳定,经常不按格式输出或者乱填参数。我的建议是:如果你要做严肃的工具调用,至少用 13B 以上或云端中大型模型,本地小模型做玩具可以,生产环境还是别冒险。

4. 实操记录:把工具调用接进 Spring Boot 项目

4.1 配置环境和依赖

我用的是 Spring Boot 3.2 + Spring AI 1.0.0(这套写法在 Spring AI 0.8.x 上也基本兼容,只是个别 API 名有差异)。Maven 依赖如下:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>

如果你是接通义千问,用spring-ai-tongyi-spring-boot-starter;接 DeepSeek 的话,DeepSeek 兼容 OpenAI 协议,直接用 OpenAI starter 然后改 base-url 就行:

spring: ai: openai: base-url: https://api.deepseek.com api-key: ${DEEPSEEK_API_KEY} chat: options: model: deepseek-chat temperature: 0.7

注意:temperature 建议设置在 0.2~0.7 之间。工具调用内部的参数生成需要一定的稳定性,温度过高容易导致模型在工具名和参数上“发挥创意”,温度太低又会让模型过于保守甚至拒绝调用工具。我在实际项目中一般用 0.3~0.5 这个区间。

4.2 从 ChatClient 到工具调用的完整代码

Spring AI 1.0 之后的推荐用法是ChatClient,链式调用非常爽快。我直接贴一个完整的示例,这既是我在餐饮项目里实际使用的简化版,也是你能直接跑通的最小可复现代码。

先定义一个订单查询工具:

@Component public class OrderTools { private final OrderService orderService; public OrderTools(OrderService orderService) { this.orderService = orderService; } @Tool(name = "queryOrder", description = "根据订单号或手机号查询订单信息。" + "当用户询问订单状态、订单金额、配送进度、订单详情时使用此工具。" + "如果用户提供了订单号,优先填入orderId;如果只提供了手机号,填入phone。") public String queryOrder( @ToolParam(description = "订单号,例如 O20250601001") String orderId, @ToolParam(description = "下单时使用的手机号,11位数字") String phone) { if (orderId != null && !orderId.isBlank()) { return JSON.toJSONString(orderService.getByOrderId(orderId)); } if (phone != null && !phone.isBlank()) { return JSON.toJSONString(orderService.listByPhone(phone)); } return "参数不足,既没有订单号也没有手机号"; } }

然后构建 AI 服务:

@Service public class AiOrderAssistant { private final ChatClient chatClient; public AiOrderAssistant(ChatClient.Builder builder) { this.chatClient = builder .defaultSystem("你是一个餐饮SaaS客服助手,可以帮用户查询订单信息。") .defaultTools("queryOrder") .build(); } public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }

注意.defaultTools("queryOrder")这一行,传入的是@Tool注解里定义的工具名。你还可以用.defaultTools(OrderTools.class)批量注册某个类下所有工具。

启动项目后,你调用这个接口,输入“帮我查一下手机号 13800138000 的最近一笔订单”,模型会先调用queryOrder工具拿到订单数据,再组织语言回复。整个过程你完全不需要手动处理工具调用的中间状态,框架都帮你搞定了。

4.3 自定义工具返回结果:不是所有返回都能直接给模型

这个点是我踩坑之后才意识到的。工具方法的返回值最终会被塞回上下文再发给模型,所以返回值必须满足两个要求:字数要精简,内容要结构化。

最开始我偷懒,直接把数据库实体类序列化返回,结果一次查出来 200 多个字段的大 JSON,模型读是读懂了,但 token 消耗感人,而且响应时间变长。后来我改成了专门为工具调用设计的精简 VO,只返回模型真正需要的信息:订单号、状态、金额、商品列表、配送地址、预计送达时间。对模型来说,“够用”比“完整”更重要,它能基于这些字段回答用户绝大部分问题。

另外,工具方法里做业务操作失败时,不要直接抛异常给 Spring AI。因为异常一旦抛出,整个对话就会中断,模型没有机会做补充解释。更优雅的做法是返回一个错误描述字符串,比如“订单号不存在,请确认后重试”,让模型基于这个结果组织给用户的回复。这样用户体验要友好得多。

4.4 多工具场景下的顺序编排

单工具是入门,多工具才是日常。比如用户问“我想把订单 O20250601001 里的辣椒炒肉退掉,顺便看看这家店还有没有奶茶”,这里涉及查订单、查菜品、查库存三个工具的协同。

Spring AI 的默认行为是:一次请求里,模型可以并行发起多个工具调用(取决于底层模型是否支持并行调用)。你不需要手动编排顺序,模型会自己判断哪些工具可以同时调用。但这里有个隐含前提:你的所有工具之间不能有强依赖关系。如果工具 B 的执行依赖工具 A 的结果,那模型通常会先调 A,看到结果后再调 B,它会自动多轮循环。这也是 Agent Loop 的意义所在——模型自己控制调用的节奏。

我在项目里给餐饮连锁店做过一个“复购推荐”场景,就涉及三个工具:queryUserOrders查历史订单、queryDishById查菜品详情、queryShopById查门店信息。模型在多轮里依次唤起这些工具,最终给用户推荐了一套“根据你上次点的菜推测你会喜欢”的组合。这个效果只靠提示词永远做不到,因为菜单信息是实时变化的,必须靠工具去拉取。

4.5 增加业务约束:同一个工具,材料不同结果就不同

很多人忽略了工具方法里可以做权限校验。模型决定调用工具,但工具内部可以校验“这个用户有没有权限查这笔订单”。

我在工具方法里注入了当前登录用户的信息(把 userId 通过上下文传入),然后在queryOrder里先判断订单归属是否与当前用户一致,不一致就直接返回“无权查看该订单”。这样做的好处是:模型层不需要做任何权限逻辑,它只负责理解用户意图;权限这种硬规则写在代码里永远比让模型自觉可靠。

这条经验来自一次真实的教训:早期我没做权限校验,模型只要知道订单号就能查任何订单。在一个内部测试环境里还好,但到了生产环境,一旦有用户拿别人的订单号来试探,就泄漏了数据。这是个非常严重的安全问题。所以工具调用落地时,一定要把权限、限流、审计都当作一等公民放进工具方法里,而不是寄希望于模型。

5. 常见问题与排查技巧实录

5.1 工具没被调用,模型直接编答案

这是最最常见的现象。我排查的思路分三步。

第一步,检查模型是否能识别当前问题属于你的工具能解决的范畴。把用户的原始问题、你的工具定义都打印出来,看看是不是因为描述太窄导致模型判断“不需要用工具”。比如店员问“我们这个店一天开几单”,你的工具描述只写了“查询订单状态”,那模型自然想不到该调它。解决方法是把工具描述和触发场景写得更宽泛一些。

第二步,确认工具是否真的被注册进去了。在构建ChatClient之后,可以打印一下它实际注册的工具列表。Spring AI 支持通过ChatClient的配置来查看;如果你不确定,可以在工具方法里打一条日志,调用时能看到。如果日志没出现,说明模型压根没发起工具调用,问题多半在描述或模型能力上。

第三步,看看是不是模型版本太弱。同一段代码,我换用 gpt-4o 一切正常,换用某个 7B 本地模型就完全不调用工具。这不是配置问题,是模型本身能力不够。升级模型或者换更强的服务是唯一的出路。

5.2 工具被调用了,但参数解析错了

最常见的问题是数字、日期、枚举这类参数被模型填了异常值。比如一个工具参数限定枚举值为PENDING、PAID、DELIVERING,模型却填了已支付。这是因为模型只是从描述里“猜”参数格式,它不是严格遵守者。

我的解决方式有几个:

  • 在@ToolParam描述里把合法值的取值范围写全,比如 “可选值:PENDING(待支付)、PAID(已支付)、DELIVERING(配送中)”;
  • 在工具方法内部做容错,对枚举值多写一个匹配方法,比如"已支付".equals(status)也映射到PAID;
  • 对简单场景用 Boolean 或 Integer 替代字符串枚举,降低模型理解成本。

还有一个细节:如果你的参数对象里用了 LocalDateTime 这类格式,模型的输出极有可能不符合 Java 的序列化格式。我建议所有工具参数和返回值都尽量避免复杂的时间格式,统一转成字符串(“2025-06-01 12:30:00”)传输,然后在方法内部解析。

5.3 工具调用超时和性能问题

工具方法里如果做了慢操作——查数据库、调外部 API、执行复杂的计算——整个对话的响应时间会被拖长。因为模型在等待工具返回结果的过程中,用户是干等的。

我的优化思路是:工具方法内部能缓存就缓存,能走索引就走索引。还有一个技巧:把工具调用设计成“先返回关键信息,再补充详情”的两阶段模式。比如queryOrder先返回订单的基本状态和金额,模型基于这些信息先给用户一个大致的答复;如果用户追要明细,模型再发起queryOrderDetail获取完整数据。这样大部分请求都能快速响应。

5.4 工具调用结果太长了,模型反而不会总结

这是我在双十一大促场景踩过的坑。外卖平台的订单量巨大,用手机号查订单时返回了 50 条订单记录,结果模型在回传时因为上下文过长,生成的回复质量直线下降,还经常截断。

解决办法是:在工具方法内做好“预聚合”和“限制条数”。比如只返回最近 5 条订单,或者直接在 SQL 层做统计(订单数、总金额、近一周趋势),然后把统计结果返回给模型。模型更擅长解读总结好的数据,而不是从一百行原始 JSON 里找规律。记住一个原则:工具返回的应该是“加工后的结果”,不是“原始数据”。

5.5 Spring AI 还是 LangGraph4j:用场景说话

这个热搜词我看到了,估计很多人都在纠结。我的看法很简单:如果你的技术栈是 Spring Boot 全家桶,而且你需要的工具调用复杂度是“模型根据意图调几个方法、做多轮循环”,Spring AI 完全够用,甚至是最省事的选择。它贴近 Spring 生态,配置简单,团队上手成本低。

但如果你的场景是复杂的 DAG 编排、多智能体协作、有明确的状态机流转和人工介入节点,LangGraph4j 这类流程框架更合适。它是把 LangGraph 的图执行概念移植到了 Java,适合把每个 Agent 步骤拆成图节点,精细控制流转和状态。

我个人在餐饮 SaaS 项目里的经验是:90% 的工具调用需求用 Spring AI 就够了,而且它的抽象层让你以后换模型厂商不用改业务代码。剩下的 10% 才需要考虑上更重的 Agent 编排框架。还是那句话,技术选型要跟着场景走,别为了炫技上重型武器。

5.6 NL2SQL 和 RAG 能不能结合起来

Spring AI Alibaba 有nl2sql的能力,就是让模型把自然语言转成 SQL。我试过之后发现一个规律:直接用工具调用的方式包装 SQL 执行,虽然灵活但风险高;更稳妥的做法是把限定条件、表结构说明都放在工具描述里,让模型只能基于白名单表来生成查询。

比如我定义了一个工具queryStatistics,描述里写明“当前可查询的指标有:营业额、订单量、客单价,可按时间范围、门店维度筛选”。模型只在这个指标白名单内生成查询条件,不会自己去创造 SQL。这种有界面的 NL2SQL,比让模型自由发挥安全得多。

至于 RAG,它和工具调用不是替代关系,而是配合关系。比如用户在问“你们平台怎么处理退款”这种制度问题时,走 RAG 检索手册;在问“我这个订单退款到哪了”这种实时问题时,走工具调用。我在项目里是把两者都注册到同一个ChatClient里,模型自己判断该走哪条路。

6. 从单工具到 Agent:走向更复杂的能力编排

6.1 把工具调用串成 Agent Loop

当你理解了工具调用之后,想往上再走一步,就涉及 Agent 的完整循环。Spring AI 的ChatClient默认会做多轮工具调用循环,但如果你需要更多控制——比如限制最大迭代次数、在每一轮之间插入日志、做结果校验并重试——就需要自己实现一个循环。

我自己写过一个简单的循环,核心逻辑大约是:

for (int i = 0; i < maxIterations; i++) { ChatResponse response = chatClient.prompt() .messages(messages) .call() .chatResponse(); if (response.hasToolCalls()) { // 执行工具调用 List<ToolResponseMessage> toolResults = executeToolCalls(response.getToolCalls()); messages.addAll(toolResults); } else { // 模型给出了最终文字回复 return response.getTextContent(); } }

这个模式虽然粗,但能帮你理解 Agent 的本质:在“思考”和“执行”之间反复横跳,直到得出最终答案。Spring AI 内部其实也是类似逻辑,只不过它帮你隐藏了循环细节。

6.2 工具注册的边界:什么时候该收敛

工具的粒度不仅影响模型调用准确性,还影响维护成本。我有个原则叫**“一个业务场景最多注册 5 到 8 个工具”**。一旦超过这个数,模型面对一堆相似工具时就会出现张冠李戴的问题,而且你很难通过描述把所有工具的边界讲清楚。

如果业务确需要很多能力,我建议把工具分组,按会话阶段或用户意图加载不同的工具集。比如用户在“查单”阶段只注册订单类工具,在“下单”阶段注册购物车和结算类工具,阶段切换由你的业务逻辑把控,而不是一股脑全塞给模型。这本质上是一种轻量级的“路由”,能显著提升大规模工具场景下的准确率。

6.3 从工具调用出发的后续扩展方向

工具调用玩熟之后,可以尝试的方向包括:

  • 把工具结果接入业务系统的事件总线,实现 AI 驱动的自动化操作(比如模型识别到用户投诉,自动创建售后工单);
  • 结合定时任务 + 工具调用,做主动式 AI 助手,每天定时检查订单履约情况,异常时通过工具下发通知;
  • 把多个工具编排成可复用的“技能包”,配合向量检索实现动态装配——模型先检索到合适的技能描述,再动态加载对应的工具。

这些其实都是 Agent 理论的工程化落地。归根结底,工具调用就是 Agent 世界里“行动力”的具体实现。

我在实际项目里还有个小技巧:每个工具方法都写一个@AuditLog之类的注解,记录模型每次调用的入参和出参。这些日志在调试时价值极高——你能看到模型“为什么这么填参数”、哪一步出了问题。有个阶段模型老是查错店铺,靠日志才发现是因为工具描述里没写清楚 shopId 和门店名称的对应关系,改完描述准确率一下上来了。所以记住,工具调用排错的第一步永远不是猜模型在想什么,而是看日志。

整体走下来,Spring AI 的工具调用已经算很成熟了,只要分清场景、克制设计、守住权限底线,它能帮你做出真正“能干活”的 AI 功能,而不是一个只会聊天的玩具。

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

AI Agent生产落地四道坎:稳定、并发、记忆与安全

开头先泼盆冷水。我见过太多这样的项目&#xff1a;Demo 演示的时候&#xff0c;Agent 在台上侃侃而谈、把工具调用得行云流水&#xff0c;客户当场拍板。结果一上线&#xff0c;不是答非所问&#xff0c;就是卡在某个工具调用里出不来&#xff0c;要不就是并发一上来直接超时&…

作者头像 李华
网站建设 2026/10/1 5:12:33

Madeira兼容层解析:FEX-Emu与Wine如何实现x86-64应用跨平台运行

1. 从“Madeira”这个名字说起&#xff1a;一个跨平台兼容层的野心第一次看到“Madeira”这个项目名&#xff0c;很多人会以为是某个旅游项目或者葡萄酒品牌。但结合热搜词里的 FEX-Emu、Wine、DXMT、x86-64 这些关键词&#xff0c;方向就很清楚了——这是一个围绕x86-64 应用在…

作者头像 李华
网站建设 2026/10/1 5:12:17

Spring Boot + Vue电影院购票系统:从选座并发到部署上线

1. 项目定位与技术选型&#xff1a;为什么是 Spring Boot Vue 这对组合先聊聊这个项目到底是个什么东西。电影院购票管理系统&#xff0c;说白了就是一套完整的线上售票解决方案&#xff0c;覆盖了用户从浏览影片、查看排片、选座下单到支付取票的全流程&#xff0c;同时给影院…

作者头像 李华
网站建设 2026/10/1 5:11:13

USACO黄金组真题解析:搜索剪枝与区间DP的算法思维

USACO&#xff08;美国计算机奥林匹克&#xff09;黄金组&#xff0c;这个难度区间在我刷题经历里一直是最有训练价值的题库。2005年11月这套真题&#xff0c;我备战算法竞赛时反复啃过好几遍&#xff0c;后来翻大厂笔试真题&#xff0c;发现里面不少思路都能对上号。这篇解析就…

作者头像 李华
网站建设 2026/10/1 5:11:11

C++虚函数表vtable与虚指针vptr内存布局深度解析

1. 为什么你写的多态代码“看起来对”&#xff0c;却在内存里悄悄出错&#xff1f;我第一次真正意识到虚函数表不是教科书里的抽象概念&#xff0c;是在调试一个嵌入式设备的通信模块时。那是个基于C11开发的CAN总线协议栈&#xff0c;核心用了一个BaseProtocol类派生出CANopen…

作者头像 李华
网站建设 2026/10/1 5:10:48

SUMO交通仿真进阶:真实路网导入、OD需求生成与TraCI交互实战

做交通仿真的人大多经历过这么一个阶段&#xff1a;路网能加载了&#xff0c;车也能跑起来了&#xff0c;但屏幕上稀稀拉拉几辆车绕圈&#xff0c;根本看不出任何有价值的结论。我前两篇把 SUMO 的安装、基本路网、最小可运行配置捋了一遍&#xff0c;那只能算"环境通了&q…

作者头像 李华