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

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法