news 2026/9/30 15:30:21

Spring AI Function Call实战:让大模型学会调用外部工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI Function Call实战:让大模型学会调用外部工具

最近好几个朋友来问Spring AI里Function Call到底怎么用,尤其是从 Spring AI 1.0 GA 版本开始,API做了不少调整,网上的教程又良莠不齐,照着抄经常跑不通。这个系列前面已经聊了模型接入、Prompt 模板、RAG、结构化输出,今天这篇把 Function Call 单独拉出来讲清楚。它不是个炫技功能,恰恰是大模型从"聊天玩具"变成"业务系统一部分"的关键节点——模型负责理解意图、拆解任务,代码负责执行具体操作,各干各擅长的事。

这篇文章主要面向两类人:一是已经用 Spring AI 跑通了基础对话、RAG,想把手上的功能"接出去"的 Java 开发;二是被 Function Call 各种概念绕晕,想知道底层原理、好避坑的同学。我会从机制讲起,再对比 Spring AI 的几种 API 姿势,最后带一个完整实操,把我踩过的坑一并抖出来。

1. 为什么需要 Function Call,它到底解决了什么问题

1.1 大模型的两个天花板:知识时效性与确定性计算

先说一个最基本的认知:大模型本质上是一个"高压缩的文本生成器",它的知识来自训练集,是有截止日期的。你问它"今天上海天气如何",它不知道,它只知道训练数据里 2023 年某个日期的天气规律;你问它"3.14 乘以 2.6 等于多少",它经常算错,因为它在做 token 概率预测,不是在执行算术运算;再比如业务场景里"这个订单处于什么状态""库存还剩多少",这类数据在数据库里,模型压根看不到。

这不是模型"笨",而是架构决定的。大模型擅长的是语义理解、意图判断、文本生成,但遇到需要实时数据、精确计算、业务规则校验、操作外部系统这类任务时,它的表现就是"一本正经地胡说八道"。RAG 能解决一部分知识时效性问题,把相关资料塞进上下文让模型参考,但它解决不了"执行动作"的问题——模型说"我可以帮你发货",实际上它动不了任何物流系统。

这时候就需要一种机制,让模型在对话过程中检测到"这个问题我答不了,但我可以去调用一个函数来解决",然后把结果拿回来再组织回答。这个机制就是 Function Calling(也叫 Tool Calling)。

1.2 Function Call 的运行机制:模型不是执行者,而是决策者

很多第一次接触的人会有一个误解,觉得 Function Call 是"模型调用我写的代码"。这话说对了一半,更准确的说法是:模型根据对话内容,决定"应该调用哪一个工具、传什么参数",真正执行的是你的应用代码。

完整流程分四步:

  1. 你提前把若干函数的"说明书"注册给模型,说明书包括函数名称、功能描述、参数结构(JSON Schema)。这一步在 Spring AI 里通常就是把一个 @Bean 函数绑定到 ChatClient 上。
  2. 用户发来一句话,比如"帮我查一下订单 SO20240901001 到哪了"。模型在生成回答前,先对比这句话和函数说明书,判断"这里需要用 orderStatusFunction 这个函数,参数 orderNo 等于 SO20240901001"。
  3. 模型不是把这句话直接答给用户,而是返回一个特殊结构给应用:我要调用函数 orderStatusFunction,参数是 {"orderNo": "SO20240901001"}。Spring AI 收到这个结构后,在服务端执行对应的 Java 方法,拿到真实结果。
  4. 应用把函数执行结果回传给模型,模型基于这个真实结果,生成一句给用户的最终回答:"您的订单正在运输途中,当前位于上海分拨中心。"

整个过程看起来像模型自己去查了数据,实际上是你的代码替它跑了腿。用一个生活化的类比:你(用户)向老板(模型)问公司财务状况,老板不直接回答,而是让财务(函数)去查报表,拿到数字之后,老板再给你一个谈吐得体的答复。老板是决策者,财务是执行者,数据永远是财务说了算。

这个机制的意义非常大:它不改变模型的知识边界,而是给了模型一条"按需外接"的路径。查询订单、查天气、算价格、写数据库、调用第三方 API,只要定义成函数,模型就能"学会用",而且技能可以无限扩充。

