【从零理解 Spring AI · 03】Spring AI 在 Spring 生态里的位置

【从零理解 Spring AI · 03】Spring AI 在 Spring 生态里的位置

写在开头

写完前两篇的概念铺垫,有性子急的朋友问我:概念我大概懂了,但我现在就能用 RestClient 发个 POST、拼个 JSON 把模型调通,为什么还要引入一个框架?

这个问题问得很好,而且答案不是"框架更高级"这种空话。

我先把"直接调 HTTP"这条路走一遍给你看它会在哪变丑,再讲 Spring AI 怎么解------你会发现它用的全是你在 Spring 里已经学过的老招式 :模板类、自动配置、可插拔拦截器。这也是我把这篇放在这么前面的原因:Spring AI 不是一门新技术,而是老 Spring 思想在新领域的一次落地。


一、直接调 HTTP,会在哪变丑

1.1 能跑通的"最小版本"

不引入任何框架,调一个兼容 OpenAI 接口规范的模型服务,大概长这样:

java 复制代码
public String chat(String userMessage) {
    Map<String, Object> body = Map.of(
            "model", "some-model-name",
            "messages", List.of(
                    Map.of("role", "system", "content", "你是一个严谨的技术顾问。"),
                    Map.of("role", "user", "content", userMessage)
            )
    );

    RestClient restClient = RestClient.builder()
            .baseUrl("https://api.some-provider.com")
            .defaultHeader("Authorization", "Bearer " + apiKey)
            .build();

    Map<?, ?> resp = restClient.post()
            .uri("/v1/chat/completions")
            .contentType(MediaType.APPLICATION_JSON)
            .body(body)
            .retrieve()
            .body(Map.class);

    // 从响应里一层层挖出文本
    List<Map<?, ?>> choices = (List<Map<?, ?>>) resp.get("choices");
    Map<?, ?> message = (Map<?, ?>) choices.get(0).get("message");
    return (String) message.get("content");
}

能跑。 第一次调通的时候还挺有成就感。但在真实项目里活下去之后,你会发现它开始到处漏风。

1.2 三处典型的漏风点

第一处:字符串强转的地狱。 上面那段 Map<?, ?> 的层层强转只是冰山一角。响应结构里还有用量统计、结束原因、多候选结果......每多关心一样,就多几层强转。写错一个字段名,运行时才炸。

第二处:模型服务的"方言"。 你写死的 choices[0].message.content,是某一种接口规范的结构。换一家服务商,字段名可能就不一样了;流式返回的数据格式更是每家一个花样。业务代码和某个具体服务商被焊死了。

第三处:横切能力没有落点。 对话记忆(每轮重带历史)、结构化输出(把文本转成对象)、日志审计------这些能力放在哪?写在业务代码里?那每个调用点都要重复一遍;写个工具类?那工具类会越长越大,最后变成一个私有 mini 框架。

这三处漏风点,正好对应 Spring AI 的三个核心设计。 接下来一个一个对。


二、Spring AI 的四层结构

先上全景图,后面逐层展开:

四层结构:易变的推到最底层,稳定的留在上面

复制代码
你的业务代码
    ↓
ChatClient        ------ 你每天打交道的入口
    ↓
Advisor 链        ------ 记忆、检索、工具调用、日志
    ↓
ChatModel 适配层   ------ 把统一调用翻译成各家的请求格式
    ↓
各家模型服务

一句话概括这个设计:把"经常变的东西"(哪家模型、什么协议、什么参数名)压到最底层,把"稳定的东西"(你的业务意图、能力编排)留在最上层。


三、三层抽象各自管什么

3.1 ChatModel:协议适配层

ChatModel 是最底层的接口,它定义了一个统一的方法形态:

java 复制代码
public interface ChatModel extends Model<ChatRequest, ChatResponse> {
    ChatResponse call(ChatRequest request);
    // 另有 stream(...) 用于流式
}

每一家模型服务商,对应一个 ChatModel 实现------OpenAI 规范的、其他厂商的、本地部署的,各自把"统一的调用"翻译成"自家的 HTTP 报文"。

你平时不会直接碰这一层,它的价值体现在"换模型"那一刻:换实现、换配置,上面的代码不动。

3.2 ChatClient:业务侧的流式 API

ChatClient 是你每天写代码面对的东西 ,定位类似 RestClient 之于 HTTP 调用、JdbcTemplate 之于数据库:

java 复制代码
String answer = chatClient.prompt()
        .system("你是一个严谨的技术顾问,回答要给出依据。")
        .user("Spring 的依赖注入有哪几种方式?")
        .call()
        .content();

对比第一章那一坨 Map 强转------同样的功能,代码量少一个数量级,且全程类型安全。

它把"组装请求 → 发送 → 解析响应 → 提取文本"这套固定动作封装成了链式 API,你只声明变了的部分:这次问什么、这次带哪些能力。

3.3 Advisor:可插拔的能力链

Advisor 是这套设计里最妙的一环,如果你写过 Servlet Filter 或 Spring MVC 的 HandlerInterceptor,可以瞬间理解:

请求进来时依次穿过一串拦截器,每个拦截器可以在"发给模型之前"加工请求(比如把历史补进去、把检索资料塞进去),在"模型返回之后"加工响应(比如记日志、做审计)。

复制代码
prompt() ──→ [Advisor 1] ──→ [Advisor 2] ──→ ChatModel ──→ 响应原路返回

记忆、检索增强、工具调用、日志......在 Spring AI 里全部是 Advisor。这就是第一篇说的"把三套机制标准化"的具体落点:

  • 想加记忆?挂一个记忆 Advisor;
  • 想接知识库?挂一个检索 Advisor;
  • 不想要了?摘掉即可,业务代码一行不改。

