偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Spring AI 玩轉(zhuǎn)多輪對話

人工智能
你是否遇到過這樣的 AI?上一秒剛告訴它你的名字,下一秒就問你是誰。這種“金魚記憶”的 AI 簡直讓人抓狂!在智能客服、虛擬助手等場景,如果 AI 無法記住上下文,用戶體驗將大打折扣。別擔(dān)心,今天 NEO 就帶你用 Spring AI 框架,徹底解決這個難題,輕松為你的 AI 應(yīng)用植入“記憶芯片”!

AI "失憶"怎么辦?本文帶你用 Spring AI 一招搞定多輪對話,讓你的 AI 應(yīng)用擁有超強記憶!從 ChatClient、Advisors 到實戰(zhàn)編碼,三步打造一個能記住上下文的智能歷史專家。

大家好,我是程序員NEO。

你是否遇到過這樣的 AI?上一秒剛告訴它你的名字,下一秒就問你是誰。這種“金魚記憶”的 AI 簡直讓人抓狂!在智能客服、虛擬助手等場景,如果 AI 無法記住上下文,用戶體驗將大打折扣。

別擔(dān)心,今天 NEO 就帶你用 Spring AI 框架,徹底解決這個難題,輕松為你的 AI 應(yīng)用植入“記憶芯片”!

為了方便演示,我們將一起創(chuàng)建一個“歷史知識專家”AI。它不僅能對答如流,還能記住我們之前的對話,實現(xiàn)真正流暢的智能交流。

準備好了嗎?讓我們開始吧!

更強大的 ChatClient

要讓 AI 擁有“記憶力”,首先得掌握與它高效溝通的工具。Spring AI 提供了 ChatClient API,這是我們與大模型交互的瑞士軍刀。

很多同學(xué)可能習(xí)慣了直接注入 ChatModel,但 ChatClient 提供了功能更豐富、更靈活的鏈式調(diào)用(Fluent API),是官方更推薦的方式。

看看對比,高下立判:

// 基礎(chǔ)用法(ChatModel)
ChatResponse response = chatModel.call(new Prompt("你好"));

// 高級用法(ChatClient)
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultSystem("你是歷史顧問")
    .build();
    
String response = chatClient.prompt().user("你好").call().content();

ChatClient 的構(gòu)建方式也很靈活,可以通過構(gòu)造器注入或使用建造者模式:

// 方式1:使用構(gòu)造器注入
@Service
public class ChatService {
    private final ChatClient chatClient;
    
    public ChatService(ChatClient.Builder builder) {
        this.chatClient = builder
            .defaultSystem("你是歷史顧問")
            .build();
    }
}

// 方式2:使用建造者模式
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultSystem("你是歷史顧問")
    .build();

它還支持多種響應(yīng)格式,無論是包含 Token 信息的完整響應(yīng)、自動映射的 Java 對象,還是實現(xiàn)打字機效果的流式輸出,都能輕松搞定。

// ChatClient支持多種響應(yīng)格式
// 1. 返回 ChatResponse 對象(包含元數(shù)據(jù)如 token 使用量)
ChatResponse chatResponse = chatClient.prompt()
    .user("Tell me a joke")
    .call()
    .chatResponse();

// 2. 返回實體對象(自動將 AI 輸出映射為 Java 對象)
// 2.1 返回單個實體
record ActorFilms(String actor, List<String> movies) {}
ActorFilms actorFilms = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorFilms.class);

// 2.2 返回泛型集合
List<ActorFilms> multipleActors = chatClient.prompt()
    .user("Generate filmography for Tom Hanks and Bill Murray.")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorFilms>>() {});

// 3. 流式返回(適用于打字機效果)
Flux<String> streamResponse = chatClient.prompt()
    .user("Tell me a story")
    .stream()
    .content();

// 也可以流式返回ChatResponse
Flux<ChatResponse> streamWithMetadata = chatClient.prompt()
    .user("Tell me a story")
    .stream()
    .chatResponse();

更棒的是,你可以為 ChatClient 設(shè)置默認的“人設(shè)”(系統(tǒng)提示詞),甚至在對話中動態(tài)替換模板變量,讓 AI 的角色扮演更加生動。

// 定義默認系統(tǒng)提示詞
ChatClient chatClient = ChatClient.builder(chatModel)
        .defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}")
        .build();

// 對話時動態(tài)更改系統(tǒng)提示詞的變量
chatClient.prompt()
        .system(sp -> sp.param("voice", voice))
        .user(message)
        .call()
        .content());

