Java程序員學從0學AI(七)

一、前言

上一篇文章圍繞 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;同…