继续spring-ai学习之旅,大模型本身是无状态的,也就是每次请求对它来说,都是全新的,无记忆!比如:我们以ollama本地加载qwen3:0.6b模型为例,依次问它以下三个问题:
- 有1个变量A,它的值是5,那么A加1后,变成几?
- A再乘上3,结果是多少?
- A再减1,现在是多少?
直接用postman 之类的工具发给ollama,入参示例:- {
- "model": "qwen3:0.6b",
- "messages": [
- {
- "role": "user",
- "content": "有1个变量A,它的值是5,那么A加1后,变成几?"
- }
- ],
- "stream": false
- }
复制代码 响应:- {
- "model": "qwen3:0.6b",
- "created_at": "2025-07-20T02:22:48.9488355Z",
- "message": {
- "role": "assistant",
- "content": "<think>\n嗯,我现在要解决的问题是:有一个变量A,它的值是5,那么A加1后变成几?看起来好像挺简单的,但可能我有点困惑,特别是关于变量赋值的问题。让我仔细想想。\n\n首先,变量A的初始值是5,对吧?所以现在的问题应该是,当我们将A的值加上1之后,A变成了多少。不过,我是不是应该考虑在编程语言中,变量赋值的规则?比如在编程中,如果变量的值是5,那么在执行A += 1之后,变量的值应该变成6吗?对,如果是这样的话,答案应该是6。不过,有没有可能这里的问题是在数学上,或者只是简单的加法运算?\n\n不过,可能题目是在某种特定的上下文中,比如在数学问题中,或者可能题目本身存在一些陷阱。比如,有时候变量可能有不同的含义,或者在某些语言中,变量名可能与实际值不一致?不过题目中明确说变量A的值是5,所以应该是在数学运算范围内。\n\n或者,可能题目是想测试学生的数学基础,比如是否知道加法运算的规则。比如,5加1等于6,所以正确答案应该是6。这时候应该直接给出结果,不需要进一步的解释,对吧?\n\n不过,有没有可能题目中的变量A在某种情况下会被其他操作影响?比如,在某些编程语言中,变量名可能被修改,但题目中并没有提到其他操作,只是简单的加法。所以应该没问题。\n\n或者,可能题目中的变量A在某些情况下会变成其他值,比如在处理数组或者列表的时候?不过题目里并没有提到数组,所以应该还是简单的加法。\n\n再仔细检查一遍,题目是“有1个变量A,它的值是5,那么A加1后,变成几?”,所以直接的数学运算就是5+1=6,所以正确结果应该是6。可能这个问题本身是数学题,所以答案是6,不需要进一步的思考,只是直接给出结果。\n\n不过,有没有可能题目是在某种特定的数学问题中,比如涉及代数方程或者变量替换?比如,如果A是一个变量,那么加1之后,变量的值应该保持不变,对吗?不过,如果是这样的话,答案还是6,对吗?\n\n或者,可能题目中的“加1”指的是其他操作,比如在某种特定的编程语言中,变量名是A,但实际值可能不同?比如,在某些语言中,变量名可能被修改,但题目中已经明确说变量A的值是5,所以应该不需要考虑其他情况。\n\n总之,根据题目的信息,正确的答案应该是6,所以应该直接回答6。\n</think>\n\n根据题目描述,变量A的初始值为5,执行A += 1操作后,变量A的值变为:\n\n**6**\n\n因此,答案是:6。"
- },
- "done_reason": "stop",
- "done": true,
- "total_duration": 15604468300,
- "load_duration": 34368500,
- "prompt_eval_count": 28,
- "prompt_eval_duration": 42765400,
- "eval_count": 598,
- "eval_duration": 15526323700
- }
复制代码
继续,问第2个问题:这次就回答不上来了
继续,第3个问题:意料之中的回答不出来
那么,如何破局呢?
ollama的api文档里,有一行提示
啥意思呢?messages是1个list,我们可以每次问新问题时,把先前问过的问题按顺序都塞到里面,这样大模型从前往后,依次理解这些问题,就能回答出来了,即:- {
- "model": "qwen3:0.6b",
- "messages": [
- {
- "role": "user",
- "content": "有1个变量A,它的值是5,那么A加1后,变成几?"
- },
- {
- "role": "user",
- "content": "A再乘上3,结果是多少?"
- },
- {
- "role": "user",
- "content": "A再减1,现在是多少?"
- }
- ],
- "stream": false
- }
复制代码
这次回答对了!很多高上大的东西,捅破窗户纸后,会发现实现方式居然这么朴实无华!
理解原理后,实现起来就非常容易了,无非是在工程应用层面,弄1个存储(redis/mysql/jvm内存中都行),把历史上的问题都存下来,下次提问时,都扔给它就好。spring-ai就是这么做的,下面演示最基本的In Memory模式(即:保存到本机内存中)
一、添加pom依赖
- 1 <dependency>
- 2 <groupId>org.springframework.ai</groupId>
- 3 spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
- 4 <version>1.0.0</version>
- 5 </dependency>
复制代码 View Code友情提示:目前spring-ai-starter-model-chat-memory-repository-jdbc最新版本只有1.0.0,但是spring-ai-bom最新版却是1.1.0。
最新的ChatMemory接口,在1.1.0中,有1个方法定义:
而spring-ai-starter-model-chat-memory-repository-jdbc 1.0.0版本中的ChatMemory接口
二个版本的方法签名不一样,导致这2个版本混用时,运行时会报错,希望将来1.1.0出来后,会统一。
为了解决使用最新版本的这个矛盾,可以参考 org.springframework.ai.chat.memory.MessageWindowChatMemory,自行定义1个新类- package com.cnblogs.yjmyzz;
- import org.springframework.ai.chat.memory.ChatMemory;
- import org.springframework.ai.chat.memory.ChatMemoryRepository;
- import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
- import org.springframework.ai.chat.messages.Message;
- import org.springframework.ai.chat.messages.SystemMessage;
- import org.springframework.util.Assert;
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Set;
- public class MyMessageWindowChatMemory implements ChatMemory {
- private static final int DEFAULT_MAX_MESSAGES = 20;
- private final ChatMemoryRepository chatMemoryRepository;
- private final int maxMessages;
- private MyMessageWindowChatMemory(ChatMemoryRepository chatMemoryRepository, int maxMessages) {
- Assert.notNull(chatMemoryRepository, "chatMemoryRepository cannot be null");
- Assert.isTrue(maxMessages > 0, "maxMessages must be greater than 0");
- this.chatMemoryRepository = chatMemoryRepository;
- this.maxMessages = maxMessages;
- }
- @Override
- public void add(String conversationId, List<Message> messages) {
- Assert.hasText(conversationId, "conversationId cannot be null or empty");
- Assert.notNull(messages, "messages cannot be null");
- Assert.noNullElements(messages, "messages cannot contain null elements");
- List<Message> memoryMessages = this.chatMemoryRepository.findByConversationId(conversationId);
- List<Message> processedMessages = process(memoryMessages, messages);
- this.chatMemoryRepository.saveAll(conversationId, processedMessages);
- }
- @Override
- public List<Message> get(String conversationId, int lastN) {
- Assert.hasText(conversationId, "conversationId cannot be null or empty");
- List<Message> messages = this.chatMemoryRepository.findByConversationId(conversationId);
- if (messages.size() > lastN) {
- return messages.subList(messages.size() - lastN, messages.size());
- }
- return messages;
- }
- @Override
- public void clear(String conversationId) {
- Assert.hasText(conversationId, "conversationId cannot be null or empty");
- this.chatMemoryRepository.deleteByConversationId(conversationId);
- }
- private List<Message> process(List<Message> memoryMessages, List<Message> newMessages) {
- List<Message> processedMessages = new ArrayList<>();
- Set<Message> memoryMessagesSet = new HashSet<>(memoryMessages);
- boolean hasNewSystemMessage = newMessages.stream()
- .filter(SystemMessage.class::isInstance)
- .anyMatch(message -> !memoryMessagesSet.contains(message));
- memoryMessages.stream()
- .filter(message -> !(hasNewSystemMessage && message instanceof SystemMessage))
- .forEach(processedMessages::add);
- processedMessages.addAll(newMessages);
- if (processedMessages.size() <= this.maxMessages) {
- return processedMessages;
- }
- int messagesToRemove = processedMessages.size() - this.maxMessages;
- List<Message> trimmedMessages = new ArrayList<>();
- int removed = 0;
- for (Message message : processedMessages) {
- if (message instanceof SystemMessage || removed >= messagesToRemove) {
- trimmedMessages.add(message);
- } else {
- removed++;
- }
- }
- return trimmedMessages;
- }
- public static MyMessageWindowChatMemory.Builder builder() {
- return new MyMessageWindowChatMemory.Builder();
- }
- public static final class Builder {
- private ChatMemoryRepository chatMemoryRepository;
- private int maxMessages = DEFAULT_MAX_MESSAGES;
- private Builder() {
- }
- public MyMessageWindowChatMemory.Builder chatMemoryRepository(ChatMemoryRepository chatMemoryRepository) {
- this.chatMemoryRepository = chatMemoryRepository;
- return this;
- }
- public MyMessageWindowChatMemory.Builder maxMessages(int maxMessages) {
- this.maxMessages = maxMessages;
- return this;
- }
- public MyMessageWindowChatMemory build() {
- if (this.chatMemoryRepository == null) {
- this.chatMemoryRepository = new InMemoryChatMemoryRepository();
- }
- return new MyMessageWindowChatMemory(this.chatMemoryRepository, this.maxMessages);
- }
- }
- }
复制代码 二、注入ChatMemory实例- @Bean
- public ChatMemoryRepository chatMemoryRepository() {
- return new InMemoryChatMemoryRepository();
- }
- @Bean
- public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
- return MyMessageWindowChatMemory.builder()
- .chatMemoryRepository(chatMemoryRepository)
- .maxMessages(10)
- .build();
- }
复制代码 三、使用示例- @RequestMapping(value = "/conversation-stream", produces = "text/html;charset=utf-8")
- public Flux<String> conversationStream(@RequestParam String conversationId, @RequestParam String prompt) {
- // 1. 当前新问题,扔到聊天上下文中
- chatMemory.add(conversationId, new UserMessage(prompt));
- // 2. 把历史消息全都取出来
- List<Message> history = chatMemory.get(conversationId, MAX_HISTORY_SESSION);
- // 3. 所有历史消息,扔给大模型
- return chatClient.prompt(new Prompt(history))
- .stream()
- .content();
- }
复制代码 四、运行效果
浏览器里,开3个tab依次问这3个问题:
文中示例代码 :yjmyzz/spring-ai-sample at day07
参考:
Chat Memory :: Spring AI Reference
ollama/docs/api.md at main · ollama/ollama
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |