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

AI实用特性

1、自定义日志

1.1、自定义日志 Advisor

Spring ‏AI 的 Advisor ؜可以理解为拦截器,可以对​调用 AI 的请求进行增强‌,比如调用 AI 前鉴权、‏调用 AI 后记录日志。

例如,官方的 Advisor 中日志记录是logger.debug

private AdvisedRequest before(AdvisedRequest request) {logger.debug("request: {}", this.requestToString.apply(request));return request;}

这是 DEBUG 级别日志,平时开发调试用的。

默认情况下,生产环境很多框架会把 debug 级别给关了,目的是减少日志量和性能消耗。

如果想查看 DEBUG 级别日志,需要在 yml 配置中修改配‏置文件来指定特定文؜件的输出级别

logging:level:org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor: debug

但如果为了؜更灵活地打印指定的​日志,可以自己实现‌一个日志 Adv‏isor。

自定义日志可以借鉴内置的 SimpleLoggerAdvisor 源码,重点实现两个方法:CallAroundAdvisorStreamAroundAdvisor

  • CallAroundAdvisor:用于处理同步请求和响应(非流式)
  • StreamAroundAdvisor:用于处理流式请求和响应
@Slf4j
public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {public String getName() {return this.getClass().getSimpleName();}public int getOrder() {return 0;}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) {// 1. 处理请求(前置处理)advisedRequest = this.before(advisedRequest);// 2. 调用链中的下一个AdvisorAdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);// 3. 处理响应(后置处理)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.2、自定义 Re-Reading Advisor

通过 Re-Reading(Re2) 的技巧,能让大语言模型(像我这种)推理能力更强。

  • 当你给模型一个问题(Input_Query)的时候,不是只让它读一遍,而是让它“再读一遍”——在输入里把问题重复两次,比如:

{Input_Query}
Read the question again: {Input_Query}

  • 这个“再读一遍”的操作能帮模型更好地理解问题细节,提高回答时的逻辑推理和准确性。

打个比方,就是你自己做题时,有时候第一遍看题可能没全理解,读第二遍细品,答案就更靠谱了。

💡总结:Re-Reading 就是让模型把问题读两遍,从而加强思考,提升推理水平

官网代码如下👇

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();}
}

①复制原请求的用户参数到新 Map -> ②把用户输入的文本(问题)存入新参数 “re2_input_query” -> ③用模板写新的 prompt,重复两遍 {re2_input_query} -> ④构建新的请求对象,带上新的 prompt 和参数

在这里插入图片描述
弊端:让用户输入问题输入两遍,Token 加倍,成本加倍。

最佳实践

  1. 保持单‏一职责:每个 Ad؜visor 应专注​于一项特定任务
  2. 注意执行顺序:合理设置getOrder()值确保 Advisor 按正确顺序执行
  3. 同时支‏持流式和非流式:尽؜可能同时实现两种接​口以提高灵活性
  4. 高效处理请求:避免在 Advisor 中执行耗时操作
  5. 测试边‏界情况:确保 Ad؜visor 能够优​雅处理异常和边界情‌况
  6. 对于需‏要更复杂处理的流式؜场景,可以使用 R​eactor 的操‌作符:

还可以使用 adviseContext 在 Advisor 链中共享状态:比如我想在自定义日志拦截器(MyLoggerAdvisor )中看见Re-Reading拦截器 (ReReadingAdvisor )的信息,那么我可以在 ReReadingAdvisor 中加入如下代码:

// 更新上下文
advisedRequest = advisedRequest.updateContext(context -> {context.put("key", "value");return context;
});

可以理解为 ThreadLocal ,相当于单次 AI 请求中,定义了一个全局变量,这次请求之后,全局变量就失效了,这个全局变量在这次请求中所有的拦截器都能看见,都能获取到。

同时在 MyLoggerAdvisor 中,写如下代码:

// 读取上下文
Object value = advisedResponse.adviseContext().get("key");

这样 MyLoggerAdvisor 就可以知道 ReReadingAdvisor 输出的信息了。

2、结构化输出

就是用于将大语言模型返回的文本输出转换为结构化数据格式,如 JSON、XML 或 Java 类,这对于需要可靠解析 AI 输出值的下游应用程序非常重要。虽然通过 Prompt 也可以让 AI 自己输出结构化数据,但是这么做不可靠,Spring AI 框架直接提供了这种方式。官方文档

在这里插入图片描述
注意:

StructuredOutputConverter 是一种 “尽力而为” 的工具,用于将模型的输出转换为结构化的结果。但是,不能保证 AI 模型一定会按照要求返回结构化的输出。模型有可能无法正确理解 prompt,或者无法按照预期生成结构化格式的结果。

因此,官方建议你实现一个校验机制,确保模型输出符合预期的格式和数据结构。

结构化输出转换器 StructuredOutputConverter 接口允许开发者获取结构化输出,例如将输出映射到 Java 类或值数组。接口定义如下:

public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {
}

它集成了 2 个关键接口:

  • FormatProvider 接口:提供特定的格式指令给 AI 模型
  • Spring 的 Converter<String, T>接口:负责将模型的文本输出转换为指定的目标类型 T