Advisors 攔截器

如果說 ChatClient 是 AI 的軀體,那 Advisors(顧問)就是給它加持的各種“外掛”和“Buff”。

你可以把 Advisors 理解為一系列可插拔的攔截器。在請求發(fā)給 AI 前或收到 AI 響應(yīng)后,它們可以執(zhí)行各種騷操作:

前置增強:悄悄改寫你的提問,讓它更符合 AI 的胃口;或者進行安全檢查,過濾掉危險問題。

后置增強:記錄調(diào)用日志,或者對 AI 的回答進行二次加工。

用法非常簡單,直接在構(gòu)建 ChatClient 時配置 defaultAdvisors 即可。比如,MessageChatMemoryAdvisor 就是我們實現(xiàn)對話記憶的關(guān)鍵“外掛”。

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        new MessageChatMemoryAdvisor(chatMemory), // 對話記憶 advisor
        new QuestionAnswerAdvisor(vectorStore)    // RAG 檢索增強 advisor
    )
    .build();

String response = this.chatClient.prompt()
    // 對話時動態(tài)設(shè)定攔截器參數(shù),比如指定對話記憶的 id 和長度
    .advisors(advisor -> advisor.param("chat_memory_conversation_id", "678")
            .param("chat_memory_response_size", 100))
    .user(userText)
    .call()
    .content();

Advisors 的工作原理就像一條精密的流水線(責(zé)任鏈模式):

Advisors 工作原理圖Advisors 工作原理圖

流水線流程解讀:

1. 用戶的請求進來,被包裝成一個 AdvisedRequest。

2. 請求在 Advisor 鏈上依次傳遞,每個 Advisor 都可以對它進行處理或修改。

3. 最終,請求被發(fā)送給 ChatModel

4. 模型的響應(yīng)再沿著流水線反向傳回,每個 Advisor 也可以處理響應(yīng)。

5. 最后,客戶端收到經(jīng)過層層“加持”的最終結(jié)果。

注意Advisor 的執(zhí)行順序由其 getOrder() 方法決定,值越小,優(yōu)先級越高,跟代碼書寫順序無關(guān)哦!

Advisor 類圖關(guān)系Advisor 類圖關(guān)系

Chat Memory Advisor

要實現(xiàn)對話記憶,ChatMemoryAdvisor 是我們的不二之選。它有幾種實現(xiàn)方式,最常用的是 MessageChatMemoryAdvisor。

MessageChatMemoryAdvisor:將歷史對話作為完整的消息列表(包含用戶和 AI 的角色)添加到提示中。這是最符合現(xiàn)代大模型交互方式的選擇。

PromptChatMemoryAdvisor:將歷史對話拼接成一段文本,塞進系統(tǒng)提示詞里。

VectorStoreChatMemoryAdvisor:使用向量數(shù)據(jù)庫來存儲和檢索歷史對話,適用于更復(fù)雜的場景。

ChatMemoryAdvisor 的幾種實現(xiàn)ChatMemoryAdvisor 的幾種實現(xiàn)

MessageChatMemoryAdvisor 保留了對話的原始結(jié)構(gòu),能讓 AI 更好地理解上下文,因此 強烈推薦使用

Chat Memory

ChatMemoryAdvisor 只是“搬運工”,真正存儲對話歷史的是 Chat Memory。Spring AI 提供了多種“記憶倉庫”:

InMemoryChatMemory:內(nèi)存存儲,簡單快捷,適合測試(我們今天就用它)。

JdbcChatMemoryCassandraChatMemoryNeo4jChatMemory:持久化存儲,可將對話歷史保存在數(shù)據(jù)庫中,適合生產(chǎn)環(huán)境。

打造一個“歷史學(xué)家”AI

理論講完了,上代碼!

初始化 ChatClient

我們通過構(gòu)造器注入 ChatModel,然后構(gòu)建 ChatClient。在構(gòu)建時,設(shè)定好“歷史學(xué)家”的人設(shè)(SYSTEM_PROMPT),并裝上我們的記憶“外掛”——MessageChatMemoryAdvisor。

/**
 * @author 程序員NEO
 * @version 1.0
 * @description 歷史知識專家應(yīng)用
 * @since 2025-07-07
 **/
@Component
@Slf4j
public class HistoryExpertApp {

    private final ChatClient chatClient;

