《SpringAI 入门教程》 11. 结构化输出

60 阅读3分钟

1. 基础类型

以Boolean为例,在Agent中可以用于判定用于的内容的2个分支,不同分支走不同的逻辑

可以接受一些对象,布尔值等

下面也是一种简单的Agent

  1. Boolean类型的返回

import lombok.AllArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AllArgsConstructor
@RestController
public class ChatClientOutputController {

    private final DeepSeekChatModel deepSeekChatModel;

    @RequestMapping(value = "/promptOutput", produces = "text/stream;charset=UTF-8")
    public String promptOutput() {
        ChatClient chatClient = ChatClient.builder(deepSeekChatModel)
                //.defaultSystem(prompt)
                .build();
        Boolean isComplain = chatClient.prompt().system("""
                        请判断用户信息是否表达了投诉意图?
                        只能用 true 或 false 回答,不要输出多余内容
                        """)
                .user("你们商家的快递迟迟不到,我要退货!")
                .call()
                .entity(Boolean.class);
        if (Boolean.TRUE.equals(isComplain)) {
            System.out.println("用户是投诉,转接人工客服!");
        } else {
            System.out.println("用户不是投诉,自动流转客服机器人");
        }
        return "" + isComplain;
    }

}

2. POJO类型

用购物APP应该见过复制一个地址,自动为你填入每个输入框,这种例子用大模型可以轻松完成!


@RequestMapping(value = "/promptOutputPojo", produces = "text/stream;charset=UTF-8")
public void promptOutputPojo() {
    ChatClient chatClient = ChatClient.builder(deepSeekChatModel)
            //.defaultSystem(prompt)
            .build();
    Address address = chatClient.prompt().system("""
                    请从下面这条文本中提取收货信息
                    """)
            .user("收货人:张三,电话13199999999,地址:上海市松江区人民路枫林郡小区2号楼1单元801室")
            .call()
            .entity(Address.class);
    System.out.println(address);
}

/***
* 内部类
*/
public record Address(String name, // 收件人姓名
                      String phone, // 联系电话
                      String province, // 省
                      String city, // 市
                      String district, // 区/县
                      String detail) // 详细地址
                      { }

注意:对象中的属性名不能乱定义,需要能被大模型理解

3. 原理

核心:结构化输出转换器 StructuredOutputConverter

使用通用补全 API 从大型语言模型(LLMs)生成结构化输出时,需要对输入和输出进行精细处理。其中,结构化输出转换器在调用 LLM 之前和之后都起着关键作用,确保最终输出符合预期的结构。

在调用 LLM 之前,转换器会将格式说明附加到提示词中,为模型提供明确的指导,说明所需的输出结构。这些说明就像一份蓝图,引导模型生成符合特定格式的响应。

在调用 LLM 之后,转换器会将模型输出的文本转换为结构化类型的实例。这个转换过程包括解析原始文本输出,并将其映射为对应的结构化数据表示形式,例如 JSON、XML 或领域特定的数据结构。

底层呢还是要告诉大模型,输出结果的格式的,但是ChatClient默认给做了封装,不需要我们显示地去给大模型指示返回格式。下面是原始的处理。BeanOutputConverter.getFormat()会返回大模型的格式转换指令。

通过核心的转换器,自动提供了转换指令。告诉大模型应该提取哪些内容,用什么样的格式进行响应。


public record Address(String name, // 收件人姓名
                      String phone, // 联系电话
                      String province, // 省
                      String city, // 市
                      String district, // 区/县
                      String detail) // 详细地址
{
}


@RequestMapping(value = "/promptOutputPojoRaw", produces = "text/stream;charset=UTF-8")
public void promptOutputPojoRaw() {
    ChatClient chatClient = ChatClient.builder(deepSeekChatModel)
            //.defaultSystem(prompt)
            .build();
    BeanOutputConverter<Address> beanOutputConverter = new BeanOutputConverter<>(Address.class);

    String format = beanOutputConverter.getFormat();

    PromptTemplate promptTemplate = PromptTemplate.builder().template("""
                    请从下面的文本中提取收货信息
                    {format}
                    {text}
                    """).variables(Map.of("format", format, "text", "收货人:张三,电话13199999999,地址:上海市松江区人民路枫林郡小区2号楼1单元801室"))
            .build();

    ChatResponse response = deepSeekChatModel.call(promptTemplate.create());

    Address address = beanOutputConverter.convert(response.getResult().getOutput().getText());

    System.out.println(address);
}

debug format的内容

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:
```{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "city" : {
      "type" : "string"
    },
    "detail" : {
      "type" : "string"
    },
    "district" : {
      "type" : "string"
    },
    "name" : {
      "type" : "string"
    },
    "phone" : {
      "type" : "string"
    },
    "province" : {
      "type" : "string"
    }
  },
  "additionalProperties" : false
}```