Spring AI 再更新:如何借助全局參數實現智能數據庫操作與個性化待辦管理
引言
好的,今天我們繼續聊一下Spring AI的相關內容。在10月的時候,我使用Spring AI搭建了一個簡易版的個人助理系統,整體來說效果還是非常不錯的。通過這次嘗試,我對業務系統與AI結合的探索有了更為明確的理解和實踐。雖然目前功能上還相對簡單,整體系統也缺乏較多可操作的交互方式,特別是在數據庫操作方面,功能較為基礎,目前主要實現了一個簡單的查詢功能。
但就在10月末,Spring AI迎來了一個重要的更新,更新后不僅增強了函數調用的能力,還引入了全局參數的概念。這兩個新特性為系統的擴展性和可玩性帶來了極大的提升,開啟了更多可能性。
圖片
那么,今天我們就利用這個全局參數的特性,來實現一個數據庫插件。具體來說,我們將實現一個完整的增刪改查(CRUD)操作。對于目前的智能體系統來說,數據庫操作已經是一個至關重要的功能,尤其是在業務系統中,智能體能夠與數據庫進行交互,不僅提升了系統的靈活性和智能化程度,也大大增強了業務處理的效率。因此,我們今天的目標就是通過Spring AI的強大功能,實現一套基礎的數據庫操作框架,完成增、刪、改、查四個功能模塊。
需要特別注意的是,這里我們僅僅是通過一個簡單的使用案例來進行分析和講解,當然,這并不代表只能局限于此,實際上對于大部分業務場景來說,這樣的數據庫操作已經足夠滿足需求,并且可以根據具體的業務需求進一步擴展和優化功能。
如果有小伙伴還不太清楚如何使用Spring AI搭建自己的智能體系統,或者對于Spring AI的基本功能還不太了解,歡迎查看我們之前分享的相關文章,了解更多相關內容:https://www.cnblogs.com/guoxiaoyu/p/18453559
個人助理大優化
首先,讓我們來看一下我們計劃實現的個人助理功能。目前,我們已經實現了旅游攻略查詢和天氣查詢功能。今天,我們將在此基礎上新增一個“個人待辦”功能。由于數據庫模塊較為龐大且復雜,針對這個部分我們將單獨進行詳細講解。以下是該功能的大致實現效果及相關流程示意圖:
圖片
效果演示
首先,讓我們來看看經過半天調整后的效果演示。經過一段時間的優化和調試,最終呈現的效果基本符合我的預期。
這里只演示了下待辦的增刪改查,并沒有演示天氣查詢和旅游攻略,可以看上一章節的演示。
圖片
開始優化
提示詞
當然,我們的優化工作從入口部分開始,首先,提示詞的設計是不可或缺的。回顧上一章節,由于當時功能較少,我們并沒有對提示詞做過多的修飾,因此整體的交互和模型的響應相對簡單。然而,隨著本次新增功能的增多,模型的回答可能會變得較為雜亂無序。
因此,為了確保模型的輸出能夠更精準、有序,我們在本次優化中提前準備并生成了詳細的提示詞。
String conversation_id = "123";
OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
.withModel("hunyuan-pro").withTemperature(0.5)
.build();
String systemPrompt = """
- Role: 個人助理小助手
- Background: 用戶需要一個多功能的AI助手,可以提供實時的天氣信息、詳盡的旅游攻略以及幫助記錄待辦事項。
- Profile: 你是一個專業的旅行天氣小助手,具備強大的信息檢索能力和數據處理能力,能夠為用戶提供精確的天氣信息、詳盡的旅游攻略,并幫助管理日常待辦事項。
- Skills: 你擁有強大的網絡搜索能力、數據處理能力以及用戶交互能力,能夠快速準確地為用戶提供所需信息。
- Goals: 提供準確的天氣信息,制定包含航班、酒店、火車信息的詳盡旅游攻略,并幫助用戶記錄和管理待辦事項。
- Constrains: 提供的信息必須準確無誤,旅游攻略應詳盡實用,待辦事項管理應簡潔高效。
- OutputFormat: 友好的對話式回復,包含必要的詳細信息和格式化的數據。
- Workflow:
1. 接收用戶的天氣查詢請求,并提供準確的天氣信息。
2. 根據用戶的旅游目的地,搜索并提供包括航班、酒店、火車在內的旅游攻略。
3. 接收用戶的待辦事項,并提供簡潔的記錄和提醒服務。
""";
ChatMemory chatMemory1 = messageChatMemoryAdvisor.getChatMemory();
String content = this.myChatClientWithSystem
.prompt()
.system(systemPrompt)
.user(userInput)
.options(openAiChatOptions)
.advisors(messageChatMemoryAdvisor,myLoggerAdvisor,promptChatKnowledageAdvisor,promptChatDateAdvisor)
.advisors(advisor -> advisor.param("chat_memory_conversation_id", conversation_id)
.param("chat_memory_response_size", 100))
.functions("CurrentWeather","TravelPlanning","toDoListFunctionWithContext")
.toolContext(Map.of("sessionId", conversation_id, "userMemory", chatMemory1,"client",chatClient))
.call()
.content();
log.info("content: {}", content);
ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();
return chatDataPO;
好的,剛才我們新增了一些參數,我現在來詳細解釋一下每個參數的作用和使用場景:
- advisor.param:這是用于單獨修改我們默認增強器(增強型顧問)的參數。它與上面提到的 Advisor 類相關聯,允許用戶在不修改核心代碼的情況下,自定義和調整增強器的行為和配置。
- toolContent:這個參數是我們在新增函數回調功能時引入的全局參數,主要用于處理回調時的各種工具內容。在函數調用中,toolContent 可以傳遞不同的工具數據,確保回調過程的正確執行。
- sessionId:這個參數用于標識每個獨立的會話,它幫助我們控制每個用戶的會話狀態。通過給每個會話分配一個唯一的 sessionId,我們可以確保每個函數調用是針對特定用戶的,而非共享的全局數據。例如,我們的待辦事項是個人化的,只有對應用戶可以看到和操作自己的待辦列表。這里為了演示的方便,我們將 sessionId 設置為固定值,并未接入登錄接口,實際應用中應根據用戶身份動態生成。
- userMemory:此參數用于傳遞用戶的歷史上下文,使得回調函數能夠使用到之前的對話或操作記錄。例如,在待辦事項管理中,我們可能需要根據歷史數據判斷某個任務是否已經完成。
- chatClient:該參數將待辦函數與一個大模型連接,借助大模型生成SQL查詢或其他復雜操作。chatClient 負責與大模型進行交互,生成所需的SQL,而外層的思考模型則專注于調用接口并處理業務邏輯。
好的,再次提醒一下,如果有些小伙伴之前沒有接觸過智能體的相關內容,建議你們先去瀏覽一下我之前的第一篇文章,了解一下基礎知識,補充相關的背景信息,這樣對接下來的內容會更加容易理解。
那么,接下來我們繼續探討與待辦事項相關的內容。
數據表
個人待辦事項的目的非常明確,主要是針對待辦事項表進行增、刪、改、查等基本操作。為了保證系統的高效性與簡潔性,我們在設計數據庫時,所需的數據字段也非常簡潔直觀。這是我們的建表語句:
create table todo_info(
id int(11) auto_increment primary key,
todo_info varchar(1000) not null,
todo_Date date not null,
done boolean not null default false
)
在生成完這個建表語句之后,一定要妥善保存,以便后續給大模型提供參考。
函數回調
首先,我們需要明確待辦函數的回調必須能夠支持四種基本操作:增刪改查,。此外,回調函數還需要具備生成SQL語句的能力,并能執行這些SQL語句,以便與數據庫進行交互。只有在這些操作順利完成之后,我們才能將最終的結果數據返回給外層的思考模型,以供進一步的處理和分析。
基于這些要求,我們現在可以開始具體操作,逐步實現所需功能。
圖片
好的,我們一步一步來。
待辦函數
根據上述信息,我們需要設計一個函數,該函數需要包含一個入參和一個回參。入參的主要作用是傳遞一個標識符,用于唯一標識某一操作,而回參則是一個字符串類型的返回值,用于向調用方反饋相應的結果或狀態信息。
public class ToDoListInfoService implements BiFunction<ToDoListInfoService.ToDoRequest, ToolContext, ToDoListInfoService.ToDoResponse> {
private JdbcTemplate jdbcTemplate;
public ToDoListInfoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@JsonClassDescription("crud:c 代表增加;r:代表查詢,u:代表更新,d:代表刪除")
public record ToDoRequest(String crud) {}
public record ToDoResponse(String message) {}
@Override
public ToDoResponse apply(ToDoListInfoService.ToDoRequest request, ToolContext toolContext) {}
}
可以觀察到,在這里我們使用的是 BiFunction 接口,而不是之前使用的 Function 接口,因為我們需要使用ToolContext。
?
需要特別注意的是,盡管在這里我們使用了 JsonClassDescription,但它的主要目的是為了提高代碼的可讀性和可維護性,方便開發人員理解和查看結構。實際上,大模型并不會依賴于 JsonClassDescription 來判斷或解析傳遞的具體參數。
圖片
Bean裝配
由于我們在這里使用了 JdbcTemplate 進行數據庫操作,這就需要在項目中引入相應的 Maven 依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
我這里仍然選擇使用 Bean 裝配的方式,主要是因為它能夠提供更高的靈活性與可維護性。此外,維護好入參的定義和配置,也能夠在后續的開發中為大模型提供有效的參考和支持。
@Bean
public FunctionCallback toDoListFunctionWithContext(JdbcTemplate jdbcTemplate) {
return FunctionCallbackWrapper.builder(new ToDoListInfoService(jdbcTemplate))
.withName("toDoListFunctionWithContext") // (1) function name
.withDescription("添加待辦,crud:c 代表增加;r:代表查詢,u:代表更新,d:代表刪除") // (2) function description
.build();
}
接下來,我們將著手實現函數內部的具體方法。
上下文信息
默認提供的 MessageChatMemoryAdvisor 類并不支持直接獲取歷史聊天記錄。因此,我們需要自定義一個類來實現這一功能。為了方便使用,下面的代碼將集成該功能并暴露一個接口供調用。
@Slf4j
public class MyMessageChatMemoryAdvisor extends MessageChatMemoryAdvisor {
private ChatMemory chatMemory;
public MyMessageChatMemoryAdvisor(ChatMemory chatMemory) {
super(chatMemory);
this.chatMemory = chatMemory;
}
public MyMessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize);
this.chatMemory = chatMemory;
}
public MyMessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize, int order) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize, order);
this.chatMemory = chatMemory;
}
public ChatMemory getChatMemory() {
return chatMemory;
}
}
為了能夠順利訪問歷史聊天記錄,我們專門編寫了一個方法,該方法會將歷史上下文對象返回。通過這種方式,我們可以在后續的操作中方便地獲取到完整的聊天信息,從而實現對歷史對話內容的正常訪問和處理。
數據庫操作
接下來,我們將進行數據庫操作,但在此過程中,必須依賴大模型的幫助來生成SQL語句。原因在于,外層的大模型具備強大的能力,可以準確地分析并理解需求,從而判斷出具體的操作類型是增、刪、改還是查。接下來,我們將詳細介紹如何實現這一過程。
public ToDoResponse apply(ToDoListInfoService.ToDoRequest request, ToolContext toolContext) {
String tableinfo = """
- Role: SQL語句生成專家
- Background: 用戶需要根據特定的表結構和參數信息生成精準的MySQL查詢或修改語句,以實現數據庫操作的自動化和效率化。
- Profile: 你是一位經驗豐富的數據庫管理員和SQL專家,精通MySQL數據庫的各種查詢和修改語句,能夠根據用戶提供的表結構和參數信息快速生成正確的SQL語句。
- Skills: 你具備深厚的數據庫理論知識和豐富的實踐經驗,能夠理解復雜的表結構,準確把握用戶需求,并據此生成高效、準確的SQL語句。
- Goals: 根據用戶提供的表結構和參數信息,生成可以直接執行的MySQL查詢或修改語句,確保語句的正確性和執行的成功率。
- Constrains: 生成的SQL語句必須符合MySQL的語法規則,能夠直接在MySQL數據庫中執行,且不包含任何額外的信息或提示。
- OutputFormat: 純SQL文本語句,格式規范,無多余信息。禁止使用markdown格式。
- Workflow:
1. 分析用戶提供的表結構信息和參數信息。
2. 根據分析結果,確定需要執行的數據庫操作類型(查詢、插入、更新或刪除)。
3. 結合操作類型和用戶提供的信息,生成符合MySQL語法的SQL語句。
-example:
q.幫我創建一個待辦:明天9點提醒我讀書。今天日期是2024-11-07 20:20:11
a:INSERT INTO todo_info (todo_info, todo_date) VALUES ('明天8點讀書', '2024-11-08 08:00:00');
- tableinfo:
create table todo_info(
id int(11) auto_increment primary key,
todo_info varchar(1000) not null,
todo_Date date not null,
done boolean not null default false
)
""";
String crud = request.crud;
ChatMemory chatMemory = (ChatMemory)toolContext.getContext().get("userMemory");
String conversation_id = (String)toolContext.getContext().get("sessionId");
ChatClient client = (ChatClient)toolContext.getContext().get("client");
List<Message> messages = chatMemory.get(conversation_id, 1);
var userqa = messages.get(0).getContent();
String jsonString = "執行成功";
String content = client.prompt()
.system(tableinfo)
.user("請根據當前問題生成相應SQL文本即可,禁止生成SQL以外的內容:"+userqa+",今天日期是:"+ DateUtil.now())
.call().content();
try {
if (crud.equals("r")) {
jsonString = "查詢到待辦內容如下:" + JSONObject.toJSONString(jdbcTemplate.queryForList(content));
} else {
jdbcTemplate.execute(content);
}
}catch (Exception e){
log.info("ToDoListInfoService:{}", e.getMessage());
jsonString = "執行失敗了";
}
log.info("ToDoListInfoService:{}", content);
return new ToDoResponse(jsonString);
這段代碼的實現完全依賴于通過全局參數傳遞過來的信息,以便更好地處理歷史上下文的問題。傳統的回調函數方法在處理多輪對話的歷史上下文時存在很大的局限性,無法有效地追蹤會話中的上下文,因此難以解決這類問題。在這種情況下,我們通過全局參數傳遞的方式,能夠跨越多次交互,確保在每個步驟中都能訪問到最新的上下文信息。
讓我們簡單解釋一下這段代碼的流程:
- 獲取當前會話中的用戶提問:我們從當前會話中獲取最近一次用戶提出的問題,確保我們不會誤取到其他會話的上下文。
- 將問題提交給大模型生成SQL:我們將用戶的提問傳遞給大模型,利用其能力幫助我們生成適當的SQL查詢語句。
- 判斷是否為查詢請求:我們檢查生成的SQL語句是否屬于查詢操作。如果是查詢,則執行查詢并將查詢結果返回給用戶。
- 其他操作的處理:如果不是查詢請求,則說明用戶發出的指令可能是更新或執行類的操作,在這種情況下,我們返回一個"執行成功"的響應。
可以看到,提示詞中的 tableinfo 是我硬編碼寫死的。實際上,我們完全可以將其設計成一個可傳入的參數,這樣不僅提升了插件的靈活性和可復用性,而且使得該插件不再僅僅局限于待辦事項的使用場景,而能夠作為一個通用的數據庫操作插件,適應不同的需求和應用場景。
為了能夠在演示過程中展示效果,目前我將某些部分做了臨時的硬編碼處理。這只是為了給大家提供一個初步的思路和參考框架,后續我會逐步完善這些功能。
接下來,讓我們一起來看看調試過程中得到的效果。以下是我在調試時的截圖:
圖片
接下來,我們將檢查數據庫是否已經成功存儲并正常更新了數據。
圖片
接下來,我將展示查詢的實際效果,同時生成的 SQL 語句也相當優秀,能夠高效地滿足查詢需求。
圖片
可以看到,此處已成功將數據或結果正常返回給前端,系統運行狀態良好。
圖片
總結
在本文中,我們深入探討了如何利用 Spring AI 的新功能,特別是全局參數和增強函數調用能力,來構建一個智能化的個人助理系統。通過這個系統,我們實現了基礎的增、刪、改、查(CRUD)功能,特別聚焦在數據庫交互與待辦事項管理上。我們展示了如何將 Spring AI 集成到實際業務流程中,通過模型生成 SQL 查詢語句,提升數據庫操作的自動化程度和靈活性。
首先,我們介紹了 Spring AI 在功能更新后如何簡化和擴展業務邏輯處理,特別是在處理多輪對話、用戶歷史數據以及復雜數據庫操作時的優勢。通過全局參數,系統能夠更精準地捕捉用戶需求,并在數據庫層面執行相關操作,真正實現了智能化的交互和自動化的業務流程。
我們還設計了一個待辦事項管理功能,其中通過精心設計的提示詞和模型優化,使得待辦功能的增刪改查操作更加高效與準確。
總結來說,這次基于 Spring AI 的系統優化,不僅為我們提供了一個強大的智能助手框架,也為實際業務中的智能化系統提供了可借鑒的方案。未來,我們可以基于此進一步擴展功能,打造更加智能化和個性化的業務解決方案。