当前位置: 首页 > news >正文

Spring AI 的特性 及其 最佳实践

1.本文简介

当前文章是基于3分钟 Spring AI 实现对话功能-CSDN博客 这个文章之后的拓展,主要讲解使用SpringAI如何实现 流式输出 自定义 Advisor 实现敏感消息的记录  以及 自定义ChatMemory实现聊天会话 持久化 

零基础的先去阅读该文章3分钟 Spring AI 实现对话功能-CSDN博客,

有基础的同学可以直接食用该文章

2.自定义Advisor

          2.1实现日志记录

   Ai的调用分为阻塞式调用 和 流式调用,所以自定义Advisor 需要实现两个不同的接口,分别是

CallAroundAdvisor 和 StreamAroundAdvisor ,
第一个需要实现的方法为 aroundCall ,第二个为 aroundStream

每当阻塞式调用 就会走aroundCall 方法 流式调用则走 aroundStream 方法

所以我们需要记录日志就需要在两个方法中都进行记录。

package com.example.demo.advisor;import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.ai.chat.model.MessageAggregator;
import reactor.core.publisher.Flux;@Slf4j
public class MyAdvisorLog implements CallAroundAdvisor, StreamAroundAdvisor {public AdvisedRequest before(AdvisedRequest advisedRequest){log.info("MyAdvisorLog before: {}",advisedRequest.userText()); //记录用户输入return advisedRequest;}public  void after(AdvisedResponse advisedResponse){log.info("MyAdvisorLog after: {}",advisedResponse.response().getResult().getOutput().getText()); //打印模型返回结果}@Overridepublic AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {//记录请求输入AdvisedRequest request = before(advisedRequest);AdvisedResponse response = chain.nextAroundCall(request); //调用下一个切面//记录模型返回结果after(response);return response;}@Overridepublic Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {advisedRequest = before(advisedRequest);Flux<AdvisedResponse> advisedResponse = chain.nextAroundStream(advisedRequest);return new MessageAggregator().aggregateAdvisedResponse(advisedResponse, this::after);}@Overridepublic String getName() {return MyAdvisorLog.class.getName();}@Overridepublic int getOrder() {return 0;}
}

并且在初始化大模型时 配置 自定义Advisor

进行测试

日志成功打印

2.2  实现 Re2 提高大模型的推理能力

RE2(Re-Reding)是一种通过结合 检索(Retrieval)机制 提升大模型推理能力的技术。它的核心思想是让模型在生成答案或推理时减少对纯参数化记忆的依赖,增强事实准确性和逻辑推理能力

让大模型对输入进行多次阅读或“重复理解”(类似人类的“重读”行为)

本质上就算你提问一次,但是让大模型对你的提问进行两次阅读,避免阅读出幻觉,就像我们如果做题时,第一次没理解题目意思,再读一遍可能就理解了题目意思。

优点:提升大模型的推理能力

缺点: 输入Token数量翻倍

package com.xiaog.aiapp.advisor;import org.springframework.ai.chat.client.advisor.api.*;
import reactor.core.publisher.Flux;import java.util.HashMap;
import java.util.Map;/*** 自定义 Re2 Advisor* 可提高大型语言模型的推理能力* //弊端:token数量翻倍*/
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {private AdvisedRequest before(AdvisedRequest advisedRequest) {Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());advisedUserParams.put("key1", advisedRequest.userText());return AdvisedRequest.from(advisedRequest).userText("""{key1}Read the question again: {key1}""")  //模板 将输入内容作为参数传入模板 例如 将key1对应的value转为字符串再传入大模型.userParams(advisedUserParams).build();}//    /**
//     * 执行请求前改写prompt
//     * @param advisedRequest
//     * @return
//     */
//    private AdvisedRequest before(AdvisedRequest advisedRequest) {
//
//
//        // 构造"读两遍"的提示词 让模型推理能力更强 //弊端:token数量翻倍
//        String doubleReadPrompt = advisedRequest.userText() +
//                "\nRead the question again: " + advisedRequest.userText();
//
//        return AdvisedRequest.from(advisedRequest)
//                .userText(doubleReadPrompt)
//                .build();
//    }@Overridepublic AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {return chain.nextAroundCall(this.before(advisedRequest));}@Overridepublic Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {return chain.nextAroundStream(this.before(advisedRequest));}@Overridepublic int getOrder() {return 0;}@Overridepublic String getName() {return this.getClass().getSimpleName();}
}

设置到大模型初始化中

Re2并不好演示,大家只需要知道有这个东西就好了,例如用户点击了深度思考,就可以开启RE2。

2.3 实现用户敏感词输入进行报警

比如现在教育部门非常在意在校学生的心理健康问题,学校选择在学校部署一个心理健康小助手大模型,用于解决学生的心理健康问题,当发现有学生输入例如:(紫砂,跳喽)等关键字眼,则进行记录,识别用户身份,向该学生的辅导员进行短信告知,及时发现心理存在问题的学生

代码中获取当前用户调用dochat聊天会话的方法,找到用户的信息和其辅导员信息

