Spring AI 大模型返回内容格式化源码分析及简单使用

news2024/12/23 10:05:48

为什么需要格式化输出

对于依赖可靠解析输出值的下游应用程序来说,生成结构化输出是LLMs非常重要的能力。开发人员希望快速将 AI 模型的结果转换为数据类型,例如 JSON、XML 或 Java 类,这些数据类型可以传递给其他应用程序函数和方法。

特别是函数调用、智能体等都需要将大模型的输出进行格式化,然后调用外部函数辅助大模型更好的回答提出的问题。

哪些大模型支持结构化输出

并不是所有的大模型都支持格式化输出的,目前知道的支持结构化输出的大模型如下;

  • OpenAI
  • Anthropic Claude 3 【美国初创公司Anthropic发布的大模型】
  • Azure OpenAI 【微软大模型】
  • Mistral AI 【法国2023年成立的年轻人工智能公司发布的大模型】
  • Ollama 【本地运行大模型工具】,放在这里有点不太懂???
  • Vertex AI Gemini 【Google公司的大模型】
  • Bedrock Anthropic 2 【Anthropic公司发布的 Claude 2大模型】
  • Bedrock Anthropic 3 【Anthropic公司发布的 Claude 3大模型】
  • Bedrock Cohere 【初创公司Cohere的大模型】
  • Bedrock Llama 【Meta公司发布的Llama大模型】

后续会继续补充!

处理流程图

image.png Spring AI提供了调用之前和调用之后两个步骤进行处理;

  • 在LLM调用之前,转换器会将格式说明附加到提示词中,为模型生成所需的输出结构提供明确的指导。

  • LLM调用后,转换器获取模型的输出文本,并将其转换为结构化类型的实例。此转换过程涉及分析原始文本输出并将其映射到相应的结构化数据表示形式,例如 JSON、XML 或特定于域的数据结构。

类交互图

image.png

第一步:通过PromptTemplate定义提示词模板,然后通过FormatProvider#getFormat()获取指示大模型输出格式的指令。

第二步:通过ChatModel调用大模型,并返回原始文本输出。

第三步:通过Spring AI提供的Converter<String,T> 转换结构化内容输出。

源码分析

注意,本文分析的是1.0.0-SNAPSHOT版本的,与1.0.0之前的版本相差比较大。

整体架构图

image.png

StructuredOutputConverter接口

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

    /**
     * 该接口废弃,应该使用父接口中的convert方法替代
     * @deprecated Use the {@link #convert(Object)} instead.
     */
    default T parse(@NonNull String source) {
       return this.convert(source);
    }

}

该接口继承两个接口 Converter<String, T>FormatProvider

其中Converter<String, T> springframework core包中的转换器接口。之所以继承该接口,官方文档说保持所有转换器风格的一致性。

FormatProvider 由 Spring AI 提供,主要获取格式化指令内容,指导大模型以什么格式输出内容。

package org.springframework.ai.converter;

public interface FormatProvider {

    /**
     * 返回含有指导大模型返回格式化的指令,大家先这么理解着,后续示例中会使用,让大家更好地理解它
     */
    String getFormat();
}

BeanOutputConverter 源码

BeanOutputConverterStructuredOutputConverter<T> 的唯一子类。具体源码如下【只留了核心代码】;

public class BeanOutputConverter<T> implements StructuredOutputConverter<T> {

    /** The object mapper used for deserialization and other JSON operations. */
    @SuppressWarnings("FieldMayBeFinal")
    private ObjectMapper objectMapper;

    
    /**
     * Parses the given text to transform it to the desired target type.
     * @param text The LLM output in string format.
     * @return The parsed output in the desired target type.
     */
    @Override
    public T convert(@NonNull String text) {
       try {
          if (text.startsWith("```json") && text.endsWith("```")) {
             text = text.substring(7, text.length() - 3);
          }
          return (T) this.objectMapper.readValue(text, this.typeRef);
       }
       catch (JsonProcessingException e) {
          logger.error("Could not parse the given text to the desired target type:" + text + " into " + this.typeRef);
          throw new RuntimeException(e);
       }
    }

