Solon AI 开发学习9 - chat - 聊天会话(对话)的记忆与持久化

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

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

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

java 复制代码
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 的聊天会话记忆参考

java 复制代码
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、ChatSession 的持久化定制

InMemoryChatSession 只适合开发测试用。一般需要与业务结合,定制需要的聊天会话实现(比如用 Redis 性能就会比较好)。也可以用 JDBC(Mybql、PgSQL、MongoDb),假如有个 SessionJdbcService 服务,是用于会话的消息执久化管理的。通过定制直接同步数据(仅供参考)

java 复制代码
public class JdbcChatSession implements ChatSession {
    private SessionJdbcService sessionService; 
    private String sessionId;
    
    public JdbcChatSession(String sessionId) {
        this.sessionId = sessionId;
    }
    
    @Override
    public String getSessionId(){
        return sessionId;
    }
    
    @Override
    public List<ChatMessage> getMessages() {
        //设计时,可以通过时间限制消息,或者具体的数量
        return sessionService.getMessages(getSessionId(), 100); //只取100条
    }
    
    @Override
    public void addMessage(ChatMessage... messages) {
        sessionService.addMessages(getSessionId(), messages);
    }
    
    @Override
    public void clear() {
        sessionService.clearMessages(getSessionId());
    }
}

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

java 复制代码
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 {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        toNdjson(out);
        return new String(out.toByteArray(), Solon.encoding());
    }

    /**
     * 转为 ndjson
     */
    default void toNdjson(OutputStream out) throws IOException {
        for (ChatMessage msg : getMessages()) {
            out.write(ChatMessage.toJson(msg).getBytes(Solon.encoding()));
            out.write("\n".getBytes(Solon.encoding()));
            out.flush();
        }
    }

    /**
     * 加载 ndjson
     */
    default void loadNdjson(String ndjson) throws IOException {
        loadNdjson(new ByteArrayInputStream(ndjson.getBytes(Solon.encoding())));
    }

    /**
     * 加载 ndjson
     */
    default void loadNdjson(InputStream ins) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(ins))) {
            while (true) {
                String json = reader.readLine();

                if (Utils.isEmpty(json)) {
                    break;
                } else {
                    addMessage(ChatMessage.fromJson(json));
                }
            }
        }
    }
}
相关推荐
FQNmxDG4S2 小时前
Java多线程编程:Thread与Runnable的并发控制
java·开发语言
冬奇Lab2 小时前
RAG 系列(五):Embedding 模型——语义理解的核心
人工智能·llm·aigc
虹科网络安全3 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
哥布林学者3 小时前
深度学习进阶(十五)通道注意力 SE
机器学习·ai
axng pmje3 小时前
Java语法进阶
java·开发语言·jvm
rKWP8gKv73 小时前
Java微服务性能监控:Prometheus与Grafana集成方案
java·微服务·prometheus
老前端的功夫3 小时前
【Java从入门到入土】28:Stream API:告别for循环的新时代
java·开发语言·python
qq_435287923 小时前
第9章 夸父逐日与后羿射日:死循环与进程终止?十个太阳同时值班的并行冲突
java·开发语言·git·死循环·进程终止·并行冲突·夸父逐日
小江的记录本3 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
yaoxin5211234 小时前
397. Java 文件操作基础 - 创建常规文件与临时文件
java·开发语言·python