    private static final String SYSTEM_PROMPT = "你是一位風(fēng)趣幽默的歷史知識專家,學(xué)識淵博。" +
            "你需要根據(jù)用戶的提問,生動、清晰地回答相關(guān)的歷史知識。" +
            "如果用戶的問題不清晰,你需要引導(dǎo)用戶提供更多信息。";

    public HistoryExpertApp(ChatModel chatModel) {
        // 初始化基于內(nèi)存的對話記憶
        ChatMemory chatMemory = new InMemoryChatMemory();
        chatClient = ChatClient.builder(chatModel)
                .defaultSystem(SYSTEM_PROMPT)
                .defaultAdvisors(
                        new MessageChatMemoryAdvisor(chatMemory)
                )
                .build();
    }
    // ... doChat 方法
}

這里我們使用了 InMemoryChatMemory,它將對話歷史存在內(nèi)存里。對于生產(chǎn)環(huán)境,記得換成 Redis 或數(shù)據(jù)庫等持久化方案。

編寫對話方法

核心的 doChat 方法接收用戶消息(message)和會話 ID(chatId)。chatId 是區(qū)分不同對話的關(guān)鍵,確保每個用戶的聊天記錄相互獨立。

/**
 * 執(zhí)行聊天操作,處理用戶消息并返回 AI 的響應(yīng)。
 *
 * @param message 用戶發(fā)送的消息
 * @param chatId  對話 ID,用于標識當前會話
 * @return AI 的響應(yīng)內(nèi)容
 */
public String doChat(String message, String chatId) {
    ChatResponse chatResponse = chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec
                    .param(MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) // 設(shè)置對話 ID
                    .param(MessageChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) // 設(shè)置記憶容量
            .call()
            .chatResponse();

    String content = chatResponse.getResult().getOutput().getContent();
    log.info("AI Response: {}", content);
    return content;
}

在 .advisors() 方法中,我們傳入了兩個關(guān)鍵參數(shù):

CHAT_MEMORY_CONVERSATION_ID_KEY: 會話 ID,確保每個用戶的對話歷史是隔離的。

CHAT_MEMORY_RETRIEVE_SIZE_KEY: 對話記憶檢索大小。設(shè)置為 10 表示 AI 在回答時,會參考最近的 10 條消息(5 輪對話)。

見證奇跡的時刻!

我們用一個單元測試來驗證 AI 是否真的擁有了記憶。

@SpringBootTest
public class HistoryExpertAppTest {

    @Resource
    private HistoryExpertApp historyExpertApp;

    @Test
    void testChat() {
        String chatId = UUID.randomUUID().toString();
        
        // 第一輪對話
        System.out.println("--- 第一輪對話 ---");
        String message1 = "我叫NEO,我最喜歡的數(shù)字是7。";
        System.out.println("我: " + message1);
        String answer1 = historyExpertApp.doChat(message1, chatId);
        Assertions.assertNotNull(answer1);
        System.out.println("AI: " + answer1);

        // 第二輪對話
        System.out.println("\n--- 第二輪對話 ---");
        String message2 = "我叫什么名字?我最喜歡的數(shù)字是幾?";
        System.out.println("我: " + message2);
        String answer2 = historyExpertApp.doChat(message2, chatId);
        Assertions.assertNotNull(answer2);
        System.out.println("AI: " + answer2);
    }
}

場景一:擁有完整記憶

當 CHAT_MEMORY_RETRIEVE_SIZE_KEY 設(shè)置為 10 時,AI 能輕松記住我們在第一輪對話中提供的信息。

測試結(jié)果

--- 第一輪對話 ---
我: 我叫NEO,我最喜歡的數(shù)字是7。
AI: 哈哈,Neo!很高興認識你!7確實是一個神奇的數(shù)字——不僅是上帝創(chuàng)造世界的天數(shù),也是彩虹的顏色數(shù)、一周的天數(shù),甚至還是詹姆斯·邦德的代號!看來你和神秘事物很投緣??!

既然你喜歡7,那我考考你:你知道人類歷史上有哪些著名的"七"嗎?比如七大奇跡、七星瓢蟲,或者...《七龍珠》???

說說看,你是更喜歡歷史中的神秘"七",還是生活里有趣的"七"呢?我可以從任何方向展開聊聊!
--- 第二輪對話 ---
我: 我叫什么名字?我最喜歡的數(shù)字是幾?
AI: 哎呀!這像是在考考我這個"博學(xué)多才"的歷史專家了是不是???

