为什么需要格式化输出
对于依赖可靠解析输出值的下游应用程序来说,生成结构化输出是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大模型】
后续会继续补充!
处理流程图
Spring AI提供了调用之前和调用之后两个步骤进行处理;
-
在LLM调用之前,转换器会将格式说明附加到提示词中,为模型生成所需的输出结构提供明确的指导。
-
LLM调用后,转换器获取模型的输出文本,并将其转换为结构化类型的实例。此转换过程涉及分析原始文本输出并将其映射到相应的结构化数据表示形式,例如 JSON、XML 或特定于域的数据结构。
类交互图
第一步:通过PromptTemplate
定义提示词模板,然后通过FormatProvider#getFormat()
获取指示大模型输出格式的指令。
第二步:通过ChatModel
调用大模型,并返回原始文本输出。
第三步:通过Spring AI提供的Converter<String,T>
转换结构化内容输出。
源码分析
注意,本文分析的是1.0.0-SNAPSHOT版本的,与1.0.0之前的版本相差比较大。
整体架构图
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
源码
BeanOutputConverter
是 StructuredOutputConverter<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());
}
}
MapOutputConverter
是 AbstractMessageOutputConverter<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
源码
ListOutputConverter
是 AbstractConversionServiceOutputConverter<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);
}
}
测试结果
另外一种实现方式
@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);
}
putConverter和
MapOutputConverter` 使用大家可以参考github上的代码,文章篇幅原因不在列举。
可能大家都想学习AI大模型技术,也想通过这项技能真正达到升职加薪,就业或是副业的目的,但是不知道该如何开始学习,因为网上的资料太多太杂乱了,如果不能系统的学习就相当于是白学。为了让大家少走弯路,少碰壁,这里我直接把全套AI技术和大模型入门资料、操作变现玩法都打包整理好,希望能够真正帮助到大家。
👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