spring ai 入门之4 : 结构化输出

296 阅读5分钟

Spring AI结构化输出应用场景解析

结构化输出主要应用于需要从非结构化文本中提取特定信息并转换为标准化数据格式的场景,比如将一段描述演员及其参演电影的文字转换成包含演员名称和电影列表的Java Bean对象。这种做法能够帮助减少处理自然语言理解时的复杂度与错误率,使得AI生成的内容更易于被程序解析和利用。通过这种方式,可以极大地简化后续的数据处理流程,并提高应用程序对于自然语言内容的理解能力及响应速度。

本文采用spring ai alibaba 调用通义qwen来实现。 qwen有100万免费Token额度,可以快速实现,同时,因为qwen 也是个开源的模型,我们可以自己搭建模型来实现免费使用

Spring AI:简化Java中AI应用开发的框架

在过去,Java 缺乏一个高效且统一的 AI 应用框架,这使得开发者在使用不同 AI 组件时需要分别对接各自的 API,增加了开发难度和维护成本。为了解决这一问题,Spring 团队推出了 Spring AI,这是一个专门设计用于简化 AI 工程的 Java 框架。Spring AI 的核心优势在于它提供了一套标准化的接口,使得开发者可以轻松地通过更改配置来切换不同的 AI 服务提供商,如 OpenAI、Azure 或阿里云等。此外,Spring AI 与现有的 Spring 生态系统及 Java 的面向对象编程理念完美兼容,支持流式处理,特别适用于诸如车牌号识别这样的场景,极大提高了开发效率并降低了迁移成本。

Qwen简介

通义千问Qwen是阿里巴巴云推出的一种先进的语言模型,在多个权威基准测评中表现出色。它在MMLU、TheoremQA和GPQA等评测指标上超越了Llama 3 70B,并在Hugging Face的开源大模型排行榜Open LLM Leaderboard上位居第一,展示了其卓越的性能和广泛的应用潜力。该模型能够生成高质量的文章、故事、诗歌等多种文本内容,还能进行智能问答、代码编写等任务,为用户提供强大的语言理解和生成能力。

v2-16575c01dc9e36f063595bbe7e49277c_b.jpg.png

大家也可以参与和支持这些竞技活动,亲自体验并投票支持你心目中的最佳模型。它在思南评测平台 arena.opencompass.org.cn/ 上表现优异,仅次于国际知名的GPT和Claude模型; 同时,在国外大模型竞技场 huggingface.co/spaces/lmar… 中在中文处理中属于第一梯队。

Spring AI Alibaba框架介绍

Spring AI Alibaba 是一个专为Spring AI设计的实现框架,它基于Spring AI的API,提供了对阿里云百炼系列大模型(包括通义等国产大模型)的支持。通过Spring AI Alibaba,开发者能够轻松地构建聊天、文生图、文生语音等多种类型的AI应用,并且得益于其优秀的抽象能力,只需编写一次代码,即可通过简单配置切换不同的AI服务提供商,从而极大地减少了开发和迁移的成本。此外,该框架还支持结构化输出等功能,使得处理非结构化数据变得更加高效便捷。对于希望利用国产大模型进行创新的企业和个人来说,Spring AI Alibaba提供了一个强大而灵活的选择。

使用Spring AI Alibaba实现结构化输出Java Bean

为了能够基于Spring AI Alibaba实现内容的结构化输出,并将其转换为一个Java Bean,需要按照以下步骤操作。这些步骤将帮助您设置开发环境、配置必要的依赖项,并最终完成代码编写以实现预期的功能。

环境准备

  • JDK版本:确保您的项目使用的是 JDK 17 或更高版本。
  • Spring Boot版本:项目应基于 Spring Boot 3.3.x 版本构建。

获取阿里云通义千问API Key

  1. 访问阿里云百炼页面并登录您的账号。
  2. 开通“百炼大模型推理”服务,并等待开通成功的短信通知。
  3. 登录后,在用户中心找到 API-KEY 的创建入口,生成一个新的 API-KEY 并妥善保存,因为后续会用到这个key来配置应用程序。

配置环境变量

设置环境变量 AI_DASHSCOPE_API_KEY 以便于在应用中引用:

export AI_DASHSCOPE_API_KEY=您的实际API-KEY

添加Maven仓库与依赖

由于Spring AI Alibaba组件尚未正式发布到Maven中央仓库,因此需要额外添加Spring官方提供的临时仓库地址至您的 pom.xml 文件中:

<repositories>
    <repository>
        <id>sonatype-snapshots</id>
        <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        <snapshots><enabled>true</enabled></snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots><enabled>false</enabled></snapshots>
    </repository>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <releases><enabled>false</enabled></releases>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter</artifactId>
        <version>1.0.0-M3.1</version>
    </dependency>
</dependencies>

同时,请确认已经指定了正确的Spring Boot父POM:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.4</version>
</parent>

定义Java Bean

创建一个简单的Java类用于接收和处理从AI模型返回的数据:

package com.example;

import java.util.List;

public class ActorsFilms {
    private String actor;
    private List<String> movies;

    // 构造函数、getters 和 setters 省略...
}

编写Controller逻辑

接下来,在Spring MVC控制器内注入 ChatClient 对象,并利用 BeanOutputConverter 将AI响应解析为自定义对象列表:

@RestController
@RequestMapping("/ai")
public class OutputParserController {

    @Autowired
    private ChatClient chatClient;

    @GetMapping("/output/stream")
    public List<ActorsFilms> generateStream(@RequestParam(value = "actor", defaultValue = "Jeff Bridges") String actor) {
        var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});
        
        Flux<String> flux = this.chatClient.prompt()
            .user(u -> u.text("""
                Generate the filmography for a random actor.
                {format}
            """).param("format", converter.getFormat()))
            .stream()
            .content();

        return converter.convert(String.join("", Objects.requireNonNull(flux.collectList().block())));
    }
}

通过上述配置及编码步骤,您可以成功地将非结构化的文本数据转化为结构化的Java对象,从而便于进一步处理或展示给用户。注意这里使用了Reactor库中的 Flux 类型来异步处理网络请求的结果流。