一、前言

上一篇文章围绕 Spring AI 的 Chat Memory(聊天记忆)功能展开,先是通过代码演示了不使用 Chat Memory 时,大模型因无状态无法记住上下文(如用户姓名)的情况,随后展示了使用基于内存的 Chat Memory 后,大模型能关联历史对话的效果。同时,剖析了其实现原理 —— 通过拦截请求拼接历史上下文发送给大模型,并介绍了 ChatMemory 接口及默认实现,还探讨了将对话记录持久化到 MySQL 的自定义方案及相关问题解决,为构建连续对话能力提供了思路。接下来,我们将继续深入探索 Spring AI 的更多功能。

二、方法调用

1、简介

方法调用,Tool Calling(或者说是Function Calling),允许大模型去调用一些我们的方法或者接口。例如:

1、信息检索

此类工具可用于从外部来源检索信息,例如数据库、网络服务、文件系统或网络搜索引擎。其目的是扩充模型的知识储备,使模型能够回答原本无法回答的问题。因此,它们可应用于检索增强生成(RAG)场景。举例来说,工具可用于获取特定地点的当前天气、检索最新的新闻文章,或查询数据库中的特定记录。

2、执行操作

此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目的是自动化那些原本需要人工干预或专门编程才能完成的任务。例如,工具可用于为与聊天机器人交互的客户预订航班、在网页上填写表单,或在代码生成场景中基于自动化测试(TDD)实现 Java 类。

2、注意点

需要注意的是要想使用Tool Calling(Function Calling)需要大模型本身支持,如果模型不支持那无法实现。Spring 官网中为我们提供了一个表格,记录了那些大模型支持函数调用。可以参考一下链接

https://docs.spring.io/spring-ai/reference/api/chat/comparison.html

三、代码演示

遗憾的是DeepSeek暂时不支持Function Call,所以我们不得不换一个模型。这里我们使用阿里的Qwen3大模型来实验,并且采用本地ollama部署。

1、引入依赖

    <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-ollama</artifactId></dependency>

2、编写配置文件

说明一下:由于本项目中使用了多个模型Deepseek、Ollama(部署的是Qwen3),所以配置文件需要一定的调整。

server:port: 8080
spring:application:name: spring-ai-demodatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/learn?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTCpassword: zaige806username: rootai:chat:memory:repository:jdbc:initialize-schema: alwaysplatform: mysqlschema: classpath:schema/schema-@@platform@@.sqlclient:enabled: false #这个为false则不会自动装配ChatClientBuildermodel:chat:  #这个参数为空,ChatModel则不会自动装配      

3、配置Chat Client