1.3 和 RAG、Agent 的区别:别把工具和能力混为一谈

聊到 Function Call,很多人会和 RAG 混在一起。我简单总结两种思路的差异,帮你建立判断力:

  • RAG 是把外部资料先检索出来,塞进 Prompt 上下文,让模型"带着资料回答"。它解决的是"模型不知道但资料里有"的问题,本质是增强模型的背景知识。
  • Function Call 是模型判断"我需要外部系统给我一个结果",然后通过代码去拿。它解决的是"模型需要实时数据或执行动作"的问题,本质是给模型接上手脚。

RAG 更像是给员工一本参考资料让他翻阅;Function Call 是让员工直接打电话给业务系统问现状。你可以两个都用,并不冲突:先 RAG 找到相关文档,再 Function Call 去查文档里提到的订单实时状态,这种组合在真实项目里很常见。

另外,Function Call 是 Agent(智能体)的核心基础。Agent 的"规划-执行-观察-再规划"循环里,执行这一步落地就是靠函数调用。你学会了 Function Call,后面学 Agent 框架,就会觉得很顺;反过来,如果连 Function Call 的机制都理解不透,用 Agent 框架基本等于黑盒操作,出了问题完全不知道从哪排查。这也是我建议 Spring AI 使用者先把 Function Call 单独吃透的原因。

2. Spring AI 的 Function Call 能力拆解:从 API 设计到实现原理

2.1 三种使用姿势:ChatModel 直接调用、ChatClient 链式调用、流式调用

Spring AI 从 1.0 GA 开始,主推的是ChatClient,这个类名和 Spring 生态里的RestClient、WebClient风格一致,链式调用写起来非常舒服。但如果你很早就在用 Spring AI,可能会见过ChatModel直接调用的写法,它依然保留,只是更底层一些。实际项目中两种方式各有适用场景,我分别说一下。

第一种,ChatModel直调。这是最底层的编程模型,你需要自己构造ChatRequest或使用Prompt,然后传入包含函数调用选项的ChatOptions。优点是灵活,适合自己封装公共组件、写一些框架级代码;缺点是碎,每个参数都要你手动拼。

