chat - 聊天会话(对话)的记忆与持久化
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);
}
}