    /**
     * Provides the expected format of the response, instructing that it should adhere to
     * the generated JSON schema.
     * @return The instruction format string.
     */
    @Override
    public String getFormat() {
       String template = """
             Your response should be in JSON format.
             Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
             Do not include markdown code blocks in your response.
             Remove the ```json markdown from the output.
             Here is the JSON Schema instance your output must adhere to:
             ```%s```
             """;
       return String.format(template, this.jsonSchema);
    }

}

BeanOutputConverter 使用的方案是 ObjectMapper。 想必大家对 ObjectMapper 都很熟悉了。BeanOutputConverter本身代码也比较简单。

AbstractMessageOutputConverter<T>

AbstractMessageOutputConverter<T> - 使用预定的转换器对大模型输出进行格式化输出。未提供默认 FormatProvider 实现。

package org.springframework.ai.converter;

import org.springframework.messaging.converter.MessageConverter;

/**
 * Abstract {@link StructuredOutputConverter} implementation that uses a pre-configured
 * {@link MessageConverter} to convert the LLM output into the desired type format.
 *
 * @param <T> Specifies the desired response type.
 * @author Mark Pollack
 * @author Christian Tzolov
 */
public abstract class AbstractMessageOutputConverter<T> implements StructuredOutputConverter<T> {
    // 引用springframework-messaging提供的消息转换器
    private MessageConverter messageConverter;

    public AbstractMessageOutputConverter(MessageConverter messageConverter) {
       this.messageConverter = messageConverter;
    }

    public MessageConverter getMessageConverter() {
       return this.messageConverter;
    }

}

MapOutputConverter 源码

public class MapOutputConverter extends AbstractMessageOutputConverter<Map<String, Object>> {

    public MapOutputConverter() {
        // 调用父类构造方法,设置converter。
       super(new MappingJackson2MessageConverter());
    }

    @Override
    public Map<String, Object> convert(@NonNull String text) {
       // 大模型返回的String类型为 ```json xxxx ```
       if (text.startsWith("```json") && text.endsWith("```")) {
          text = text.substring(7, text.length() - 3);
       }
       // 
       Message<?> message = MessageBuilder.withPayload(text.getBytes(StandardCharsets.UTF_8)).build();
       return (Map) this.getMessageConverter().fromMessage(message, HashMap.class);
    }

    // 发送给大模型提示词中的format内容,能够指导大模型按照format的说明进行返回
    @Override
    public String getFormat() {
       String raw = """
             Your response should be in JSON format.
             The data structure for the JSON should match this Java class: %s
             Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
             Remove the ```json markdown from the output.
             """;
       return String.format(raw, HashMap.class.getName());
    }

}

MapOutputConverterAbstractMessageOutputConverter<T> 的唯一的实现类。其内部使用的MessageConverter类型为springframework.messaging包提供的消息转换器 MappingJackson2MessageConverter。使用Jackson 2 JSON库实现消息与JSON格式之间相互转换。

Spring 还提供了其它消息类型转换器,大大简化消息的读取和写入。

  • MappingJacksonMessageConverter:使用Jackson JSON库实现消息与JSON格式之间的相互转换
  • MarshallingMessageConverter:使用JAXB库实现消息与XML格式之间的相互转换
  • SimpleMessageConverter:实现String与TextMessage之间的相互转换,字节数组与Bytes Message之间的相互转换,Map与MapMessage之间的相互转换 以及Serializable对象与ObjectMessage之间的相互转换。

这里不再详细介绍MessageConveter的实现,如果大家有兴趣自行研究其实现原理。

AbstractConversionServiceOutputConverter 源码

public abstract class AbstractConversionServiceOutputConverter<T> implements StructuredOutputConverter<T> {

    private final DefaultConversionService conversionService;

    public AbstractConversionServiceOutputConverter(DefaultConversionService conversionService) {
       this.conversionService = conversionService;
    }

    public DefaultConversionService getConversionService() {
       return this.conversionService;
    }

}