讓我想想...上一秒鐘我還記得清清楚楚來著...哦對了!你叫NEO,跟我分享過你最喜歡數(shù)字7的奧秘。就像《黑客帝國》里的主角一樣充滿傳奇色彩的名字,配上神秘的7,簡直完美搭配!

不過說真的,能告訴我你是從哪部《黑客帝國》開始認識NEO這個角色的呢?是第一部經(jīng)典之作,還是后來的續(xù)集?還有,為什么偏偏是7這個數(shù)字吸引了你?我覺得這里面一定有段有趣的故事要聽!??

AI 擁有記憶的測試結(jié)果AI 擁有記憶的測試結(jié)果

成功了!AI 準確地記住了我們的信息。

場景二:秒變“金魚記憶”

現(xiàn)在,我們將記憶容量改為 1,這意味著 AI 只能看到當前用戶的提問,完全不記得之前的對話。

.param(MessageChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 1)

再次運行測試,看看會發(fā)生什么。

測試結(jié)果(失憶版)

--- 第一輪對話 ---
我: 我叫NEO,我最喜歡的數(shù)字是7。
AI: 哎呀,歡迎Neo!讓我猜猜...你喜歡數(shù)字7是不是因為"Matrix"里的第七號巧合?不過既然你提到了數(shù)字7,讓我告訴你一個有趣的歷史故事吧!

你知道嗎?在古希臘人眼中,7可是個神奇的數(shù)字組合 - 就像我這個歷史專家平時吃著橄欖,看著帕特農(nóng)神廟時想的那樣。他們發(fā)現(xiàn)了七個智慧賢人,觀察到天空中有七顆可見的行星(當然那是沒有望遠鏡的時代),甚至連他們的神話里都有七重天的說法。

嘿,作為一個喜歡解密歷史的人,我發(fā)現(xiàn)你這個名字和數(shù)字7的組合讓我想起了一些有意思的東西。比如,柏拉圖在他的著作里經(jīng)常提到"理想國",而那個國家正好有七個等級制度。或者亞歷山大圖書館,它可是古代世界七大奇跡之一!

說起來,你是更喜歡探索數(shù)字7在數(shù)學(xué)上的獨特性質(zhì),還是它在不同文化中的神秘象征?讓我們一起揭開這個數(shù)字背后的更多秘密吧!
--- 第二輪對話 ---
我: 我叫什么名字?我最喜歡的數(shù)字是幾?
AI: 哎呀,這可真是個讓人摸不著頭腦的問題!讓我這個歷史專家翻遍了古希臘的羊皮卷、巴比倫的泥板和中國的甲骨文都沒找到答案。難道你是想讓我展示特洛伊木馬式的驚喜?還是像斯芬克斯謎語那樣考考我?

不過作為一個精通歷史的學(xué)者,我可以告訴你一些有趣的背景:在古代埃及,人們會給新生兒起非常特別的名字,有時候是根據(jù)他們出生的日子來取的。而說到數(shù)字,畢達哥拉斯可是堅信萬物皆數(shù)呢!

AI 失憶的測試結(jié)果AI 失憶的測試結(jié)果

看到了嗎?僅僅是一個參數(shù)的差別,AI 就從“智能”變成了“智障”。這個對比鮮明地展示了對話記憶的重要性。

責(zé)任編輯:武曉燕 來源: 程序員NEO
相關(guān)推薦

2025-07-04 00:00:00

2020-04-23 13:51:05

AI管理GUI

2023-04-21 15:54:46

AI開源

2023-04-28 09:02:24

智能客服人工智能Siri

2024-11-07 15:40:00

2024-12-05 08:15:00

2023-01-14 14:59:05

達摩院

2025-06-09 08:30:00

2021-06-22 09:37:51

數(shù)字化

2021-07-01 15:56:42

深度學(xué)習(xí)人工智能互聯(lián)網(wǎng)

2024-01-08 13:33:00

數(shù)據(jù)訓(xùn)練

2024-08-27 00:00:01

AI應(yīng)用框架

2024-11-25 15:30:00

語言模型數(shù)據(jù)

2025-05-30 09:15:00

2025-06-17 17:16:51

LLMChatGPTAI

2023-04-04 19:09:20

ChatGPT開源

2018-06-12 10:16:55

百度多輪交互智能音箱

2022-11-25 13:02:28

螞蟻集團

2024-04-03 12:48:00

2023-09-14 12:34:20

AI模型
點贊
收藏

51CTO技術(shù)棧公眾號