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

SpringAI特性

一、SpringAI 顾问(Advisors)

Spring AI 使用 Advisors机制来增强 AI 的能力,可以理解为一系列可插拔的拦截器,在调用 AI 前和调用 AI 后可以执行一些额外的操作,比如:

  • 前置增强:调用 AI 前改写一下 Prompt 提示词、检查一下提示词是否安全
  • 后置增强:调用 AI 后记录一下日志、处理一下返回的结果

在这里插入图片描述

解释上图的执行流程:

  1. Spring AI 框架从用户的 Prompt 创建一个 AdvisedRequest,同时创建一个空的 AdvisorContext 对象,用于传递信息。
  2. 链中的每个 advisor 处理这个请求,可能会对其进行修改。或者,它也可以选择不调用下一个实体来阻止请求继续传递,这时该 advisor 负责填充响应内容。
  3. 由框架提供的最终 advisor 将请求发送给聊天模型 ChatModel。
  4. 聊天模型的响应随后通过 advisor 链传回,并被转换为 AdvisedResponse。后者包含了共享的 AdvisorContext 实例。
  5. 每个 advisor 都可以处理或修改这个响应。
  6. 最终的 AdvisedResponse 通过提取 ChatCompletion 返回给客户端。

自定义实现拦截器:

  • CallAroundAdvisor:用于处理同步请求和响应(非流式)
  • StreamAroundAdvisor:用于处理流式请求和响应
    由于源码中实现类很多、所以我们可以自定义拦截器
    在这里插入图片描述
package com.MrSun.mrsun_agent.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;/*** 自定义日志 Advisor* 打印 info 级别日志、只输出单次用户提示词和 AI 回复的文本*/
@Slf4j
public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {@Overridepublic String getName() {return this.getClass().getSimpleName();}@Overridepublic int getOrder() {return 0;}/**** @param request* @return*/private AdvisedRequest before(AdvisedRequest request) {//  日志输出log.info("AI Request: {}", request.userText());return request;}private void observeAfter(AdvisedResponse advisedResponse) {log.info("AI Response: {}", advisedResponse.response().getResult().getOutput().getText());}public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {// 先执行before 处理请求,然后在进行处理响应advisedRequest = this.before(advisedRequest);AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);this.observeAfter(advisedResponse);return advisedResponse;}public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {advisedRequest = this.before(advisedRequest);Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);// 消息聚合器 等到一起在进行响应return (new MessageAggregator()).aggregateAdvisedResponse(advisedResponses, this::observeAfter);}
}

1、Chat Memory Advisor

前面我们提到了,想要实现对话记忆功能,可以使用 Spring AI 的 ChatMemoryAdvisor,它主要有几种内置的实现方式:

  • MessageChatMemoryAdvisor:从记忆中检索历史对话,并将其作为消息集合添加到提示词中
  • PromptChatMemoryAdvisor:从记忆中检索历史对话,并将其添加到提示词的系统文本中
  • VectorStoreChatMemoryAdvisor:可以用向量数据库来存储检索历史对话

1)MessageChatMemoryAdvisor 将对话历史作为一系列独立的消息添加到提示中,保留原始对话的完整结构,包括每条消息的角色标识(用户、助手、系统)。

[{"role": "user", "content": "你好"},{"role": "assistant", "content": "你好!有什么我能帮助你的吗?"},{"role": "user", "content": "讲个笑话"}
]

2)PromptChatMemoryAdvisor 将对话历史添加到提示词的系统文本部分,因此可能会失去原始的消息边界。

以下是之前的对话历史:
用户: 你好
助手: 你好!有什么我能帮助你的吗?
用户: 讲个笑话
现在请继续回答用户的问题。

一般情况下,更建议使用 MessageChatMemoryAdvisor。更符合大多数现代 LLM 的对话模型设计,能更好地保持上下文连贯性。

2、Chat Memory

上述 ChatMemoryAdvisor 都依赖 [Chat Memory]进行构造,Chat Memory 负责历史对话的存储,定义了保存消息、查询消息、清空消息历史的方法。其实就是基本的增删改查

在这里插入图片描述
Spring AI 内置了几种 Chat Memory,可以将对话保存到不同的数据源中,比如:

  • InMemoryChatMemory:内存存储
  • CassandraChatMemory:在 Cassandra 中带有过期时间的持久化存储
  • Neo4jChatMemory:在 Neo4j 中没有过期时间限制的持久化存储
  • JdbcChatMemory:在 JDBC 中没有过期时间限制的持久化存储

实现对话记忆

1)首先初始化 ChatClient 对象。使用 Spring 的构造器注入方式来注入阿里大模型 dashscopeChatModel 对象,并使用该对象来初始化 ChatClient。初始化时指定默认的系统 Prompt 和基于内存的对话记忆 Advisor。代码如下:

