Spring AI Structured-Output源码分析

Spring AI Structured-Output源码分析

  • 在 Spring AI 中,StructuredOutputConverter(常用的实现类是 BeanOutputConverter)的核心作用是将大模型的非结构化文本输出 转换为 Java 的结构化对象(POJO)。
  • StructuredOutputConverter extends Converter<String, T>, FormatProvider
    • FormatProvider:负责将你 Java Bean 的 JSON Schema
    • Converter:负责将模型返回的字符串 通过 Jackson 反序列化转换为你的Java Bean
  • 其实结构化对象本质实现原理也是基于:JSON 解析器 + Prompt 工程 + 结果解析 机制

实现思考&源码分析

如何告知大模型需要转换格式?

  • 原理

StructuredOutputConverter 接口有一个方法 getFormat()。当你调用这个方法时,它会生成一段自然语言的指令 ,这段指令中包含了你 Java Bean 的 JSON Schema。你需要手动或自动将这段指令拼接到发给 AI 的 Prompt 中。

  • 源码
    • 将你 Java Bean 的格式为 JSON Schema,并放到请求上下文中
java 复制代码
@GetMapping("/chat-format")
public BeanEntity simpleChatFormat(@RequestParam(value = "query", defaultValue = "写诗歌") String query) {
  return chatClient.prompt(query).call()
    .format(converter.getFormat()) // 显式注入格式指令
    .entity(BeanEntity.class) // 这里内部会自动调用 format()
    ;
}

//org.springframework.ai.chat.client.DefaultChatClient.DefaultCallResponseSpec#entity(java.lang.Class<T>)
public <T> T entity(Class<T> type) {
  var outputConverter = new BeanOutputConverter<>(type);
  return doSingleWithBeanOutputConverter(outputConverter);
}

@Nullable
private <T> T doSingleWithBeanOutputConverter(StructuredOutputConverter<T> outputConverter) {
  //1. 根据泛型 T 的类型,生成标准的 JSON Schema:详见BeanOutputConverter.getFormat()
  //2. 将生成的生成标准的 JSON Schema放入到上下文中,key为:"spring.ai.chat.client.output.format"
  var chatResponse = doGetObservableChatClientResponse(this.request, outputConverter.getFormat())
    .chatResponse();
  var stringResponse = getContentFromChatResponse(chatResponse);
  //3.大模型返回的字符串反序列化为JavaBean,详见:BeanOutputConverter.convert()
  return outputConverter.convert(stringResponse);
}
  • 将 JSON Schema添加到Prompt提示词中,在Advisor链中最后一个ChatModelCallAdvisor实现
java 复制代码
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
  //1.增强格式指令,即提示词增强
  ChatClientRequest formattedChatClientRequest = augmentWithFormatInstructions(chatClientRequest);
  ChatResponse chatResponse = this.chatModel.call(formattedChatClientRequest.prompt());
  return ChatClientResponse.builder()
    .chatResponse(chatResponse)
    .context(Map.copyOf(formattedChatClientRequest.context()))
    .build();
}

private static ChatClientRequest augmentWithFormatInstructions(ChatClientRequest chatClientRequest) {
  //2.从请求上下文获取JSON Schema格式指令
  String outputFormat = (String) chatClientRequest.context().get(ChatClientAttributes.OUTPUT_FORMAT.getKey());
	//3.其实逻辑很简单:用户文本 + 换行 + JSON Schema格式指令
  Prompt augmentedPrompt = chatClientRequest.prompt()
    .augmentUserMessage(userMessage -> userMessage.mutate()
                        .text(userMessage.getText() + System.lineSeparator() + outputFormat)
                        .build());
  return ChatClientRequest.builder()
    .prompt(augmentedPrompt)
    .context(Map.copyOf(chatClientRequest.context()))
    .build();
}

大模型返回结果后是如何转换为对应的实体?

  • 原理

转换发生在应用层(你的服务内部),而不是模型层。模型返回的仍然是 String(一段 JSON 文本),Spring AI 在接收到这段文本后,调用 convert() 方法将其反序列化,详见:BeanOutputConverter.convert()。

  • 源码
java 复制代码
public T convert(@NonNull String text) {
  try {
    // 1. 预处理:清洗文本
    // 大模型经常喜欢返回 ```json { ... } ```这种 Markdown 格式
    // 这里会去除首尾的 Markdown 标记,只保留纯 JSON 字符串
    text = this.textCleaner.clean(text);

    // 2. 反序列化
    // 使用 Jackson 的 ObjectMapper 将 JSON 字符串转为 Java 对象
    return (T) this.objectMapper.readValue(text, this.objectMapper.constructType(this.type));
  }
  catch (JsonProcessingException e) {
    throw new RuntimeException(e);
  }
}

注意事项

  • 如果你使用了 StructuredOutputConverter(或者 .entity() 方法),请不要在你的 User Prompt 中再手写任何关于格式(Format)的指令,避免提示词冲突,只关注业务内容即可。
相关推荐
桃西西呀5 分钟前
我把整本接口文档塞进 system prompt,前 3 轮答得准,第 8 轮开始胡说,账单还翻了 4 倍
人工智能·llm·ai编程
天天代码码天天8 分钟前
给孩子打印一本“数字连点画”!连出小鱼、火箭,画完还能涂色
人工智能
SL_staff1 小时前
规则联动:给工厂装上‘自动化神经反射弧’——JVS·物联网平台规则引擎实战教程
java·人工智能·物联网
骑着蜗牛撵大象3271 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
WJ.Polar1 小时前
Java封装、继承、多态
java·开发语言
众壹新能源科技1 小时前
逆变器自带无功不够用,加装5MVar SVG后功率因数从0.85回到0.99
运维·人工智能·自动化
ly76891 小时前
PostgreSQL 备份恢复策略:pg_basebackup、WAL 归档与 PITR 在企业 Java 系统中的落地
java·数据库·postgresql·pitr·pg_basebackup·wal 归档
king13598745501 小时前
LFM2.5-24B-A2B Mac 本地部署:内存估算、GGUF/MLX 与 Python 预检脚本
人工智能·macos
摇滚侠1 小时前
Java 中如何表示负数,有符号和无符号的区别,补码的计算规则,char byte short int long 的范围
java·开发语言
DeeGLMath1 小时前
optimtool最新版加速训练分类任务
人工智能·分类·数据挖掘