Spring AI 对话记忆 + 结构化输出
对话记忆
大型语言模型 (LLM) 是无状态的,这意味着它们不会保留先前交互的信息。
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Test</span>
<span style="color:#015692">public</span> <span style="color:#015692">void</span> <span style="color:#b75501">testChatOptions</span>() {
<span style="color:#b75501">String</span> <span style="color:#54790d">content</span> <span style="color:#ab5656">=</span> chatClient.prompt()
.user(<span style="color:#54790d">"我叫小兔子 "</span>)
.call()
.content();
System.out.println(content);
System.out.println(<span style="color:#54790d">"--------------------------------------------------------------------------"</span>);
content = chatClient.prompt()
.user(<span style="color:#54790d">"我叫什么 ?"</span>)
.call()
.content();
System.out.println(content);
}
</code></span></span>

那我们平常跟一些大模型聊天是怎么记住我们对话的呢?实际上,每次对话都需要将之前的对话消息内置发送给大模型,这种方式称为多轮对话。

SpringAi提供了一个ChatMemory的组件用于存储聊天记录,允许您使用 LLM 跨多个交互存储和检索信息。并且可以为不同用户的多个交互之间维护上下文或状态。
可以在每次对话的时候把当前聊天信息和模型的响应存储到ChatMemory, 然后下一次对话把聊天记录取出来再发给大模型。
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java">`
<span style="color:#656e77">//输出 名字叫徐庶</span>
</code></span></span>
但是这样做未免太麻烦! 能不能简化? 思考一下!


