Spring AI 结构化输出转换器有助于将LLM输出转换为结构化格式,例如列表、映射或 Java Bean 中定义的复杂结构。

这些类有助于将预期的响应格式传达给 LLMs Java 类,然后使用标准的 mashalling 和 unmarshalling 功能将响应解析为 Java 类。

Spring AI 提供了 3 个内置类 MapOutputConverter、ListOutputConverter 和 BeanOutputConverter,它们可以帮助传达 API 中常见的大多数预期响应结构。

在非常高的级别上,这些转换器执行两个主要任务:

  • 它指示提示所需的响应格式(XML 或 JSON)和结构。这确保了以严格的格式生成响应。
  • 收到响应后,它会将响应解析为 Java 类,例如 POJO、List 或 Map。

 1. 设置

在计算机上运行任何代码之前,请确保已将 OpenAPI 项目密钥设置为环境变量,并且应用程序从那里读取它。

1
2
3
Termimal

export OPENAI_API_KEY=[api_key_copied_from_openai_site]

稍后,我们可以在项目的属性文件中引用这个 API 密钥:

1
2
3
application.properties

spring.ai.openai.api-key=${OPENAI_API_KEY}

接下来,我们需要添加特定于我们正在与之交互的 LLM Maven 依赖项。在这个演示中,我们使用的是 OpenAI 的 ChatGPT,所以让我们在应用程序中添加它的依赖项。

1
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency>

有关完整的设置步骤,请遵循 Spring AI 教程。

2. Spring AI MapOutputConverter 示例

MapOutputConverter 类指示提示LLM以符合 JSON 格式请求输出RFC8259并且结构应为 java.util.HashMap 类型。稍后,当我们得到LLM响应时,此转换器会解析来自 JSON 的响应并填充到 Map 实例中。

MapOutputConverter 的工作方式是将固定文本附加到请求特定格式的用户消息中。我们可以在 MapOutputConverter 类源代码或调用其 toFormat() 方法中看到这个固定文本。

1
public class MapOutputConverter extends AbstractMessageOutputConverter<Map<String, Object>> { //... @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. """; return String.format(raw, HashMap.class.getName()); } }

在以下示例中,我们提供了一个国家/地区列表,LLM并要求以 Map 格式返回国家/地区的名称及其首都。

1
@GetMapping("/country-capital-service/map") public Map<String, Object> getCapitalNamesInMap(@RequestParam String countryNamesCsv) { if (countryNamesCsv == null || countryNamesCsv.isEmpty()) { throw new IllegalArgumentException("Country names CSV cannot be null or empty"); } MapOutputConverter converter = new MapOutputConverter(); String format = converter.getFormat(); PromptTemplate pt = new PromptTemplate("For these list of countries {countryNamesCsv}, return the list of capitals. {format}"); Prompt renderedPrompt = pt.create(Map.of("countryNamesCsv", countryNamesCsv, "format", format)); ChatResponse response = chatClient.call(renderedPrompt); Generation generation = response.getResult(); // call getResults() if multiple generations return converter.parse(generation.getOutput().getContent()); }

收到请求后,API 通过将“countryNamesCsv”替换为提供的国家/地区列表来准备最终提示,并将格式替换为 MapOutputConverter 的 getFormat() 方法中指定的固定文本。

我们从 API 接收LLM并最终从 API 返回的响应是 Map 格式的 JSON 输出。

3. Spring AI ListOutputConverter 示例

ListOutputConverter 类的工作方式与列表输出转换器几乎相同,只是它指示提示在逗号分隔值列表中请求LLM输出。稍后,Spring AI 使用 Jackson 将 CSV 值解析为列表。

1
public class ListOutputConverter extends AbstractConversionServiceOutputConverter<List<String>> { //... @Override public String getFormat() { return """ Your response should be a list of comma separated values eg: `foo, bar, baz` """; } }

在以下示例中,我们提供了一个国家/地区列表,并在列表中询问其首都。

1
@GetMapping("/country-capital-service/list") public List<String> getCapitalNamesInList(@RequestParam String countryNamesCsv) { if (countryNamesCsv == null || countryNamesCsv.isEmpty()) { throw new IllegalArgumentException("Country names CSV cannot be null or empty"); } ListOutputConverter converter = new ListOutputConverter(new DefaultConversionService()); String format = converter.getFormat(); PromptTemplate pt = new PromptTemplate("For these list of countries {countryNamesCsv}, return the list of capitals. {format}"); Prompt renderedPrompt = pt.create(Map.of("countryNamesCsv", countryNamesCsv, "format", format)); ChatResponse response = chatClient.call(renderedPrompt); Generation generation = response.getResult(); // call getResults() if multiple generations return converter.parse(generation.getOutput().getContent()); }

返回 LLM CSV 值的纯字符串中的响应:

LLM output

New Delhi, Washington D.C., Ottawa, Jerusalem

这个 CSV 使用其 converter.parse() 方法解析为 java.util.List。让我们测试一下 API:

4. Spring AI BeanOutputConverter 示例

BeanOutputConverter 有助于请求与 Java POJO 匹配的 JSON 格式和结构的LLM响应。LLM响应中的字段始终与指定 Java Bean 中的字段兼容。

在以下方法中,JSON 模式是从作为参考提供的 Java Bean 类生成的。

1
public class BeanOutputConverter<T> implements StructuredOutputConverter<T> { //... @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); } }

让我们再举一个例子。在此示例中,我们提供了一个国家/地区的名称,并要求返回LLM其前 10 个城市。响应应遵循指定的 Java Bean 结构:

1
public record Pair(String countryName, List<String> cities) { }

让我们看看如何使用 BeanOutputConverter 来指示、提示和解析收到的响应。

1
@GetMapping("/country-capital-service/bean") public Pair getCapitalNamesInPojo(@RequestParam String countryName) { BeanOutputConverter<Pair> converter = new BeanOutputConverter(Pair.class); String format = converter.getFormat(); PromptTemplate pt = new PromptTemplate("For these list of countries {countryName}, return the list of its 10 popular cities. {format}"); Prompt renderedPrompt = pt.create(Map.of("countryName", countryName, "format", format)); ChatResponse response = chatClient.call(renderedPrompt); Generation generation = response.getResult(); // call getResults() if multiple generations return converter.parse(generation.getOutput().getContent()); }

我们可以通过调用 API 来验证生成的响应:

 5. 结论

正如本Spring AI教程中所讨论的,LLM不会总是以非结构化文本返回响应。很多时候,我们需要它们以固定格式返回响应,这就是这些结构化输出转换器的用武之地。

我建议您使用这些 API,并尝试以不同的格式请求输出,以更好地理解和欣赏它们的用法。

 祝您学习愉快!!

Github 上的源代码