深入解析 Spring AI 系列:解析返回参数处理
关于普通聊天对接,目前已经完成了大部分讲解,剩下的就是最后一步,今天我们将重点讨论在返回参数时需要注意的几个关键点。为了更好地说明这些注意事项,我们仍然以OpenAI接口为例,逐步讲解相关的代码实现,帮助大家更清楚地理解这一部分的细节。
接下来,我们就直接看一下这一部分代码,分析其中的注意事项。

其实,对于这部分代码来说,核心逻辑的重点在于将大模型返回的内容封装到 response 中,从而完成对接工作。然而,值得注意的是,为什么代码中还要额外获取并处理一些看似并未直接使用的值,例如 metadata、rateLimit、usage 等?实际上,这并非多余的操作。这些信息虽然在当前版本的代码中并没有立即被使用,但它们的存在是为了为后续可能的需求变化做准备。
如果我们仅仅是为了简单地将获取到的返回内容输出给用户,其实是可以省去这些额外步骤的,也不需要做如此复杂的处理。如图所示:

这是Minimax算法的处理方式,相较于OpenAI的处理方式,这里采用的方法显得更加简洁和直观。因此,这部分代码的实现较为简单,理解后可以快速跳过,不必深入细节。在实际应用中,只需要在有用的地方将相应的信息提取并填充到response对象中即可。
如果某些信息在当前上下文中并未使用到,那么就不需要进行封装或处理,避免不必要的复杂度。
ChatGenerationMetadata
buildGeneration的核心逻辑其实就是在寻找需要的工具调用和结束信息,核心代码如下所示,看一下:
private Generation buildGeneration(Choice choice, Map<String, Object> metadata, ChatCompletionRequest request) {
List<AssistantMessage.ToolCall> toolCalls = choice.message().toolCalls() == null ? List.of()
: choice.message()
.toolCalls()
.stream()
.map(toolCall -> new AssistantMessage.ToolCall(toolCall.id(), "function",
toolCall.function().name(), toolCall.function().arguments()))
.toList();
String finishReason = (choice.finishReason() != null ? choice.finishReason().name() : "");
var generationMetadataBuilder = ChatGenerationMetadata.builder().finishReason(finishReason);
List<Media> media = new ArrayList<>();
String textContent = choice.message().content();
var audioOutput = choice.message().audioOutput();
if (audioOutput != null) {
String mimeType = String.format("audio/%s", request.audioParameters().format().name().toLowerCase());
byte[] audioData = Base64.getDecoder().decode(audioOutput.data());
Resource resource = new ByteArrayResource(audioData);
Media.builder().mimeType(MimeTypeUtils.parseMimeType(mimeType)).data(resource).id(audioOutput.id()).build();
media.add(Media.builder()
.mimeType(MimeTypeUtils.parseMimeType(mimeType))
.data(resource)
.id(audioOutput.id())
.build());
if (!StringUtils.hasText(textContent)) {
textContent = audioOutput.transcript();
}
generationMetadataBuilder.metadata("audioId", audioOutput.id());
generationMetadataBuilder.metadata("audioExpiresAt", audioOutput.expiresAt());
}
var assistantMessage = new AssistantMessage(textContent, metadata, toolCalls, media);
return new Generation(assistantMessage, generationMetadataBuilder.build());
}
无论在何种情况下省略逻辑,toolCalls和finishReason这两个要素是必须要被识别和处理的。除非某个大型模型不支持toolCalls功能,否则我们在实现时不应忽略它们。实际上,绝大多数主流的大型模型都具备这部分功能,因为如果一个模型缺失了toolCalls功能,这意味着它无法支持Agent的开发和运行,进而就失去了介入Spring AI生态系统的基本目的。总之,确保对这两个关键要素的识别,对于实现模型的有效性和功能完整性至关重要。
关于media是因为OpenAI接口是会返回此信息字段,看下接口文档:

除此之外ChatGenerationMetadata目前除了finishReason我找到了使用目的,其他的还未找到用处,ChatGenerationMetadata功能基本如下:
- 可以用来测试
- 可以用来观测
- 目前还没咋用上,先留个心
ChatResponseMetadata
作用也是一样的,仍然是为了观测使用,只不过他封装的信息和上面有一些区别而已。如图所示:

Usage
本质上,这只是一个用于统计token使用情况的信息,功能上并没有特别复杂的内容,理解起来并不难。如果你之前不太了解这部分的细节,可以查看它的核心代码,这将帮助你迅速掌握其工作原理。
需要注意的是,绝大多数大型模型接口都提供了类似的字段,因为在实际应用中,了解token的消耗情况非常重要,毕竟资源的投入(如费用)最终需要与使用效果相对应,这也是模型开发者和使用者关心的重点之一。如图所示:

总结
在这一部分的讲解中,我们详细探讨了返回参数处理的关键要点,特别是如何封装与使用相关的字段。尽管某些信息(如metadata、rateLimit等)在当前实现中未直接用到,但它们的引入是为了更好地支持未来的扩展和需求变化。
通过对比不同处理方式,我们也看到不同模型接口在设计上的差异。在实际开发过程中,理解这些细节对于保证接口的扩展性和系统的稳定性至关重要。
我是努力的小雨,一个正经的 Java 东北服务端开发,整天琢磨着 AI 技术这块儿的奥秘。特爱跟人交流技术,喜欢把自己的心得和大家分享。还当上了腾讯云创作之星,阿里云专家博主,华为云云享专家,掘金优秀作者。各种征文、开源比赛的牌子也拿了。
想把我在技术路上走过的弯路和经验全都分享出来,给你们的学习和成长带来点启发,帮一把。
欢迎关注努力的小雨,咱一块儿进步!
深入解析 Spring AI 系列:解析返回参数处理的更多相关文章
- (办公)Spring boot(系列)的返回json封装类
package com.imooc.util; import com.fasterxml.jackson.databind.ObjectMapper; /** * 自定义响应数据结构: * 这个类是提 ...
- Spring源码解析系列汇总
相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...
- Spring Boot系列(三):Spring Boot整合Mybatis源码解析
一.Mybatis回顾 1.MyBatis介绍 Mybatis是一个半ORM框架,它使用简单的 XML 或注解用于配置和原始映射,将接口和Java的POJOs(普通的Java 对象)映射成数据库中的记 ...
- Spring Boot系列(四):Spring Boot源码解析
一.自动装配原理 之前博文已经讲过,@SpringBootApplication继承了@EnableAutoConfiguration,该注解导入了AutoConfigurationImport Se ...
- Spring Cloud系列(三):Eureka源码解析之服务端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-ser ...
- Spring Boot 系列教程11-html页面解析-jsoup
需求 需要对一个页面进行数据抓取,并导出doc文档 html解析器 jsoup 可直接解析某个URL地址.HTML文本内容.它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操 ...
- Spring Cloud系列(四):Eureka源码解析之客户端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...
- Spring04——Spring MVC 全解析
前文分别介绍了 Spring IOC 与 Spring AOP 的相关知识,本文将为各位大概带来 Spring MVC 的知识点.关注我的公众号「Java面典」,每天 10:24 和你一起了解更多 J ...
- 你知道Spring是怎么解析配置类的吗?
彻底读懂Spring(二)你知道Spring是怎么解析配置类的吗? 推荐阅读: Spring官网阅读系列 彻底读懂Spring(一)读源码,我们可以从第一行读起 Spring执行流程图如下: 如果图片 ...
- Spring技术内幕——深入解析Spring架构与设计原理(一)IOC实现原理
IOC的基础 下面我们从IOC/AOP开始,它们是Spring平台实现的核心部分:虽然,我们一开始大多只是在这个层面上,做一些配置和外部特性的使用工作,但对这两个核心模块工作原理和运作机制的理解,对深 ...
随机推荐
- 内网IP地址实现HTTPS加密访问教程
一.前期准备 确定内网IP地址: 确保有一个明确且固定的内网IP地址.动态IP地址可能不适合此场景,因为它们会频繁改变,导致SSL证书失效. 选择SSL证书颁发机构(CA): 选择一个受信任的CA,如 ...
- Redis中常见的数据类型及其应用场景
五种常见数据类型 Redis中的数据类型指的是 value存储的数据类型,key都是以String类型存储的,value根据场景需要,可以以String.List等类型进行存储. 各数据类型介绍: R ...
- nginx之日志处理
日常对于NGINX日志文件的处理 1.将访问日志中爬虫相关请求导出 cat access.log | grep Baiduspider > spider.log
- 《HelloGitHub》第 104 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. github.com/521xueweihan/HelloG ...
- Servlet内存马
emmm.....本篇写的还不是很完善,学着后边的忘着后边的,后续边学边完善吧........ 概述 如果你不了解IDEA调试Tomcat和Tomcat各组件概念可以参考我的博客:JAVA WEB环境 ...
- BitLocker驱动器加锁和解锁
应用场景: 单位配备给你使用的电脑(Win10系统),偶尔也会有其他人使用.你可以设置某一个磁盘为你的私密数据存储空间,只有你输入密码后才能进入磁盘.即使系统重装.硬盘被拆下来挂载到其他机器上,没有密 ...
- Prime2_解法二:openssl解密凭据
Prime2_解法二:openssl解密凭据 本博客提供的所有信息仅供学习和研究目的,旨在提高读者的网络安全意识和技术能力.请在合法合规的前提下使用本文中提供的任何技术.方法或工具.如果您选择使用本博 ...
- 【C#】【平时作业】习题-10-委托
什么是委托? C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针. 委托(Delegate) 是存有对某个方法的引用的一种引用类型变量.引用可在运行时被改变. 委托(Delegat ...
- 【MyBatis】学习笔记10:添加功能获取自增的主键
[Mybatis]学习笔记01:连接数据库,实现增删改 [Mybatis]学习笔记02:实现简单的查 [MyBatis]学习笔记03:配置文件进一步解读(非常重要) [MyBatis]学习笔记04:配 ...
- kubeadm卸载清空k8s环境
#!/bin/bash kubeadm reset -f modprobe -r ipip lsmod rm -rf ~/.kube/ rm -rf /etc/kubernetes/ rm -rf / ...