用我们之前的Advisor对话拦截是不是就可以不用每次手动去维护了。 并且SpringAi早已体贴的为我提供了ChatMemoryAutoConfiguration自动配置类
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-xml"><<span style="color:#b75501">dependency</span>>
<<span style="color:#b75501">groupId</span>>org.springframework.ai</<span style="color:#b75501">groupId</span>>
<<span style="color:#b75501">artifactId</span>>spring-ai-autoconfigure-model-chat-memory</<span style="color:#b75501">artifactId</span>>
</<span style="color:#b75501">dependency</span>>
</code></span></span>
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@AutoConfiguration</span>
<span style="color:#015692">@ConditionalOnClass({ ChatMemory.class, ChatMemoryRepository.class })</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">ChatMemoryAutoConfiguration</span> {
<span style="color:#015692">@Bean</span>
<span style="color:#015692">@ConditionalOnMissingBean</span>
ChatMemoryRepository <span style="color:#b75501">chatMemoryRepository</span>() {
<span style="color:#015692">return</span> <span style="color:#015692">new</span> <span style="color:#b75501">InMemoryChatMemoryRepository</span>();
}
<span style="color:#015692">@Bean</span>
<span style="color:#015692">@ConditionalOnMissingBean</span>
ChatMemory <span style="color:#b75501">chatMemory</span>(ChatMemoryRepository chatMemoryRepository) {
<span style="color:#015692">return</span> MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build();
}
}
</code></span></span>
所以我们可以这样用:
PromptChatMemoryAdvisor
SpringAi提供了 PromptChatMemoryAdvisor 专门用于对话记忆的拦截
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@SpringBootTest</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">ChatMemoryTest</span> {
ChatClient chatClient;
<span style="color:#015692">@BeforeEach</span>
<span style="color:#015692">public</span> <span style="color:#015692">void</span> <span style="color:#b75501">init</span>(<span style="color:#015692">@Autowired</span>
DeepSeekChatModel chatModel,
<span style="color:#015692">@Autowired</span>
ChatMemory chatMemory) {
chatClient = ChatClient
.builder(chatModel)
.defaultAdvisors(
<span style="color:#656e77">// PromptChatMemoryAdvisor拦截器 就会自动将我们与大模型的历史对话记录下来</span>
PromptChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
<span style="color:#015692">@Test</span>
<span style="color:#015692">public</span> <span style="color:#015692">void</span> <span style="color:#b75501">testChatOptions</span>() {
<span style="color:#b75501">String</span> <span style="color:#54790d">content</span> <span style="color:#ab5656">=</span> chatClient.prompt()
.user(<span style="color:#54790d">"我叫徐庶 ?"</span>)
<span style="color:#656e77">// </span>
.advisors(<span style="color:#015692">new</span> <span style="color:#b75501">ReReadingAdvisor</span>())
.call()
.content();
System.out.println(content);
System.out.println(<span style="color:#54790d">"--------------------------------------------------------------------------"</span>);
content = chatClient.prompt()
.user(<span style="color:#54790d">"我叫什么 ?"</span>)
.advisors(<span style="color:#015692">new</span> <span style="color:#b75501">ReReadingAdvisor</span>())
.call()
.content();
System.out.println(content);
}
}
</code></span></span>
配置聊天记录最大存储数量
你要知道, 我们把聊天记录发给大模型, 都是算token计数的。
大模型的token是有上限了, 如果你发送过多聊天记录,可能就会导致token过长。
如下是大模型存储的 token 历史条数上限。

并且更多的token也意味更多的费用, 更久的解析时间. 所以不建议太长
(DEFAULT_MAX_MESSAGES默认20即10次对话)
一旦超出DEFAULT_MAX_MESSAGES只会存最后面N条(可以理解为先进先出),参考MessageWindowChatMemory源码
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Bean</span>
ChatMemory <span style="color:#b75501">chatMemory</span>(<span style="color:#015692">@Autowired</span> ChatMemoryRepository chatMemoryRepository) {
<span style="color:#656e77">// MessageWindowChatMemory 创建一个历史对话存储的配置,</span>
<span style="color:#015692">return</span> MessageWindowChatMemory
.builder()
.maxMessages(<span style="color:#b75501">10</span>) <span style="color:#656e77">// 设置最大存储 10 条</span>
.chatMemoryRepository(chatMemoryRepository).build();
}
</code></span></span>
配置多用户隔离记忆
如果有多个用户在进行对话, 肯定不能将对话记录混在一起, 不同的用户的对话记忆需要隔离
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Test</span>
<span style="color:#015692">public</span> <span style="color:#015692">void</span> <span style="color:#b75501">testChatOptions</span>() {
<span style="color:#b75501">String</span> <span style="color:#54790d">content</span> <span style="color:#ab5656">=</span> chatClient.prompt()
.user(<span style="color:#54790d">"我叫徐庶 ?"</span>)
<span style="color:#656e77">// 注意:这里要先构建一个 ChatMemory的 Bean,和上面类似,这里我们设置历史对话的用户ID</span>
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID,<span style="color:#54790d">"1"</span>))
.call()
.content();
System.out.println(content);
System.out.println(<span style="color:#54790d">"--------------------------------------------------------------------------"</span>);
content = chatClient.prompt()
.user(<span style="color:#54790d">"我叫什么 ?"</span>)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID,<span style="color:#54790d">"1"</span>))
.call()
.content();
System.out.println(content);
System.out.println(<span style="color:#54790d">"--------------------------------------------------------------------------"</span>);
content = chatClient.prompt()
.user(<span style="color:#54790d">"我叫什么 ?"</span>)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID,<span style="color:#54790d">"2"</span>))
.call()
.content();
System.out.println(content);
}
</code></span></span>
会发现, 不同的CONVERSATION_ID,会有不同的记忆

原理源码$
主要有前置存储
MessageWindowChatMemory
具体存储实现
ChatMemoryRepository

