StackTraces
Spring AI已驗證

Spring AI 報 Conversation ID is required

@debug_master13 天前更新閱讀 6 min00

Spring AI 1.0.7 / 1.1.6 起 PromptChatMemoryAdvisor 標記棄用,2.0.0-M6 起直接移除,編譯會報 cannot find symbol。新版聊天記憶 advisor 要求每次請求顯式傳 conversation ID,舊的隱式兜底被刪掉。沒傳 ID 的程式碼升級後會拋例外,多用戶場景還會互相讀歷史。

Issue & Env(案發現場與環境)

  • 業務場景:升級 Spring AI 後,原本能跑的多輪對話直接編譯不過;或者改完能編譯,一調用還是拋例外。
  • 環境:以 Spring AI 1.1.x 為基線。PromptChatMemoryAdvisor 自 1.0.7、1.1.6 起棄用,2.0.0-M6 起移除。1.0.7 之前的版本仍可用舊寫法。
  • 影響面:單用戶單會話時往往看不出問題,一旦多用戶或多會話共用,歷史就會串。

StackTrace(堆疊日誌)

編譯期先炸。IDE 或 mvn compile 給出的原文:

java: cannot find symbol
  symbol:   class PromptChatMemoryAdvisor
  location: package org.springframework.ai.chat.client.advisor

如果程式碼已換成新 advisor,但沒傳 conversation ID,運行期會拋:

java.lang.IllegalStateException: Conversation ID is required

這條例外文案在不同版本里可能不一樣,以實際版本為準。截至撰寫時,官方文檔只聲明 conversation ID 為必填,未給出統一的消息文本,這裡不做臆測。

Reproduce(復現程式碼)

先看舊寫法,這是升級前很常見的一段:

@Bean
ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
    return builder
            .defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory))
            .build();
}

升級到新版本後,PromptChatMemoryAdvisor 已不在 org.springframework.ai.chat.client.advisor 包裡,編譯直接失敗。

把 advisor 換掉、卻忘了傳會話 ID,運行期就會拋 IllegalStateException:

chatClient.prompt()
        .user("你刚才说的第二点再展开下")
        .call()
        .content();

Root Cause(根因分析)

舊的隱式 conversation ID 被去掉了

老版本里,調用方不傳 conversation ID 時,advisor 會用一個隱式預設值兜底。單用戶跑得通,但多個用戶、多個會話共用同一個預設值,歷史就會互相汙染。A 用戶問「上一句是什麼」,可能讀到 B 用戶的對話。

新版本把這個兜底刪了,改成強制顯式傳入。誰不傳,誰就拋 IllegalStateException。這是一次有意的行為收緊,不是 bug。

advisor 類被整體替換

PromptChatMemoryAdvisor 屬於舊體系,它綁的是「把歷史拼進 prompt」這一層。1.0.7 / 1.1.6 起該類進入棄用狀態,2.0.0-M6 起移除,由 MessageChatMemoryAdvisor 承接,底層走新的 BaseChatMemoryAdvisor 體系。

所以升級後有兩件事同時發生:類名換了,參數也從「預設兜底」變成「必須顯式傳」。只改其中一件,另一件仍會出錯。

The Fix(解決方案)

第一步:換 advisor

把 PromptChatMemoryAdvisor 換成 MessageChatMemoryAdvisor:

@Bean
ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
    return builder
            .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
            .build();
}

第二步:每次請求傳 conversation ID

光換類還不夠,得在請求裡帶上會話 ID:

chatClient.prompt()
        .user("你刚才说的第二点再展开下")
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
        .call()
        .content();

sessionId 要每個會話唯一。推薦用「用戶 ID + 會話 ID」拼出來,比如 tenantId + ":" + conversationId。別寫死成常量字符串,否則等於把不同用戶的歷史又混到一起。

從舊寫法遷到新寫法

對照如下:

// 旧:隐式默认 ID,多会话会串历史
new PromptChatMemoryAdvisor(chatMemory)

// 新:advisor 换成 MessageChatMemoryAdvisor
MessageChatMemoryAdvisor.builder(chatMemory).build()

// 旧:不带 conversation ID
chatClient.prompt().user(q).call().content()

// 新:每次请求显式传
chatClient.prompt().user(q)
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
        .call().content()

升級檢查清單

  • 全局搜 PromptChatMemoryAdvisor,全部替換。
  • 搜聊天記憶相關的 .call(),確認每個請求都帶了 CONVERSATION_ID。
  • 壓一遍多會話併發場景,確認歷史不串。

References(參考來源)

最後更新於 2026-09-28

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法