    //Chatclien对话public String dochat(String message,String sessionId){//模拟获取当前访问此接口的用户tokenString userToken="user-token";ChatResponse chatResponse = chatClient.prompt().user(message)//传入用户输入信息.advisors(advisorSpec -> advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId)//对话的会话id 用于查看是否是当前上下文.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 3) //检索上下文的长度 如果长度过长所消耗的Token数量会过大//可以自定义参数,会作为参数传递给自定义advisor 比如用户的登陆Token//用于在advisor层面进行学生的信息识别.param("userToken", userToken)).call().chatResponse();log.info("chatResponse: {}", chatResponse.getResult().getOutput().getText());//输出模型返回的结果return chatResponse.getResult().getOutput().getText();}

3.自定义Chatmemoty

        3.1基于文件存储保存 用户聊天信息

这里使用到了

Kryo

需要引入Maven

<!--支持文件会话记忆的序列化会话-->
<dependency><groupId>com.esotericsoftware</groupId><artifactId>kryo</artifactId><version>5.6.2</version>
</dependency>

编写自定义

FileBasedChatMemory
package com.xiaog.aiapp.chatmemory;import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/*** //自定义对话存储* 对话记忆存储 到文件中*/public class FileBasedChatMemory  implements ChatMemory {// 文件路径private final String BASE_PATH ;// kryo序列化private static final Kryo kryo = new Kryo();static{//允许序列化未预先注册的类kryo.setRegistrationRequired(false);//设置实例化策略kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());}//构造对象时,指定存储地址public FileBasedChatMemory(String basePath) {this.BASE_PATH = basePath;File file = new File(BASE_PATH);//如果文件不存在,则创建文件if (!file.exists()) {file.mkdirs();}}@Overridepublic void add(String conversationId, List<Message> messages) {//获取全部消息List<Message> allMessages = getOrCreateConversation(conversationId);//将此条消息添加到全部会话中allMessages.addAll(messages);//保存会话saveConversation(conversationId, allMessages);}@Override  //lastN = 5 表示获取最近的 5 条对话消息public List<Message> get(String conversationId, int lastN) { //lastN//获取全部消息List<Message> allMessages = getOrCreateConversation(conversationId);//获取最新N条消息List<Message> collect = allMessages.stream().skip(Math.max(0, allMessages.size() - lastN)) //需要跳过的消息数量=(总消息数 - 需要最新的消息数量).collect(Collectors.toList());return collect;}@Overridepublic void clear(String conversationId) {//删除会话文件File file = new File(conversationId);if (file.exists()) {file.delete();}}/*** 创建 或 读取会话消息*/private List<Message> getOrCreateConversation(String conversationId) {File file = getConversationFile(conversationId);List<Message> messages = new ArrayList<>();if (file.exists()) { //如果文件存在,则读取文件//创建输入流try (Input input = new Input(new FileInputStream(file))) {//反序列化messages = kryo.readObject(input, ArrayList.class);} catch (IOException e) {e.printStackTrace();}}return messages;}/*** 保存会话信息*/private void saveConversation(String conversationId, List<Message> messages) {//获取到文件路径File file = getConversationFile(conversationId);//获取到输出流try (Output output = new Output(new FileOutputStream(file))) {//将信息写到文件中(这是覆写操作)kryo.writeObject(output, messages);} catch (IOException e) {e.printStackTrace();}}/*** 每个文件单独保存* conversationId 是Srping AI框架生成的*/private File getConversationFile(String conversationId) {return new File(BASE_PATH, conversationId + ".kryo");}}

        修改初始化大模型内容

    //构造函数 初始化大模型public LoveApp(ChatModel dashscopeChatModel) { // 构造函数 初始化时会注入大模型,根据名称注入 dashscopeChatModel是阿里灵积// 初始化基于内存的对话记忆
//        ChatMemory chatMemory = new InMemoryChatMemory();String basePath = System.getProperty("user.home")+ "/.tmp/chat-memory";System.out.println("basePath:"+basePath);//初始化基于文件的对话记忆ChatMemory chatMemory = new FileBasedChatMemory(basePath);chatClient = ChatClient.builder(dashscopeChatModel) // 创建基于(某个chatModel)大模型的ChatClient.defaultSystem(SYSTEM_PROMPT) // 设置默认系统提示词.defaultAdvisors(  // 设置默认的Advisornew MessageChatMemoryAdvisor(chatMemory) // 设置基于内存的对话记忆的Advisor,new MyLogAdvisor() // 设置日志Advisor,new ReReadingAdvisor()).build();//构建返回client}

将会话内存存储修改为文件存储

http://www.dtcms.com/a/329660.html

相关文章:

  • CompletableFuture介绍及使用方式
  • 天猫商品评论API:获取商品热门评价与最新评价
  • Jmeter TPS与QPS
  • Ant Design 的 `Image` 组件,通过 `preview.src` 加载本地图片文件
  • Dockerhub 代理设置
  • 破解测试数据困境:5招兼顾安全与真实性
  • Nature Communications 西湖大学姜汉卿教授:弹电磁驱动新范式--赋能昆虫级软体机器人的肌肉仿生策略
  • HTML第三次作业
  • Redis ubuntu下载Redis的C++客户端
  • Ubuntu 20.04 虚拟机安装完整教程:从 VMware 到 VMware Tools
  • 如何在 Ubuntu 24.04 LTS Noble Linux 上安装 FileZilla Server
  • Python【算法中心 03】Docker部署Django搭建的Python应用流程实例(Docker离线安装配置+Django项目Docker部署)
  • java中list的api详细使用
  • MySQL宝典
  • 【Golang】 Context.WithCancel 全面解析与实战指南
  • 使用内联汇编获取在线OJ平台CPU的信息
  • 玩转Docker | 使用Docker部署WordPress网站服务
  • 基本计算器 II
  • 回归分析预测原神深渊血量
  • 【金仓数据库产品体验官】_从实践看金仓数据库与 MySQL 的兼容性
  • Windows系统设置内外网同时访问(小白友好版)
  • Docker部署 Neo4j 及集成 APOC 插件:安装与配置完整指南(docker-compose)
  • 【Android】RecyclerView多布局展示案例
  • Kubernetes(K8S)中,kubectl describe node与kubectl top pod命令显示POD资源的核心区别
  • C语言:队列的实现和剖析
  • Spring Boot 整合 Thymeleaf 模板引擎:从零开始的完整指南
  • 攒钱学概论:5、创业术
  • Gemma 3 报告中的蒸馏
  • PHP imagick扩展安装以及应用
  • 16_基于深度学习的道路缺陷检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)