数据库存储对话记忆
默认情况, 对话内容会存在jvm内存会导致:
- 一直存最终会撑爆JVM导致OOM。
- 重启就丢了, 如果已想存储到第三方存储进行持久化
springAi内置提供了以下几种方式(例如 Cassandra、JDBC 或 Neo4j), 这里演示下JDBC方式
- 添加依赖
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-xml"> <<span style="color:#b75501">dependency</span>>
<<span style="color:#b75501">groupId</span>>org.springframework.ai</<span style="color:#b75501">groupId</span>>
<<span style="color:#b75501">artifactId</span>>spring-ai-starter-model-chat-memory-repository-jdbc</<span style="color:#b75501">artifactId</span>>
</<span style="color:#b75501">dependency</span>>
<span style="color:#656e77"><!--jdbc--></span>
<<span style="color:#b75501">dependency</span>>
<<span style="color:#b75501">groupId</span>>org.springframework.boot</<span style="color:#b75501">groupId</span>>
<<span style="color:#b75501">artifactId</span>>spring-boot-starter-jdbc</<span style="color:#b75501">artifactId</span>>
</<span style="color:#b75501">dependency</span>>
<span style="color:#656e77"><!--mysql驱动--></span>
<<span style="color:#b75501">dependency</span>>
<<span style="color:#b75501">groupId</span>>com.mysql</<span style="color:#b75501">groupId</span>>
<<span style="color:#b75501">artifactId</span>>mysql-connector-j</<span style="color:#b75501">artifactId</span>>
<<span style="color:#b75501">scope</span>>runtime</<span style="color:#b75501">scope</span>>
</<span style="color:#b75501">dependency</span>>
</code></span></span>
- 添加配置(目前我们的需要创建一个schema-mysql.sql 文件,就是一个 SQL 脚本,在后面有配置)SPRING_AI_CHAT_MEMORY 表存储用户的历史对话,数据库,我们自行定义将该数据表存储到那个数据库中即可。
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-properties"><span style="color:#015692">spring.ai.chat.memory.repository.jdbc.initialize-schema</span>=<span style="color:#54790d">always</span>
<span style="color:#015692">spring.ai.chat.memory.repository.jdbc.schema</span>=<span style="color:#54790d">classpath:/schema-mysql.sql</span>
</code></span></span>
如下是 MySQL 的配置:
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-yaml"><span style="color:#015692">spring:</span>
<span style="color:#015692">datasource:</span>
<span style="color:#015692">username:</span> <span style="color:#54790d">root</span>
<span style="color:#015692">password:</span> <span style="color:#b75501">123456</span>
<span style="color:#015692">url:</span> <span style="color:#54790d">jdbc:mysql://localhost:3306/springai?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&</span>
<span style="color:#015692">driver-class-name:</span> <span style="color:#54790d">com.mysql.cj.jdbc.Driver</span>
</code></span></span>
- 配置类
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Configuration</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">ChatMemoryConfig</span> {
<span style="color:#015692">@Bean</span> <span style="color:#656e77">// JdbcChatMemoryRepository 是已经被封装好自动装配好了,就可以使用</span>
ChatMemory <span style="color:#b75501">chatMemory</span>(<span style="color:#015692">@Autowired</span> JdbcChatMemoryRepository chatMemoryRepository) {
<span style="color:#015692">return</span> MessageWindowChatMemory
.builder()
.maxMessages(<span style="color:#b75501">1</span>) <span style="color:#656e77">// 设置存储为上面我们传的变量的 jdbc 的存储方式</span>
.chatMemoryRepository(chatMemoryRepository).build();
}
}
</code></span></span>
- resources/schema-mysql.sql(目前1.0.0版本需要自己定义,没有提供脚本),创建这个SPRING_AI_CHAT_MEMORY 数据表,来存储用户的历史对话
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java">CREATE TABLE IF NOT EXISTS <span style="color:#b75501">SPRING_AI_CHAT_MEMORY</span> (
`conversation_id` VARCHAR(<span style="color:#b75501">36</span>) NOT NULL,
`content` TEXT NOT NULL,
`type` VARCHAR(<span style="color:#b75501">10</span>) NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`)
);
</code></span></span>
- 测试
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@SpringBootTest</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">ChatMemoryTest</span> {
ChatClient chatClient;
<span style="color:#015692">@BeforeEach</span>
<span style="color:#015692">public</span> <span style="color:#015692">void</span> <span style="color:#b75501">init</span>(<span style="color:#015692">@Autowired</span>
DeepSeekChatModel chatModel,
<span style="color:#015692">@Autowired</span>
ChatMemory chatMemory) {
chatClient = ChatClient
.builder(chatModel)
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
<span style="color:#015692">@Test</span>
<span style="color:#015692">public</span> <span style="color:#015692">void</span> <span style="color:#b75501">testChatOptions</span>() {
<span style="color:#b75501">String</span> <span style="color:#54790d">content</span> <span style="color:#ab5656">=</span> chatClient.prompt()
.user(<span style="color:#54790d">"你好,我叫徐庶!"</span>)
.advisors(<span style="color:#015692">new</span> <span style="color:#b75501">ReReadingAdvisor</span>())
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID,<span style="color:#54790d">"1"</span>))
.call()
.content();
System.out.println(content);
System.out.println(<span style="color:#54790d">"--------------------------------------</span></code></span></span>更多推荐




所有评论(0)