AbstractConversionServiceOutputConverter 实现原理与 AbstractMessageOutputConverter<T> 不一样,AbstractConversionServiceOutputConverter 使用的是springframework.core模块提供的 ConversionService 其提供了一种机制来执行类型之间的转换。默认使用 DefaultConversionService,内部提供了许多内置类型转换的支持。

ConversionService 允许开发者定义自己的转换逻辑,并注册到服务中,从而可以在运行时动态地转换对象。这种机制在数据绑定、服务层方法参数转换以及热河需要类型转换的场景中都非常有用。

ConversionService 在SpringMVC中也扮演重要的角色,它用于将请求参数绑定到控制器方法的参数上时进行类型转换。

ListOutputConverter 源码

ListOutputConverterAbstractConversionServiceOutputConverter<T>唯一实现类。

public class ListOutputConverter extends AbstractConversionServiceOutputConverter<List<String>> {

    public ListOutputConverter(DefaultConversionService defaultConversionService) {
       super(defaultConversionService);
    }

    @Override
    public String getFormat() {
       return """
             Your response should be a list of comma separated values
             eg: `foo, bar, baz`
             """;
    }

    @Override
    public List<String> convert(@NonNull String text) {
       return this.getConversionService().convert(text, List.class);
    }

}

实现非常简单,也没有太多需要说明的。

转换器实现原理

转换器实现方案
BeanOutputConverter底层使用 ObjectMapper 实现转换
ListOutputConverter底层使用 springframework.core 提供的 ConversionService 实现的
MapOutputConverter底层使用 springframework.messaging 模块提供的消息转换器实现的

三种类型的转换,使用了三种实现方案

示例

