Solon v3.8.0

chat - 聊天会话(对话)的记忆与持久化

</> markdown
2025年12月28日 下午7:45:35

大语言模型的接口是无状态的服务,如果需要形成有记忆的会话窗口。需要使用“多消息”提示语,把历史对话都输入。

1、使用“聊天会话”接口(ChatSession)

ChatSession 可以记录消息,还可以作为提示语的参数使用(直接输给 chatModel 的提示语,先输给 chatSession)。起到会话记忆的作用。

public void case3() throws IOException {
    //聊天会话
    ChatSession chatSession = InMemoryChatSession.builder().maxMessages(10).sessionId("session-1").build(); //安排个会话id
    
    
    //1.
    chatSession.addMessage(ChatMessage.ofUser("hello")); //添加请求消息
    chatModel.prompt(chatSession).call();  //(把 chatSession 作为参数)AI消息自动记录到会话里
   
    
    //2.
    chatSession.addMessage(ChatMessage.ofUser("Who are you?")); //添加请求消息
    chatModel.prompt(chatSession).stream(); //(把 chatSession 作为参数)AI消息自动汇总并记录到会话里
}

2、基于 Web 的聊天会话记忆参考

import org.noear.solon.ai.chat.ChatModel;
import org.noear.solon.ai.chat.ChatSession;
import org.noear.solon.ai.chat.message.ChatMessage;
import org.noear.solon.ai.chat.session.InMemoryChatSession;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Header;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.web.sse.SseEvent;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Controller
public class DemoController {
    @Inject
    ChatModel chatModel;

    final Map<String, ChatSession> sessionMap = new ConcurrentHashMap<>();

    //手动转为 sse
    @Mapping("case3")
    public Flux<SseEvent> case3(@Header("sessionId") String sessionId, String prompt) throws IOException {
        ChatSession chatSession = sessionMap.computeIfAbsent(sessionId, k -> InMemoryChatSession.builder().build());

        chatSession.addMessage(ChatMessage.ofUser(prompt));

        //注意提示语的参数:chatSession
        return Flux.from(chatModel.prompt(chatSession).stream())
                .filter(resp -> resp.hasContent())
                .map(resp -> new SseEvent().data(resp.getContent()));
    }
}

3、ChatMessage (消息)的序列化方法

接口描述
ChatMessage.toJson(message)把单条消息转为 json
ChatMessage.fromJson(json)把 json 转为单条消息
ChatMessage.toNdjson(messages)把一批消息转为 ndjson
ChatMessage.fromNdjson(ndjson)把 ndjson 转为一批消息

以上方法 v3.8.0 后支持。v3.8.0 前的可以 copy 里面的代码。

4、ChatSession 的持久化定制

InMemoryChatSession 只适合开发测试用。一般需要与业务结合,定制需要的聊天会话实现(比如用 Redis 性能就会比较好)。也可以用 JDBC(Mybql、PgSQL、MongoDb)。以下为一个简单的 Redis 会话参考(仅供参考)

public class RedisChatSession implements ChatSession {
    private final String sessionId;
    private final RedisList redisList;
    private final InMemoryChatSession memSession; //用它做缓冲

    public RedisChatSession(RedisClient redisClient, InMemoryChatSession memSession) {
        this.memSession = memSession;
        this.sessionId = memSession.getSessionId();
        this.redisList = redisClient.getList(sessionId);
    }

    @Override
    public String getSessionId() {
        return sessionId;
    }

    /**
     * 获取最大消息数
     */
    public int getMaxMessages() {
        return memSession.getMaxMessages();
    }

    @Override
    public List<ChatMessage> getMessages() {
        return memSession.getMessages();
    }

    @Override
    public void addMessage(Collection<? extends ChatMessage> messages) {
        for (ChatMessage m : messages) {
            memSession.addMessage(m);
            redisList.add(ChatMessage.toJson(m));
        }
    }

    @Override
    public void clear() {
        memSession.clear();
        redisList.clear();
    }
}

5、ChatSession 的接口字典(参考)

public interface ChatSession extends ChatPrompt {
    /**
     * 获取会话id
     */
    String getSessionId();

    /**
     * 获取消息
     */
    List<ChatMessage> getMessages();

    /**
     * 添加消息
     */
    default void addMessage(String userMessage) {
        addMessage(ChatMessage.ofUser(userMessage));
    }

    /**
     * 添加消息
     */
    default void addMessage(ChatMessage... messages) {
        addMessage(Arrays.asList(messages));
    }

    /**
     * 添加消息
     */
    default void addMessage(ChatPrompt prompt) {
        addMessage(prompt.getMessages());
    }

    /**
     * 添加消息
     */
    void addMessage(Collection<? extends ChatMessage> messages);

    /**
     * 清空消息
     */
    void clear();


    /// //////////////////////////////////////

    /**
     * 转为 ndjson
     */
    default String toNdjson() throws IOException {
        return ChatMessage.toNdjson(getMessages());
    }

    /**
     * 转为 ndjson
     */
    default void toNdjson(OutputStream out) throws IOException {
        ChatMessage.toNdjson(getMessages(), out);
    }

    /**
     * 加载 ndjson
     */
    default void loadNdjson(String ndjson) throws IOException {
        ChatMessage.fromNdjson(ndjson, this::addMessage);
    }

    /**
     * 加载 ndjson
     */
    default void loadNdjson(InputStream ins) throws IOException {
        ChatMessage.fromNdjson(ins, this::addMessage);
    }
}