Sprin‏g AI 提供了多؜种转换器实现,分别​用于将输出转换为不‌同的结构:

  • BeanOutputConverter:用于将输出转换为 Java Bean 对象(基于 ObjectMapper 实现
  • MapOutputConverter:用于将输出转换为 Map 结构
  • ListOutputConverter:用于将输出转换为 List 结构

在这里插入图片描述

原理:

第一步:在调用大模型之前,FormatProvider 为 AI 模型提供特定的格式指令,使其能够生成可以通过 Converter 转换为指定目标类型的文本输出。

转换器的格式指令组件会将类似下面的格式指令附加到提示词中:

Your response should be in JSON format.
The data structure for the JSON should match this Java class: java.util.HashMap
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.

使用 PromptTemplate 将格式指令附加到用户输入的末尾,示例代码如下:

StructuredOutputConverter outputConverter = ...
String userInputTemplate = """... 用户文本输入 ....{format}"""; // 用户输入,包含一个“format”占位符。
Prompt prompt = new Prompt(new PromptTemplate(this.userInputTemplate,Map.of(..., "format", outputConverter.getFormat()) // 用转换器的格式替换“format”占位符).createMessage());

第二步:Converter 负责将模型的输出文本转换为指定类型的实例。

①Bea‏nOutputCo؜nverter 示​例,将 AI 输出‌转换为自定义 Ja‏va 类:

record ActorsFilms(String actor, List<String> movies) {
}ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt().user(u -> u.text("Generate the filmography of 5 movies for {actor}.").param("actor", "Tom Hanks")).call().entity(ActorsFilms.class);

② MapOutputConverter示​例,将 AI 输出‌转换 Map 对象

Map<String, Object> result = ChatClient.create(chatModel).prompt().user(u -> u.text("Provide me a List of {subject}").param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'")).call().entity(new ParameterizedTypeReference<Map<String, Object>>() {});

③ListOutputConverter示​例,将 AI 输出‌转换 List 对象

// 可以转换为对象列表
List<ActorsFilms> actorsFilms = ChatClient.create(chatModel).prompt().user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.").call().entity(new ParameterizedTypeReference<List<ActorsFilms>>() {});

实战

   record  LoveReport(String title, List<String> suggestion){}/*** AI 报告输出(结构化输出)* @param message* @param chatId* @return*/public LoveReport doChatWithReport(String message,String chatId){LoveReport loveReport = chatClient.prompt().system(SYSTEM_PROMPT + "每次对话后都要生成恋爱结果,标题为{用户名}的恋爱报告,内容为建议列表").user(message).advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)).call().entity(LoveReport.class);log.info("loveReport: {}", loveReport);return loveReport;}

编写 test 测试用例

@Testvoid doChatWithReport() {String charId = UUID.randomUUID().toString();// 第一轮String message = "你好,我是程序员于安,我想染另一半(鱼安)更爱我,但我不知道怎么办";LoveApp.LoveReport loveReport = loveApp.doChatWithReport(message, charId);Assertions.assertNotNull(loveReport);}

在这里插入图片描述
我们 debug 一下,看一下给 AI 的提示词
在这里插入图片描述
这段话的核心意思:

我们告诉 AI:

  1. 必须返回 JSON 格式,别返回文字解释。
  2. 不要包含 markdown 代码块,比如不要输出 ```json。
  3. 必须符合你给的 JSON Schema 格式,不可以乱写。
http://www.xdnf.cn/news/979975.html

相关文章:

  • 使用R进行数字信号处理:婴儿哭声分析深度解析
  • Anaconda 迁移搭建完成的 conda 环境到另一台设备
  • 涨薪技术|Docker容器技术之镜像(image)
  • Object.defineProperty()详解
  • React 18 渲染机制优化:解决浏览器卡顿的三种方案
  • AX620Q上模型部署流程
  • Spring Security是如何完成身份认证的?
  • BUG调试案例十四:TL431/TL432电路发热问题案例
  • Python训练营打卡DAY51
  • 机器学习核心概念速览
  • 基于ElasticSearch的法律法规检索系统架构实践
  • livetalking实时数字人多并发
  • uni-app项目实战笔记1--创建项目和实现首页轮播图功能
  • 告别excel:AI 驱动的数据分析指南
  • elementui使用Layout布局-对齐方式
  • input+disabled/readonly问题
  • Vue3 + TypeScript + Element Plus 表格行按钮不触发 row-click 事件、不触发勾选行,只执行按钮的 click 事件
  • Explore Image Deblurring via Encoded Blur Kernel Space论文阅读
  • 时序数据库IoTDB数据模型建模实例详解
  • Jmeter中变量如何使用?
  • MySQL 三表 JOIN 执行机制深度解析
  • 基础数论一一同余定理
  • Qt 动态插件系统QMetaObject::invokeMethod
  • 【docker】docker registry搭建私有镜像仓库
  • 开源 java android app 开发(十二)封库.aar
  • SD-WAN 技术如何助力工业物联网(IIoT)数据传输?深度解析传统方案对比与应用实践
  • Chrome 优质插件计划
  • 智慧农业物联网实训中心建设方案
  • 趋境科技英特尔生态沙龙举办,打通大模型私有化“最后一公里”
  • 当简约美学融入小程序 UI 设计:开启高效交互新篇