在 [# 05. Spring AI 提示词模版源码分析及简单的使用] 基础上,我们返回一个电影对象。

定义Model

package com.ivy.model;

/**
 * 电影返回对象
 *
 * @param director
 * @param filmName
 * @param publishedDate
 * @param description
 */
public record Film(String director, String filmName, String publishedDate, String description) {
}

BeanOutputConverter示例

package com.ivy.controller;

import com.ivy.model.Film;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.StructuredOutputConverter;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class StructuredOutputController {
    @Resource
    private OpenAiChatModel openAiChatModel;

    @GetMapping("/bean")
    public Film structuredOutput(String director) {
        // 定义提示词模版
        // 其中 format指定输出的格式
        final String template = """
                        请问{director}导演最受欢迎的电影是什么?哪年发行的,电影讲述的什么内容?
                        {format}
                """;
        // 定义结构化输出转化器, 生成Bean
        StructuredOutputConverter<Film> structured = new BeanOutputConverter<>(Film.class);
        // 生成提示词对象
        PromptTemplate promptTemplate = new PromptTemplate(template);
        Prompt prompt = promptTemplate.create(Map.of("director", director, "format", structured.getFormat()));

        ChatClient chatClient = ChatClient.builder(openAiChatModel)
                .build();
        String content = chatClient.prompt(prompt).call().content();
        // 转换
        return structured.convert(content);
    }


}

测试结果

image.png

另外一种实现方式

@GetMapping("/bean2")
public Film structuredOutput2(String director) {
    return ChatClient.create(openAiChatModel)
            .prompt()
            .user(u -> u.text("""
                            请问{director}导演最受欢迎的电影是什么?哪年发行的,电影讲述的什么内容
                    """).params(Map.of("director", director))
            ).call()
            .entity(Film.class);

}

image.png

putConverterMapOutputConverter` 使用大家可以参考github上的代码,文章篇幅原因不在列举。

可能大家都想学习AI大模型技术,也想通过这项技能真正达到升职加薪,就业或是副业的目的,但是不知道该如何开始学习,因为网上的资料太多太杂乱了,如果不能系统的学习就相当于是白学。为了让大家少走弯路,少碰壁,这里我直接把全套AI技术和大模型入门资料、操作变现玩法都打包整理好,希望能够真正帮助到大家。

👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
在这里插入图片描述

👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
在这里插入图片描述

在这里插入图片描述

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1814887.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据分享】中国价格统计年鉴(2013-2022)

大家好&#xff01;今天我要向大家介绍一份重要的中国价格统计数据资源——《中国价格统计年鉴》。这份年鉴涵盖了从2013年到2022年中国价格统计全面数据&#xff0c;并提供限时免费下载。&#xff08;无需分享朋友圈即可获取&#xff09; 数据介绍 在过去的十年里&#xff0…

解决While loop问题 - Python

当我们在使用 while 循环时&#xff0c;需要确保循环的终止条件最终会被满足&#xff0c;否则循环将会无限执行下去。通常情况下&#xff0c;我们可以在循环内部修改循环控制变量&#xff0c;使得终止条件得以满足。 1、问题背景 一位开发者在使用 Python 开发一个基于文本的游…

NI USB-6009 DAQ采集卡拆解

所需设备&#xff1a; 1、NI USB-6009采集卡&#xff1b; 2、逻辑分析仪&#xff1b; NI USB-6009采集卡全貌&#xff1a; 性能参数&#xff1a; 内部照片&#xff1a; ADC芯片指标&#xff1a; 接线图&#xff1a; 差分模式采样&#xff1a; 采集过程中的SPI总线数据监控&a…

Laravel 6 - 第十九章 模型文件

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

不谈AI能替代什么,就聊能帮我们干活的AI客服机器人!

最近在直播群里有老板在找客服外包&#xff0c;因为客服压力大&#xff0c;接不过来了&#xff0c;想找找外包支援一下。据了解电商客服除了压力大&#xff0c;还会遇到过量的重复劳动&#xff0c;比如我们问一个产品&#xff0c;同一天可能就有几百上千客户问同样的问题&#…

如何把视频的声音转换成音频?干货分享

在数字时代&#xff0c;我们常常需要处理视频文件&#xff0c;而有时候我们只对视频中的音频部分感兴趣。无论是提取影片中的音乐、制作声音效果&#xff0c;还是其他音频处理需求&#xff0c;将视频的声音转换成音频是一个常见而有用的技能。如何把视频的声音转换成音频&#…

css实现优惠券样式

实现优惠券效果&#xff1a; 实现思路&#xff1a; 需要三个盒子元素&#xff0c;使用 css 剪裁&#xff0c;利用 ellipse 属性&#xff0c;将两个盒子分别裁剪成两个半圆&#xff0c;位置固定在另一个盒子元素左右两边适当位置上。为另一个盒子设置想要的样式&#xff0c;圆角…

Tailwind CSS 实战指南:快速构建响应式网页设计

title: Tailwind CSS 实战指南&#xff1a;快速构建响应式网页设计 date: 2024/6/12 updated: 2024/6/12 author: cmdragon excerpt: 这篇文章介绍了Tailwind CSS框架的特点与优势&#xff0c;包括其作为实用性的CSS框架如何通过预设的样式类实现快速布局和设计&#xff0c;…

jeecgboot右上角用户重置密码权限按钮配置不生效问题解决

选了菜单权限用户重置密码权限按钮还是不生效&#xff0c;使用这个缓存读取的是下述redis文件中&#xff0c;用户退出了账户&#xff0c;重新登陆也只会刷新token但是并不会刷新这这个配置&#xff0c;所以需要手动清除redis缓存。 这个问题我也是看了很久才发现&#xff0c;如…

新能源车用驱动器 电机电驱

硕博电子的电机电驱是以一体化动力总成为设计理念&#xff0c;整合电控、电机核心模块&#xff0c;推出的电机电控一体化动力总成。电机电控动力总成采用矢量控制算法和CAN总线通信技术&#xff0c;体积小、效率高、免维护、电磁兼容性强、方便调试&#xff0c;提高了系统的可靠…

【源码】16国语言交易所源码/币币交易+期权交易+秒合约交易+永续合约+交割合约+新币申购+投资理财/手机端uniapp纯源码+PC纯源码+后端PHP

测试环境&#xff1a;Linux系统CentOS7.6、宝塔面板、Nginx、PHP7.3、MySQL5.6&#xff0c;根目录public&#xff0c;伪静态laravel5&#xff0c;开启ssl证书 语言&#xff1a;16种&#xff0c;看图 这套带前端uniapp纯源码&#xff0c;手机端和pc端都有纯源码&#xff0c;后…

指定cuda版本的torch包安装

文章目录 1.查看自己电脑的cuda版本2.确定安装torch的conda指令2.1进入网站[cuda对应的torch版本](https://pytorch.org/get-started/previous-versions/) 3.检验torch的cuda版本是否可用 1.查看自己电脑的cuda版本 winr输入cmd回车进行电脑终端界面 输入nvidia-smi指令 nvid…

【CT】LeetCode手撕—53. 最大子数组和

目录 题目1-思路2- 实现⭐53. 最大子数组和——题解思路 3- ACM 实现 题目 原题连接&#xff1a;53. 最大子数组和 1-思路 动规五部曲 1. 定义 dp 数组 dp[i] 含义为&#xff1a;下标为 i 的数组的最大子数组和 2. 递推公式 因为所求的是最大子数组的和&#xff0c;即当前 n…

转型AI产品经理(8):“习惯形成模型”如何应用在Chatbot产品中

习惯形成模型是心理学中用来解释习惯如何产生、发展以及如何被改变的理论框架。它通常包含以下几个关键阶段&#xff1a; 1. 触发 习惯循环的开始是一个触发因素&#xff0c;它可以是外部的&#xff08;如时间、地点、情绪状态、特定的人或物&#xff09;或内部的&#xff08…

红海云入选《2024中国数据智能产业图谱1.0》

近日&#xff0c;国内知名大数据产业创新服务媒体数据猿携手上海大数据联盟重磅发布了《2024中国数据智能产业图谱1.0》。红海云凭借在人力资源数字化应用领域的卓越产品创新与服务&#xff0c;成功入选图谱「 企业应用-人力资源」板块。 《2024中国数据智能产业图谱1.0》由数…

深度学习(一)——使用Python读取图片

一、Python学习两大道具 1. dir()工具 作用&#xff1a;支持打开package&#xff0c;看到里面的工具函数 示例&#xff1a; (1) 输出torch库包含的函数 dir(torch)(2) 输出torch.AVG函数中的参数 dir(torch.AVG)2. help()工具 作用&#xff1a;说明书&#xff0c;查看库中…

代码随想录算法训练营第35天|● 1005.K次取反后最大化的数组和 ● 134. 加油站● 135. 分发糖果

K次取反后最大化的数组 1005. K 次取反后最大化的数组和 - 力扣&#xff08;LeetCode&#xff09; 本题首先想到尽可能将负的数变成正数&#xff0c;这样才能得到最大和,将数组进行按绝对值大小进行降序排序&#xff0c;若遇到负数将其取反后k--&#xff0c;若后面大于0 &…

7.枚举和模式匹配

一、enum枚举 1.1 定义枚举类型和对应的数据 //定义枚举 #[derive(Debug)] enum IpAddrKind{IPv4,IPv6, }struct Ipaddr{kind: IpAddrKind, //设置kind为IpAddrKind的枚举类型address: String, }fn route(ip_addr: &Ipaddr){println!("ip_type {:#?}", ip_a…

arxiv提交报错解决指南

- 编译时无错误 - 所有文件和图片文件都在同一目录下 - 生成.bbl文件 overleaf将参考文献格式bib转bbl&#xff08;bibitem&#xff09;_overleaf bbl文件-CSDN博客 - .tex文件、.bib文件、.bbl文件 的文件名要一致&#xff0c;修改.bib文件名记得在.tex文件中修改bibliograp…

成都产业园运营,多样活动助力生态建设

树莓集团在成都产业园的运营中&#xff0c;通过举办多样化的活动&#xff0c;积极助力产业园的生态建设。 企业服务活动 企业间交流会议与项目对接会&#xff1a;树莓集团定期组织企业间的交流会议和项目对接会&#xff0c;旨在促进不同企业之间的信息沟通和合作机会&#xff…