@Component
@Slf4j
public class LoveApp {private final ChatClient chatClient;private static final String SYSTEM_PROMPT = "扮演深耕恋爱心理领域的专家。开场向用户表明身份,告知用户可倾诉恋爱难题。" +"围绕单身、恋爱、已婚三种状态提问:单身状态询问社交圈拓展及追求心仪对象的困扰;" +"恋爱状态询问沟通、习惯差异引发的矛盾;已婚状态询问家庭责任与亲属关系处理的问题。" +"引导用户详述事情经过、对方反应及自身想法,以便给出专属解决方案。";public LoveApp(ChatModel dashscopeChatModel) {// 初始化基于内存的对话记忆ChatMemory chatMemory = new InMemoryChatMemory();chatClient = ChatClient.builder(dashscopeChatModel).defaultSystem(SYSTEM_PROMPT).defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)).build();}
}

2)编写对话方法。调用 chatClient 对象,传入用户 Prompt,并且给 advisor 指定对话 id 和对话记忆大小。代码如下:

public String doChat(String message, String chatId) {ChatResponse response = chatClient.prompt().user(message).advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)).call().chatResponse();String content = response.getResult().getOutput().getText();log.info("content: {}", content);return content;
}

3)编写单元测试,测试多轮对话:

    @Testvoid testChat() {//生成一个绘画IDString chatId= UUID.randomUUID().toString();//第一轮String message="你好,我是南瓜";String answer= loveApp.doChat(message,chatId);//第二轮message="我想让另一半(松鼠)更喜欢我";answer= loveApp.doChat(message,chatId);Assertions.assertNotNull(answer);//第三轮message="我的另一半是谁了。我忘记了,你帮我回忆一下";answer= loveApp.doChat(message,chatId);Assertions.assertNotNull(answer);}

效果如下:
在这里插入图片描述
这样就可以实现多轮对话功能。

3、Re-Reading Advisor

实现一个 Re-Reading(重读)Advisor,又称 Re2。该技术通过让模型重新阅读问题来提高推理能力,有文献 来印证它的效果。
注意:虽然该技术可提高大语言模型的推理能力,不过成本会加倍!
Re2 的实现原理很简单,改写用户 Prompt 为下列格式,也就是让 AI 重复阅读用户的输入:

/*** 自定义 Re2 Advisor* 可提高大型语言模型的推理能力*/
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {private AdvisedRequest before(AdvisedRequest advisedRequest) {Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());advisedUserParams.put("re2_input_query", advisedRequest.userText());return AdvisedRequest.from(advisedRequest).userText("""{re2_input_query}Read the question again: {re2_input_query}""").userParams(advisedUserParams).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();}
}

以上就是SpringAI的一些特性。

相关文章:

  • STC32G12K128-旋转编码器-软件去抖
  • 常用电阻阻值表E24(5%)和E96(1%)
  • 面试题:Java集合框架高频面试题总结
  • 【基础】模型上下文协议(Model Context Protocol, MCP)根本原理与工作机制详解
  • canoe的安装总结
  • 我的AD快捷键方案【留存】
  • JAVA多态——向上转型
  • Vmware 最新下载教程和安装教程,外带免下载文件
  • 【软件设计师:多媒体】14.多媒体技术及其应用
  • 【C/C++】C语⾔内存函数
  • 空间复杂度** 与 **所需辅助空间**
  • LVGL源码学习之渲染、更新过程(1)---标记和激活
  • 我国脑机接口市场规模将破38亿元,医疗领域成关键突破口
  • SDC命令详解:使用all_inputs命令进行查询
  • 每天批次导入 100 万对账数据到 MySQL 时出现死锁
  • 一、对linux驱动文件编写时结构认识与记录
  • gpu硬件,gpu驱动,cuda,CUDA Toolkit,cudatoolkit,cudnn,nvcc概念解析
  • 操作系统 第2章节 进程,线程和作业
  • 【PhysUnits】3.3 SI 基础量纲单位(units/base.rs)
  • Leetcode刷题 由浅入深之字符串——541. 反转字符串Ⅱ
  • 游戏论|暴君无道,吊民伐罪——《苏丹的游戏》中的政治
  • 2025柯桥时尚周启幕:国际纺都越来越时尚
  • 壹基金发布2024年度报告,公益项目惠及937万人次
  • 华为鸿蒙电脑正式亮相,应用生态系统能否挑战Windows?
  • 欧盟委员会计划对950亿欧元美国进口产品采取反制措施
  • 一企业采购国产化肥冒充“挪威化肥”:7人被抓获