能力变成了可插拔的环,而不是散落在业务代码里的 if-else。


四、自动配置:和 Spring Boot 的老配合

Spring AI 对 Spring Boot 生态的融入方式,也是你最熟悉的那套:Starter + 自动配置 + 属性绑定。

4.1 引入 starter

以兼容 OpenAI 接口规范的服务为例(DeepSeek、通义等多数国内厂商都兼容这套规范):

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

注意 artifactId 的命名是 spring-ai-starter-model-*。早期里程碑版本的 spring-ai-openai-spring-boot-starter 已经过时,网上旧教程里如果看到那个名字,直接按新命名替换。

4.2 写配置

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${API_KEY}          # 密钥不要硬编码,放环境变量
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-chat

就这么多。 启动应用后,Spring Boot 自动装配会做好三件事:

  1. 按配置创建好对应的 ChatModel 实现的 Bean;
  2. 创建一个 ChatClient.Builder Bean,预绑定了刚才的模型;
  3. 你在任何地方注入 ChatClient.Builder 就能用。

4.3 在业务代码里使用

java 复制代码
@Service
public class AssistantService {

    private final ChatClient chatClient;

    // 直接注入 Builder,构建一次,全局复用
    public AssistantService(ChatClient.Builder builder) {
        this.chatClient = builder
                .defaultSystem("你是一个严谨的技术顾问,回答要给出依据。")
                .build();
    }

    public String ask(String question) {
        return chatClient.prompt()
                .user(question)
                .call()
                .content();
    }
}

没有手动 new、没有连接池管理、没有报文解析。 这和你在 Spring Boot 里用 JdbcTemplate、RedisTemplate 的体验完全一致------写 AI 代码的姿势,就是写普通 Spring 代码的姿势。


五、"换模型不改代码"的真实含义

这个宣传语需要校准一下预期,它成立,但有边界。

5.1 成立的部分

换模型时,你改的是依赖和配置:

yaml 复制代码
# 换一家服务商:改 base-url、api-key、model 名,换对应 starter 依赖

业务代码里的 chatClient.prompt().user(...).call().content() 一个字不用动,因为 ChatClient 这层抽象是稳定的。

5.2 有边界的部分

抽象统一的是"接口",统一不了"能力"。

不同模型对高级特性的支持程度不一样:有的对工具调用支持得好,有的对长上下文更从容,有的结构化输出更稳定。换了模型之后:

  • 接口层面的代码不用改 ✅
  • 但效果可能变化------原来好用的提示词,新模型可能需要微调
  • 个别特性(如某些参数)可能不可用,需要条件化配置

所以准确的预期是:切换成本从"重写"降到"调整配置 + 回归验证",而不是零。 这已经很值了,但知道边界在哪,才不会被宣传语带偏。


六、这篇的四个要点

  1. 直接调 HTTP 的三大漏风点------字符串强转地狱、厂商方言焊死、横切能力没有落点。
  2. 四层结构 ------业务代码 / ChatClient / Advisor 链 / ChatModel 适配层;易变的压到底层,稳定的留在上层。
  3. Advisor 是能力可插拔的关键------记忆、检索、工具、日志全是链上的环;写过 Filter 的人零成本理解。
  4. 接入体验就是标准 Spring Boot 姿势 ------Starter + 配置 + 注入 ChatClient.Builder;"换模型"= 换依赖换配置,业务代码不动,但能力差异仍需回归验证。

延伸思考

这篇为了讲清结构,所有示例都是"一次调用拿一个字符串"的最简形态。真实业务里有个高频需求是对话记忆------上一轮说过的话这一轮还记得。它就是挂在 Advisor 链上的第一个"环"。

不过在看记忆怎么配之前,得先把 ChatClient 的日常用法过一遍:system 提示词怎么设、完整的响应对象怎么拿、什么时候用 ChatModel 而不是 ChatClient。这就是下一篇的内容。


下一篇

《ChatClient:把"调模型"变成一行代码》 ------把 prompt().call().content() 这条链逐段拆开:每一环在做什么、默认配置怎么设、拿到完整响应对象有什么用、以及 ChatClient 和 ChatModel 的选用标准。

这篇是整个专栏使用频率最高的一篇------后面所有的能力(记忆、检索、工具)都挂在这条链上,链本身必须先熟。


本专栏按 Spring AI 当前版本讲解。

相关推荐
Java成神之路-10 小时前
Spring AI Alibaba StateGraph 多Agent协作全解:5种编排模式
ai应用开发
码农小河6610 小时前
基于SSM+JSP的高校专业信息管理系统【附源码+数据库】
java·开发语言·数据库
风若飞12 小时前
Spring Ai 入门遇到问题1
java·后端·spring
Nuanyt13 小时前
LeetCode Hot 100 链表专题(五)· 2 题(Java)
java·算法·leetcode·链表
黑马程序员毕设13 小时前
基于 Java 的餐饮点餐系统设计与应用报告
java·开发语言
SL_staff13 小时前
从文档工具到知识中枢:基于JVS数字底座构建企业级统一内容管理平台的技术实践
java·开源·全栈
SL_staff13 小时前
从‘连得上’到‘管得好’:JVS-IOT双引擎驱动设备运维闭环
java·物联网·产品
组工部管理能手李哥15 小时前
一次政务知识库体验:AI回答之外,我更在意那行引用来源
大数据·人工智能·大模型·政务
AI源码15 小时前
旅游推荐管理系统
spring boot