@RestController public class OrderChatController { private final ChatModel chatModel; public OrderChatController(ChatModel chatModel) { this.chatModel = chatModel; } @PostMapping("/chat") public String chat(@RequestBody String userMessage) { // 构造函数调用选项,注册函数Bean名称 var options = OpenAiChatOptions.builder() .withFunction("orderStatusFunction") .build(); var prompt = new Prompt(userMessage, options); var response = chatModel.call(prompt); return response.getResult().getOutput().getContent(); } }

第二种,ChatClient链式调用。这是目前官方文档主推的用法,也是我推荐大多数业务团队使用的。它以会话为中心,把system、user、functions、params都做成链式方法,代码可读性高,维护成本低:

String answer = chatClient.prompt() .system("你是一个订单助手,请礼貌简洁地回答用户问题。") .user("帮我查一下订单 SO20240901001 到哪了") .functions("orderStatusFunction") .call() .content();

第三种,流式调用。实际聊天应用几乎都要流式输出,因为大模型生成完整回答可能要好几秒,如果让用户一直干等,体验很差。Spring AI 提供了stream()方法,配合Flux<String>让内容按 token 逐个推送。这里有一个关键点:在需要 Function Call 的场景里,流式调用必须开启工具调用会话支持,否则可能出现"函数执行结果还没回来,流已经结束了"的问题。后面实操部分我会专门演示。

2.2 函数的定义方式:@Bean + 注册的完整链路

在 Spring AI 里,一个 Function Call 的函数本质上是一个java.util.Function。你把某个 Spring Bean 的某个方法包装成一个 Function,然后通过@Description注解告诉模型这个函数是干什么的、什么时候该用。

先看一个最标准的定义:

@Configuration public class OrderFunctions { private final OrderService orderService; public OrderFunctions(OrderService orderService) { this.orderService = orderService; } @Bean @Description("查询指定订单的物流状态,参数为订单号,订单号格式形如 SO20240901001") public Function<OrderStatusRequest, OrderStatusResponse> orderStatusFunction() { return request -> orderService.queryStatus(request.orderNo()); } }

这里有几个细节值得注意。

@Description里的描述,是模型判断"什么时候该调用这个函数"的唯一依据。写得太笼统,模型可能在该用的时候不用;写得太啰嗦,又会挤占上下文空间,影响模型理解。我的经验是:描述里必须包含触发场景和参数格式示例。比如"当用户询问订单物流、配送进度、包裹位置时,调用此函数查询,参数订单号格式为 SO 开头加一串数字",这样一个描述下来,模型基本不会搞错。

参数对象OrderStatusRequest和返回对象OrderStatusResponse直接决定 Spring AI 生成的 JSON Schema。Spring AI 会利用 Jackson 的 Bean 序列化能力,把 Java 对象转换成 JSON Schema 描述,发给模型。所以你的字段名要起得规范一些,最好加上注释或用@JsonPropertyDescription这种注解说明字段含义,否则模型可能理解不了"orderNo"到底是订单号还是订单数量。

注册函数的方式有几种:可以用@Bean配合.functions("orderStatusFunction")按名称注册,也可以在ChatClient构建时用.defaultFunctions("orderStatusFunction")注册默认函数。默认函数的意思是,每次对话都会自动带上这些工具的说明书,模型随时可以用,不需要每次调用时再显式指定。我用得最多的是defaultFunctions,因为业务助手通常有固定的工具集,默认注册省心。

这里要注意一个容易踩的坑:@Bean方法名和functions()里传入的名称必须完全一致。Spring AI 是根据 Bean 名称去容器里查找对应 Function 的,如果你写了@Bean("orderStatus")却在注册时写"orderStatusFunction",运行时会直接跟你翻脸。

2.3 JSON Schema 自动生成与手工干预

模型之所以知道函数的参数怎么传,靠的不是研究你的 Java 代码,而是拿到的参数 JSON Schema。Spring AI 内置了JsonSchemaGenerator,它会根据你的参数类自动生成 JSON Schema。大部分情况下你不需要管它,但有两种情况需要手工干预。

第一种情况:参数类里有可选字段、枚举、嵌套对象,自动生成的 Schema 可能不符合预期。比如某个字段可能为空,你需要标注nullable,否则模型会强制传一个值,导致校验失败。这时候可以用 Jackson 的@JsonProperty(required = false)或@Nullable控制。

第二种情况:模型对参数理解有偏差,频繁传错格式。比如你在 Schema 里描述"date"字段是字符串,但没给格式,模型可能传 "2024-09-01"、也可能传 "2024/09/01"。解决办法是给字段加格式描述,甚至直接在方法描述里给一个完整示例。

public record OrderStatusRequest( @JsonPropertyDescription("订单号,格式为 SO 加 11 位数字,例如 SO20240901001") @JsonProperty(required = true) String orderNo, @JsonPropertyDescription("查询类型,可选值:LATEST、ALL_HISTORY,默认 LATEST") String queryType ) {}

把字段含义都交代清楚,模型传参的准确率会明显提升。这也是我从实践里总结的:函数调用失败,一半以上不是代码问题,而是"说明书"写得不清楚,导致模型理解错了参数。

2.4 多模型兼容:OpenAI、Ollama、Qwen 的差异

Spring AI 把 Function Call 做了抽象,理论上你换模型不需要改业务代码,但实际情况并没有那么省心。不同模型服务商对"工具调用"这个能力的底层实现并不一样,这里我梳理一下我实际测试下来的兼容性情况。

OpenAI 是对 tool calling 支持最成熟、最稳定的。gpt-4o、gpt-4o-mini系列在函数调用上表现都非常好,描述准确率高、参数解析极少出错。如果你的项目对稳定性要求高,预算又允许,无脑选 OpenAI 就行。

Ollama 这种本地化方案支持工具调用,但有两个前提:一是模型本身要支持 tool calling,比如llama3.1、qwen2.5、mistral的某些版本支持,但并不是所有跑在 Ollama 里的模型都支持;二是 Ollama 版本不能太老,早期版本的工具调用格式有 bug,我踩过好几次"模型答应调用却不返回正确结构"的坑,后来升级到 0.5 以上版本才稳定。

Qwen(通义千问)的开源版本qwen2.5系列也支持 Function Call,但如果用的是阿里云百炼平台,模型内部走的 API 规范和 OpenAI 兼容,Spring AI Alibaba 也做了适配。这里提醒一句:不要凭印象魔改配置,建议查一下官方文档的spring.ai.alibaba配置用户名对应关系。

还有一点要注意:虽然 Spring AI 抽象了不同模型,但函数调用的行为细节仍然有差异。比如 OpenAI 在模型非要调用一个不存在函数时会报错,而某些模型可能会"编造"一个函数调用来应付你。这就涉及到一个我在第 4 章会展开讲的兜底策略:永远不要假设模型 100% 按你的预期行动。

3. 实操:从零实现一个带函数调用的订单助手

3.1 项目搭建与依赖:基于 Spring Boot 3 快速起步

实操部分我用一个订单查询助手来做演示,这也是业务系统集成 AI 最典型的场景。技术栈是 Spring Boot 3.x + Spring AI 1.0 GA + OpenAI 兼容接口。如果你用的是本地大模型,后半部分我会单独说明怎么切换。

新建一个 Spring Boot 工程,pom.xml里核心依赖就两个:

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

配置application.yml:

spring: application: name: order-assistant ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL:https://api.openai.com} chat: options: model: gpt-4o-mini temperature: 0.3

注意引入 Spring AI 的 BOM 会省很多事:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

temperature这里我特意设成 0.3。函数调用场景下,我们希望模型尽量"听话"、按规则办事,而不是自由发挥,所以温度不宜太高。如果是闲聊场景可以调高,但接业务系统的场景,低温度是合理的起点。

3.2 实现第一个函数:查询订单状态

先造一个简单的订单服务,平时它可能是从数据库或远程接口查数据,这里为了演示直接用静态 Map 模拟。

@Service public class OrderService { private static final Map<String, String> ORDER_STATUS = Map.of( "SO20240901001", "已发货,当前位于上海分拨中心,预计明日送达", "SO20240901002", "已签收,签收人:前台小王", "SO20240901003", "待支付,订单尚未进入物流环节" ); public String queryStatus(String orderNo) { // 假设这里有数据库查询或远程调用 return ORDER_STATUS.getOrDefault(orderNo, "订单号不存在,请核对后重试"); } }

接着定义请求参数和函数 Bean:

// 请求参数 public record OrderStatusRequest( @JsonPropertyDescription("订单号,格式为 SO 加 11 位数字,例如 SO20240901001") String orderNo ) {} @Configuration public class OrderFunctions { private final OrderService orderService; public OrderFunctions(OrderService orderService) { this.orderService = orderService; } @Bean @Description("查询订单的物流状态和配送进度。当用户询问订单到哪了、发货没、什么时候送达时使用") public Function<OrderStatusRequest, String> orderStatusFunction() { return request -> orderService.queryStatus(request.orderNo()); } }

这里我故意把返回类型定义成String,简单一点,实际项目中你可以返回结构化对象,模型同样能处理。

3.3 注册函数并完成对话闭环

在 Controller 里构造 ChatClient,注册函数:

@RestController public class OrderChatController { private final ChatClient chatClient; public OrderChatController(ChatClient.Builder builder) { this.chatClient = builder .defaultSystem("你是一个订单助手,帮助用户查询订单相关信息。回答要简洁、准确。") .defaultFunctions("orderStatusFunction") .build(); } @PostMapping("/chat") public Map<String, String> chat(@RequestBody Map<String, String> request) { String userMessage = request.get("message"); String answer = chatClient.prompt() .user(userMessage) .call() .content(); return Map.of("answer", answer); } }

用 curl 测试一下:

curl -X POST http://localhost:8080/chat \ -H "Content-Type: application/json" \ -d '{"message": "帮我查一下 SO20240901001 这个订单到哪了"}'

正常情况下返回结果是这样的:

{ "answer": "您的订单 SO20240901001 已发货,目前位于上海分拨中心,预计明天可以送达。" }

整个调用链路,Spring AI 帮我们做了很多事情:它把函数描述打包成 OpenAI 的tools参数发送给模型,模型返回了tool_calls后,Spring AI 自动找到对应的 Bean,用模型传的参数执行方法,再把结果拼装成tool消息回传给模型,最后模型生成用户能读的最终回答。这些中间环节你都不用管,但它内部的"自动"是有前提的——你的函数描述、参数类能被正确序列化成 Schema。这也是前面我为什么反复强调"说明书"重要。

3.4 多函数并行与函数组合

实际业务里不可能只有一个函数。比如用户问"这个订单多少钱,现在打到几折了",你可能需要同时查订单金额和活动折扣。Spring AI 支持在一个会话里注册多个函数,模型也会根据情况决定是调用一个还是多个。

@Bean @Description("查询订单的当前折扣比例,参数为订单号") public Function<OrderStatusRequest, Double> orderDiscountFunction() { return request -> 0.85; // 模拟查到的折扣 }

注册时一起挂上:

.defaultFunctions("orderStatusFunction", "orderDiscountFunction")

这里有一个有意思的现象:如果用户同时问两件事,OpenAI 这类支持并行工具调用的模型,会返回多个tool_calls,Spring AI 会逐个执行所有函数,然后把所有结果一次回传给模型。我用 gpt-4o-mini 实测过,一次双函数调用场景下,端到端耗时大概比单函数多 200-400 毫秒,主要花在函数执行和数据组装上,还在可接受范围内。

不过要注意:不要给模型注册太多函数,尤其是函数描述既长又模糊的情况。OpenAI 的 tools 列表本身也要占 token,你注册 20 个函数,每个描述 100 token,光函数说明书就占 2000 token,不仅费钱,还会稀释模型对每个函数的注意力。经验值是单次会话 5-10 个相关函数足够,更复杂的场景应该做成"按需动态注册"。

3.5 流式输出:让聊天体验真正落地

如果上面那样一整段回答等它生成完才返回,用户体验是很差的。改成流式输出,用户能实时看到内容"打出来"。Spring AI 的 ChatClient 提供了stream()方法:

@PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> chatStream(@RequestBody Map<String, String> request) { String userMessage = request.get("message"); return chatClient.prompt() .user(userMessage) .stream() .content(); }

前端用 SSE 或者 fetch stream 接收即可。这里关键来了:传统上很多人以为流式就是Flux<String>一路返回 text,但带有 Function Call 的流式需要特殊处理。因为流程变成了:流式返回模型第一轮结果 → Spring AI 发现需要调用函数 → 暂停流式输出、执行函数 → 把函数结果加入对话 → 继续流式返回模型的第二轮回答。

Spring AI 对这个场景的处理是:你不需要改动业务代码,但底层要走工具调用会话(Tool Calling Session)。之前的旧 API 写法里,有人会遇到"函数结果等不到、流直接关闭"的问题,多半是没走对会话开关。Spring AI 1.0 的ChatClient已经默认开启了工具调用会话支持,所以你按我上面的写法就是对的。

还要注意超时控制。流式输出期间如果某个函数执行时间特别长(比如远程API调用耗时 5 秒),而模型端的超时设得比较短,就会出现"函数还在跑、连接已断开"的尴尬局面。建议给函数内部调用设置独立的超时时间,和模型连接的 socket 超时区分开。

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

4.1 函数一直被无视:问题基本出在描述和 System Prompt 上

我见过最多的现象是:函数明明注册了,模型却跟用户胡说八道,完全没有调用函数的迹象。排查时先做两步确认:第一,确认defaultFunctions或者functions里的 Bean 名称和实际注册的 @Bean 名称一致;第二,打开调试日志,看请求实际发给模型的内容里到底有没有包含 tools 参数。

如果确认工具的说明书确实发出去了,模型还是不用,那几乎可以断定是描述有问题。比如你把函数描述写成"订单状态查询函数",模型可能不太确定"快递到哪了"是不是应该走这个函数。改成触发场景导向的描述:"当用户询问订单物流、配送进度、包裹位置、发货状态时,调用此函数查询。参数为订单号,格式 SO 加 11 位数字。" 效果会立竿见影。

还有一个隐蔽问题是 System Prompt 里写了类似"如果不知道就告诉用户无法查询"的话,模型会优先遵从 System 指令,而不是调用函数。要让模型知道:查订单是你的分内事,遇到这类问题应该调用函数,而不是直接道歉。

4.2 参数格式冲突:数字被当成字符串

记录类字段类型和模型传参不匹配也是高频报错。比如字段你定义成Long amount,但模型传了一个字符串"100.5",Jackson 反序列化直接抛异常。排查方法很简单:开启 Spring AI 的日志,查看模型返回的 tool_calls 原始 JSON,然后逐个比对字段类型。

解决办法有两个方向:一是把参数类字段定义得宽松一些,全部用字符串接收再在函数里做类型转换,适合参数简单、不涉及嵌套对象的情况;二是在@JsonPropertyDescription里写清楚类型和格式,比如"数值类型,单位元,不要加引号"。实测下来,第二个方向对 OpenAI 这类模型效果更好,因为模型能理解"数值"这个概念。

4.3 本地模型(Ollama)对 Function Call 支持不稳定的处理

本地化部署是大趋势,但说实话 Ollama 的 Function Call 体验和 OpenAI 还有差距。我用 Ollama 跑qwen2.5:7b和llama3.1:8b都试过,前者相对稳定,后者偶尔会把函数调用格式搞错,返回的不是有效的 JSON 结构。

应对策略有三个:

  • 升级 Ollama 到最新版本,老版本的工具调用格式兼容性问题很严重。
  • 选择对工具调用支持更好的模型。就我实测,qwen2.5系列在开源模型里对中文场景的函数调用支持比较靠谱,而一些较老的模型(比如部分 7B llama 衍生版)确实容易出问题。
  • 如果实在不行,退回到"模型只做意图识别、代码自己拼装参数"的简化方案。也就是说不让模型自己生成参数 JSON,而是让模型输出一个意图标签,你的代码再去解析规则、查数据。这个方案虽然"笨",但在特定场景下比强行跑 Function Call 更稳定。

4.4 超时、并发与函数结果缓存的实践

函数调用不是银弹,它把"模型不可控"和"代码执行"两个环节串在一起,一旦函数执行阻塞,整个对话流程都会被拖住。我建议做三层防护:

第一层是给函数执行加超时。Spring AI 没有直接提供函数超时配置,但你可以在函数内部用CompletableFuture或者你的 HTTP 客户端超时配置来控制。比如用 RestClient 查外部接口时,连接超时设 2 秒、读取超时设 5 秒,超时快速返回兜底信息。

第二层是并发控制。一个 AI 接口背后可能被多个用户同时调用,如果每个请求都去查一次数据库,数据库压力会很大。尤其是同一个订单被反复查询的场景,加个本地 Caffeine 缓存,key 为函数名加参数 hash,过期时间 30 秒,能挡住大量重复请求。

第三层是结果兜底。函数执行失败时不要直接抛异常让整个对话崩掉,优雅返回一个"查询失败,请稍后重试"的结构化结果,模型会把这个结果组织成一句自然的回答传给用户。用户看起来是系统在正常对话,只是暂时没有数据。

我还做过一个实践:把函数的执行耗时记录到日志里,按函数名聚合统计。这样哪个函数是性能瓶颈、哪个函数被调用的频率最高,一目了然,后续优化就有了数据支撑,而不是靠猜。

4.5 常见问题速查表

问题现象可能原因解决办法
模型完全不调用函数函数描述不清晰 / System Prompt 互相矛盾重写描述,使用触发场景导向语言,让 System Prompt 明确要求使用函数
报错 Bean 找不到defaultFunctions 名称和 @Bean 名称不一致核对名称,统一规范命名
参数反序列化失败字段类型与模型传参不一致开启日志查看 tool_calls 原始 JSON,调整参数类字段类型或增加描述
流式输出丢函数结果未开启工具调用会话 / 函数超时过长确认 ChatClient 的流式写法,函数内设置更短的超时
函数执行报错函数内部异常未捕获捕获所有异常,返回兜底结构而非抛出
注册多个函数后模型表现变差函数过多、描述过长挤占上下文精简函数数量,按需动态注册,优化描述长度

这些坑几乎每个做 Spring AI Function Call 的团队都会遇到,区别只在于你提前踩还是后知后觉。对照速查表排查,能省下一大半非理性调试的时间。


最后分享一点我个人在实际项目里的体会:Function Call 写起来不难,难的是"函数设计"本身。哪些能力该暴露给模型、暴露到什么粒度、描述怎么写模型才不会误解,这些比写十行 Java 代码更花心思。我的做法是,每设计一个新函数,先在调试工具里用 5-10 个典型问题测试一遍,看模型是否总能做出正确的调用决策,不行就改描述而不是改代码。另外,这个能力天然适合往 Agent、NL2SQL 这些方向延伸——函数调用的本质就是让模型学会使用工具,而一旦模型学会了用工具,很多"看起来很聪明"的业务功能就都不再是空中楼阁了。

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

选型实战:透明加密软件怎么选,避开上线即踩坑的那些事

不少管理者会形成一个认知误区&#xff1a;部署透明加密&#xff0c;就等于解决全部文档泄密风险。实际落地场景中&#xff0c;透明加密解决的核心问题是&#xff1a;文档在受控终端正常编辑保存&#xff0c;文件脱离授权终端之后无法直接打开读取。但它管不住截图拍照、复制粘…

作者头像 李华
网站建设 2026/9/30 15:29:18

别再只会for循环:JavaScript数组遍历七法

做前端这几年&#xff0c;我面试过不少人&#xff0c;也看过不少新人写的代码。有个现象特别有意思&#xff1a;很多写了两三年 JavaScript 的人&#xff0c;遇到数组遍历要么只会用 for 循环死磕&#xff0c;要么无脑用 map 或者 forEach&#xff0c;压根没搞清楚每个方法返回…

作者头像 李华
网站建设 2026/9/30 15:27:37

极坐标系下暴力采样:原理、算法与雷达点云降采样实践

1. 这个项目到底在解决什么问题 先说结论&#xff1a;用极坐标系做暴力采样&#xff0c;本质上是把“在规则网格上均匀铺点”这件事换了一种坐标系来干&#xff0c;目的往往是为了让样本点在某些维度上分布更合理、更密集&#xff0c;或者更贴合数据的真实形状。 我在实际工作…

作者头像 李华
网站建设 2026/9/30 15:26:48

Claude Code 把自己改成了任务调度器,这次设计比功能更值得看

说实话&#xff0c;我第一次看到 Claude Code v2.1.139 的 changelog&#xff0c;以为只是个普通版本更新——新功能扫了一眼&#xff0c;Agent 视图和 /goal 命令&#xff0c;感觉不就是「任务管理器」和「批量执行」嘛&#xff0c;有什么大惊小怪的。 结果真正用了两天&…

作者头像 李华
网站建设 2026/9/30 15:26:02

Univer开源协同表格引擎:从集成到生产部署的实践指南

如果你最近在调研开源在线表格方案&#xff0c;大概率避不开 Univer 这个名字。它是一套基于 TypeScript 构建的在线协同文档引擎&#xff0c;覆盖电子表格、文档、幻灯片三类场景&#xff0c;既能像 Excel 那样处理复杂公式和多层样式&#xff0c;又能像 Google Sheets 那样支…

作者头像 李华
网站建设 2026/9/30 15:25:31

TensorFlow 2.x 安装、训练与部署实战指南:从数据管道到生产环境

TensorFlow 可能是你在 AI 领域听到最多的名字之一&#xff0c;也是我这些年被问到频率最高的一个词。很多人一上来就问“TensorFlow 和 PyTorch 到底学哪个”&#xff0c;或者“TensorFlow 是不是过气了”&#xff0c;其实这类问题本身就带着一个误解&#xff1a;把深度学习框…

作者头像 李华