package com.cmxy.springbootaidemo.config;import com.cmxy.springbootaidemo.advisor.SimpleLogAdvisor;
import com.cmxy.springbootaidemo.memory.CustomChatMemoryRepositoryDialect;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepositoryDialect;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;/*** @Author hardy(叶阳华)* @Description* @Date 2025/7/28 15:10* @Modified By: Copyright(c) cai-inc.com*/
@Configuration
public class ChatClientConfig {@Beanpublic ChatClient deepSeekChatClient(JdbcTemplate jdbcTemplate) {DeepSeekChatModel chatModel = DeepSeekChatModel.builder().deepSeekApi(DeepSeekApi.builder().apiKey("替换成自己的").build()).build();//使用自定义方言final JdbcChatMemoryRepositoryDialect dialect = new CustomChatMemoryRepositoryDialect();//配置JdbcChatMemoryRepositoryfinal JdbcChatMemoryRepository jdbcChatMemoryRepository = JdbcChatMemoryRepository.builder().jdbcTemplate(jdbcTemplate).dialect(dialect).build();// 创建消息窗口聊天记忆,限制最多保存10条消息 (其实这里的10条配置已经没有意义了,因为在dialect默认了50条)ChatMemory memory = MessageWindowChatMemory.builder().chatMemoryRepository(jdbcChatMemoryRepository).maxMessages(10).build();ChatClient.builder(chatModel).defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build(), new SimpleLogAdvisor()).build();return ChatClient.create(chatModel);}@Beanpublic ChatClient ollamaChatClient() {OllamaChatModel chatModel = OllamaChatModel.builder().defaultOptions(OllamaOptions.builder().model("qwen3:latest").build()).ollamaApi(OllamaApi.builder().baseUrl("http://w6584884.natappfree.cc").build()).build();return ChatClient.create(chatModel);}}

4、编写测试接口

package com.cmxy.springbootaidemo.tool;import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author hardy(叶阳华)* @Description* @Date 2025/7/27 14:02* @Modified By: Copyright(c) cai-inc.com*/
@RestController
@RequestMapping("/tool")
public class ToolController {private final ChatClient client;public ToolController(@Qualifier(value = "ollamaChatClient") ChatClient ollamaChatClient) {this.client = ollamaChatClient;}@GetMapping("/chat")public String chat(String msg) {return client.prompt(msg).call().content();}
}

5、测试接口

我们问大模型今天的日期

然而笔者写这篇文章的时候是2025-07-28,但是大模型告诉我今天是2023-10-15,很明显他在乱回答。这是因为大模型是大量语料训练出来的,他的知识只停留在了训练截止到哪天。那么如何让大模型能够知道训练语料之外的知识呢?

1、重新训练大模型(费时费力)

2、微调(这个笔者还没掌握,后续再说)

3、RAG(这个放到后续)

4、Function Calling:我们给大模型提供工具,让大模型能够调用外部的方法。

6、新增Function Call

package com.cmxy.springbootaidemo.tool;import java.time.LocalDateTime;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;/*** @Author hardy(叶阳华)* @Description* @Date 2025/7/28 15:46* @Modified By: Copyright(c) cai-inc.com*/
@Slf4j
public class DateTimeTools {@Tool(description = "获取用户所在时区当的日期",name = "getCurrentDateTime")String getCurrentDateTime() {log.info("方法被调用了");return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();}
}

7、修改Client配置

package com.cmxy.springbootaidemo.tool;import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author hardy(叶阳华)* @Description* @Date 2025/7/27 14:02* @Modified By: Copyright(c) cai-inc.com*/
@RestController
@RequestMapping("/tool")
public class ToolController {private final ChatClient client;public ToolController(@Qualifier(value = "ollamaChatClient") ChatClient ollamaChatClient) {this.client = ollamaChatClient;}@GetMapping("/chat")public String chat(String msg) {return client.prompt(msg).tools(new DateTimeTools()).call().content();}
}

主要修改点在 toolNames(“getCurrentDateTime”)

8、再次测试

可以看到通过FunctionCalling (或者ToolCalling)大模型可以获得更多的信息,下面我看下Function Call

简单的说,大模型是一个有决策能力的中心,会根据需要求调用注册到大模型内部的方法以便实现特定的功能。

四、实现Function Call的多种方式

1、基于Tool注解

上面的案例就是基于Tool注解,这里补充一点,如果需要参数,则可以通过@ToolParam注解来说明参数的含义,帮助大模型更好的理解调用的方法。例如:

@Tool(description = "Set a user alarm for the given time")void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);System.out.println("Alarm set for " + alarmTime);}

