Spring AI 框架使用方法(大模型集成到业务代码实战)

Spring AI 是 JVM 上的 AI 工程抽象层,作用类似 Spring Data 之于数据库:它把 OpenAI、Anthropic、阿里通义千问、本地 Ollama 等模型提供商的差异统一成一套可移植的 API,业务代码面向 ChatClient 编程,而不是面向某个厂商的 HTTP 接口编程。我把它用进 AI 爆款文章创作器后,模型切换从改代码变成改配置,多阶段生成流程也收敛成了几个稳定抽象。下面按集成路径讲清楚它在项目中实际承担的职责。

一、不引入框架时,集成大模型要写多少东西

框架的定位可以从官网自述理解:把 Spring 生态的可移植、模块化设计原则延伸到 AI 领域。它不自研模型,也不与任何模型厂商竞争,只做 Java 应用与模型之间的集成层。这决定了它的使用边界——凡是跟”调模型”相关的横切问题,它都给出统一解法;凡是业务独有的流程编排,仍由项目自己的 Service 层负责。

写死 HTTP 调用的方案人人都能搭,成本藏在维护里。对比一个只有”问答”能力的调用差异:

环节 手写 HTTP 调用 Spring AI
请求构造 手拼 JSON、维护 URL 版本 流式 Builder API
响应解析 手写反序列化与错误码映射 自动映射到实体或 record
流式输出 手写字节流解析 Flux 直接返回
厂商切换 重写调用层 换依赖与配置
工具调用 手写协议与结果回填 Bean 方法直接暴露

手写方案在单模型原型阶段看不出差距,一旦要接第二个模型,或者要从同步改成流式,改动面会铺到所有调用点。

二、Spring AI 在爆款文章创作器中承担的核心职责

2.1 统一对话入口 ChatClient

ChatClient 是框架对外的主要门面,风格贴近 WebClient。创作器里所有大模型能力都收敛到这一个客户端上,业务层不再感知具体模型。Spring Boot 会自动装配一个 ChatClient.Builder,构造函数注入即可用。先引入对应 starter:

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

换成通义或本地模型时,只改坐标与配置块,Service 代码不动:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini
          temperature: 0.7

2.2 用 ChatClient 封装多阶段生成

爆款文章创作器的生成链路是选题、大纲、正文、标题润色四段,每段都是一次模型调用。我把它拆成独立的写作模板,共用同一个客户端:

@Service
public class ArticleGenerationService {
    private final ChatClient chatClient;

    public ArticleGenerationService(ChatClient.Builder builder) {
        this.chatClient = builder.defaultSystem(
            "你是资深新媒体编辑,输出中文爆款文章,标题党、水词一律禁用")
            .build();
    }

    public String generateOutline(String topic) {
        return chatClient.prompt()
            .user(u -> u.text("围绕【{topic}】产出三版大纲,每版 5 个小节,含选题角度。")
                .param("topic", topic))
            .call()
            .content();
    }
}

system 提示词统一管住角色与约束,user 消息用参数模板填充,keyword 一变,整段逻辑无需改动。

多阶段编排本身放在编排层,每个阶段各持一份模板与一个回调,阶段失败可以单独重试,不会因为某一段超时把整篇文章推倒重来。这一步属于项目自己的设计,Spring AI 只管单次调用的稳定性。

2.3 上下文记忆与知识增强

创作器里”换一个风格重写””在上文基础上续写”这类需求,需要把历史片段带进对话。框架的 advisors 机制可以在每次请求时把对话历史自动注入上下文,业务代码不用手拼多轮消息。知识库类文章再叠加向量检索,把素材库文档先向量化,问答前按相似度取回 top-k 片段塞进 prompt,回答就锚定在自有数据上。

2.4 结构化输出直接进实体

大纲、标题列表这类结果,以前要写正则从文本里抠,现在一行声明目标类型:

public record OutlineResult(String angle, List<String> sections) {}

List<OutlineResult> results = chatClient.prompt()
    .user("为【AI 写作工具】输出 3 个选题角度,JSON 数组格式")
    .call()
    .entity(new ParameterizedTypeReference<List<OutlineResult>>() {});

框架内部负责让模型按格式返回并完成反序列化,业务代码拿到的是强类型对象,后续存库、渲染都顺理成章。

三、流式与工具调用在业务代码里的位置

3.1 流式输出走 SSE 通道

长文生成耗时以十秒计,正文阶段我用流式把 token 逐段推给前端 Vue,接口直接返回 Flux:

@PostMapping(value = "/api/article/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamArticle(@RequestBody GenerateRequest request) {
    return chatClient.prompt()
        .user(u -> u.text("按大纲【{outline}】生成正文,分 5 段输出。")
            .param("outline", request.outline()))
        .stream()
        .content();
}

3.2 工具调用把业务能力接进对话

写素材类文章时,模型需要查产品库或关键词热度,我把 Service 方法声明成工具,模型自己决定何时调用:

@Bean
@Description("查询素材库中与主题相关的文章标题与热度")
public Function<MaterialQuery, List<Material>> materialFetcher() {
    return query -> materialService.search(query.topic(), query.limit());
}

方法名带 @Description 之后,调用点只需要在 prompt 里声明工具名,协议细节全部由框架处理。前端展示时,模型哪一步调了素材库、返回了什么,都能从调用记录还原,排错时链路清晰。

四、上生产前必须盯住的四个环节

  1. 超时与重试:模型接口偶发抖动,给调用配连接超时、读超时,配合退避重试,避免用户请求跟着失败;
  2. token 预算:流式生成要设 max-tokens,正文分段生成也能压低单次成本与首字延迟;
  3. 输出护栏:模型返回进入落库或渲染前做内容校验,防止注入类文本进入下游;
  4. 评估闭环:记录每次请求的 prompt 与响应,用固定测试集回归,模型或提示词变更后对比打分。

这四项在框架之外,却是生产可用与 Demo 的分水岭。框架解决了”怎么调”,这些环节决定”调得稳不稳”。

常见问题(FAQ)

Q1:Spring AI 和直接调 SDK 有什么区别?

多模型可移植。业务代码只依赖 ChatClient 抽象,切换或扩展模型厂商不动调用层。

Q2:接入自己的私有化模型要怎么做?

选 OpenAI 兼容协议的服务部署,starter 指向自定义 base-url 与模型名即可。

Q3:流式输出用什么类型返回前端?

接口返回 text/event-stream,方法返回 Flux,前端逐段读取渲染。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部