2、通过ToolCallBack

    @Beanpublic ChatClient ollamaChatClient() {//定义ToolCallBackToolCallback[] toolCallbacks = ToolCallbacks.from(new DateTimeTools());OllamaChatModel chatModel = OllamaChatModel.builder().defaultOptions(OllamaOptions.builder().model("qwen3:latest").toolCallbacks(toolCallbacks).build()).ollamaApi(OllamaApi.builder().baseUrl("http://w6584884.natappfree.cc").build()).build();return ChatClient.create(chatModel);}

3、通过函数接口

1、编写方法(错误写法)

package com.cmxy.springbootaidemo.tool;import java.util.function.Function;/** * 这个是错误写法!!!!!!不能使用基本数据类型* @Author hardy(叶阳华)* @Description* @Date 2025/7/28 16:51* @Modified By: Copyright(c) cai-inc.com*/
public class WeatherService implements Function<String,String> {@Overridepublic String apply(final String city) {return switch (city) {case "杭州" -> "晴天";case "上海" -> "阴转多云";case "背景" -> "暴雨";default -> "不知道";};}
}

正确写法:

package com.cmxy.springbootaidemo.tool;import com.cmxy.springbootaidemo.tool.WeatherService.WeatherRequest;
import com.cmxy.springbootaidemo.tool.WeatherService.WeatherResponse;
import java.util.function.Function;/*** @Author hardy(叶阳华)* @Description* @Date 2025/7/28 16:51* @Modified By: Copyright(c) cai-inc.com*/
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {public WeatherResponse apply(WeatherRequest request) {return new WeatherResponse(30.0, Unit.C);}public enum Unit { C, F }public record WeatherRequest(String location, Unit unit) {}public record WeatherResponse(double temp, Unit unit) {}}

2、配置到客户端

 @Beanpublic ChatClient ollamaChatClient() {//天气工具ToolCallback wetherToolCallback = FunctionToolCallback.builder("currentWeather", new WeatherService()).description("获取指定位置的天气").inputType(WeatherRequest.class).build();//日期工具:这里分开定义,因为是两种类型,一个是FunctionToolCallback一个是MethodToolCallbackToolCallback[] dataTimeToolCallbacks = ToolCallbacks.from(new DateTimeTools());OllamaChatModel chatModel = OllamaChatModel.builder().defaultOptions(OllamaOptions.builder().model("qwen3:latest").toolCallbacks(wetherToolCallback).toolCallbacks(dataTimeToolCallbacks).build()).ollamaApi(OllamaApi.builder().baseUrl("http://w6584884.natappfree.cc").build()).build();ChatClient.builder(chatModel).defaultAdvisors(new SimpleLogAdvisor()).build();return ChatClient.create(chatModel);}

3、测试一下

注意点:

以下类型目前不支持作为用作工具的函数的输入或输出类型:

  • 基本类型
  • Optional 类型
  • 集合类型(例如 List、Map、Array、Set)
  • 异步类型(例如 CompletableFuture、Future)
  • 响应式类型(例如 Flow、Mono、Flux)。

笔者在一开始就返回String,导致返回的时候提示JSON返序列化失败

五、小结

本文围绕 Spring AI 的方法调用(Tool Calling/Function Calling)功能展开,先是介绍了其能让大模型调用外部方法实现信息检索、执行操作等作用,强调了需大模型本身支持该功能,并给出了相关模型支持情况参考。

通过代码演示,展示了借助 Qwen3 大模型(本地 ollama 部署)实现功能调用的过程,还说明了实现 Function Call 的多种方式及工具函数输入输出类型的限制。

总的来说,Function Calling 为大模型连接外部能力提供了有效途径,合理运用能极大扩展其应用场景,后续可进一步探索更多实践技巧。希望对你有所帮助!

六、未完待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90833.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90833.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/90833.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ESP32S3 防猫逃脱监测系统

在办公室里&#xff0c;两只可爱的猫咪给大家带来了不少欢乐&#xff0c;但其中一只总爱趁人不注意溜出房间&#xff0c;有时下班后还会被邻居告知它被锁在了外面。为了解决这个问题&#xff0c;我开发了一个基于 SeeedStudio XIAO ESP32S3 Sense 的猫咪逃脱监测预警系统&#…

Python|OpenCV-实现快速处理图像的方法(23)

前言 本文是该专栏的第25篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在视觉算法落地流程中,数据预处理往往占用 60 % 以上的工程时间。以某沿海城市智慧旅游项目为例,我们从无人机录制的 4K 海滩视频中抽帧得到 10 000 张 PNG 原图,分辨率 38402160,单张体…

Redis四种GetShell方式完整教程

Redis作为高性能内存数据库&#xff0c;若未正确配置认证和访问控制&#xff0c;可能被攻击者利用实现远程代码执行&#xff08;GetShell&#xff09;。本文详细讲解四种常见的Redis GetShell方式&#xff0c;涵盖原理、操作步骤及防御建议。方式一&#xff1a;直接写入Shell脚…

clock_nanosleep系统调用及示例

41. clock_nanosleep - 高精度睡眠 函数介绍 clock_nanosleep系统调用提供纳秒级精度的睡眠功能&#xff0c;支持绝对时间和相对时间两种模式&#xff0c;比传统的nanosleep更加灵活。 函数原型 #include <time.h>int clock_nanosleep(clockid_t clock_id, int flags,con…

用了Flutter包体积增大就弃用Flutter吗?包体积与开发效率,这两者之间如何权衡?

是否因包体积增大而弃用 Flutter&#xff0c;本质上是 “短期成本&#xff08;包体积&#xff09;” 与 “长期价值&#xff08;跨平台效率、体验一致性等&#xff09;” 的权衡 。这一决策没有绝对答案&#xff0c;需结合项目阶段、用户群体、业务需求等具体场景分析。以下从核…

80道面试经典题目

1.OSI参考模型七层网络协议? 物理层:定义计算机、网络设备、以及直接连接的介质、接口类型的标准,建立比特流的传输,用来组件物理网络的连接。 数据链路层:建立逻辑连接、进行硬件地址寻址,差错校验、差错恢复等功能。 网络层:进行逻辑地址寻址,实现不同网络之间的通…

本周大模型新动向:KV缓存压缩、低成本高性能推理框架、多智能体协作

点击蓝字关注我们AI TIME欢迎每一位AI爱好者的加入&#xff01;01Compress Any Segment Anything Model (SAM)受SAM在零样本分割任务上卓越表现的驱动&#xff0c;其各类变体已被广泛应用于医疗、智能制造等场景。然而&#xff0c;SAM系列模型体量巨大&#xff0c;严重限制了在…

利用frp实现内网穿透功能(服务器)Linux、(内网)Windows

适用于&#xff1a; 本地电脑&#xff08;windows&#xff09;或者Linux(本篇未介绍&#xff09; 工具&#xff1a;FRP&#xff08;fast reverse proxy&#xff09; 系统&#xff1a;Linux、Windows 架构&#xff1a;x86、amd Frp版本&#xff1a;frp_0.62.1_windows_amd64准备…

结合二八定律安排整块时间

你是不是常常感觉一天到晚忙忙碌碌&#xff0c;却总觉得没干成几件“要紧事”&#xff1f;时间仿佛从指缝间溜走&#xff0c;成就感却迟迟不来&#xff1f;其实&#xff0c;高效能人士的秘诀往往藏在最简单的原则里。今天&#xff0c;我们就来聊聊如何巧妙运用“二八定律”&…

波形发生器AWG硬件设计方案

目录 简介 设计需求 设计方案 核心原理图展示 简介 波形发生器是一种数据信号发生器&#xff0c;在调试硬件时&#xff0c;常常需要加入一些信号&#xff0c;以观察电路工作是否正常。用一般的信号发生器&#xff0c;不但笨重&#xff0c;而且只发一些简单的波形&#xff…

11.Dockerfile简介

1.是什么&#xff1f; dockerfile是用来构建镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 构建三步骤 编写dockerfile文件docker build命令构建镜像docker run依镜像运行的容器实列 2.dockerfile构建过程解析 1)dockerfile内容的基础知识 …

C# 接口(interface 定义接口的关键字)

目录 使用接口案例 接口继承 练习 定义一个接口&#xff0c;在语法中与定义一个抽象类是没有区别的&#xff0c;但是不允许提供接口中任意成员的实现方式&#xff0c;一般接口只会包含方法 、索引器和事件的声明&#xff0c; 不允许声明成员的修饰符&#xff0c; public都不…

5190 - 提高:DFS序和欧拉序:树上操作(区域修改1)

题目传送门 时间限制 : 2 秒 内存限制 : 256 MB 有一棵点数为 N 的树&#xff0c;以点 1 为根&#xff0c;且树点有边权。然后有 M 个 操作&#xff0c;分为三种&#xff1a; 操作 1 &#xff1a;把某个节点 x 的点权增加 a 。 操作 2 &#xff1a;把某个节点 x 为根的子树中…

【Oracle】数据泵

ORACLE数据库 数据泵 核心参数全解析 ORACLE expdp 命令使用详解 1.ATTACH[schema_name.]job_name Schema_name 用于指定方案名,job_name 用于指定导出作业名.注意,如果使用 ATTACH 选项,在命令行除了连接字符串和 ATTACH 选项外,不能指定任何其他选项,示例如下: expdp hr/hr A…

机器学习的算法有哪些?

&#x1f31f; 欢迎来到AI奇妙世界&#xff01; &#x1f31f; 亲爱的开发者朋友们&#xff0c;大家好&#xff01;&#x1f44b; 我是人工智能领域的探索者与分享者&#xff0c;很高兴在CSDN与你们相遇&#xff01;&#x1f389; 在这里&#xff0c;我将持续输出AI前沿技术、实…

【计算机网络】OSI七层模型

OSI七层模型为什么需要OSI七层模型&#xff1f;OSI七层模型具体是什么&#xff1f;Layer7&#xff1a;应用层&#xff08;Application Layer&#xff09;Layer6&#xff1a;表示层&#xff08;Presentation Layer&#xff09;Layer5&#xff1a;会话层&#xff08;Session Laye…

RS485转Profinet网关配置指南:高效启动JRT激光测距传感器测量模式

RS485转Profinet网关配置指南&#xff1a;高效启动JRT激光测距传感器测量模式RS485转Profinet网关&#xff1a;让JRT激光测距传感器高效开启测量模式在工业自动化场景中&#xff0c;设备间的高效通信是实现精准控制的关键。RS485转Profinet网关作为连接传统RS485设备与现代Prof…

「日拱一码」040 机器学习-不同模型可解释方法

目录 K最近邻(KNN) - 基于距离的模型 决策边界可视化 查看特定样本的最近邻 ​随机森林(RF) - 树模型 feature_importances_ SHAP值分析 可视化单棵树 多层感知器(MLP) - 神经网络 部分依赖图 LIME解释器 权重可视化 支持向量回归(SVR) - 核方法 支持向量可视化 部…

编程与数学 03-002 计算机网络 09_传输层功能

编程与数学 03-002 计算机网络 09_传输层功能一、传输层的作用&#xff08;一&#xff09;进程间通信&#xff08;二&#xff09;提供可靠传输&#xff08;三&#xff09;复用与分用二、TCP协议&#xff08;一&#xff09;TCP的连接建立与释放&#xff08;二&#xff09;TCP的可…

14. Web服务器-Nginx-工作原理

文章目录前言一、简介二、工作原理1. 多进程架构2. 事件驱动模型3. 模块化设计三、工作流程1. 启动阶段2. 等待连接3. 请求处理阶段4. 响应构造与输出5. 连接关闭前言 Nginx‌ Nginx&#xff08;发音为“Engine-X”&#xff09;是一款高性能的开源Web服务器软件&#xff0c;同…