Spring AI 实战:Java 项目如何接入大模型

当团队想把大语言模型(LLM)接进现有的 Java 后端时,往往会卡在”模型太多、SDK 太碎”上:OpenAI、Ollama、Anthropic 各有各的客户端,换一家就要重写一遍调用代码。Spring AI 正是为解决这个碎片化而生——它用一套统一的 ChatClient 抽象,让 Java 应用以极低的成本接入主流大模型,并天然兼容 Spring Boot 的依赖注入与自动配置。

一、为什么需要 Spring AI

在 Spring AI 出现之前,Java 接入大模型通常有三种做法:直接用各家官方 SDK、用 LangChain4j、或自己封装 HTTP 客户端。前两者 API 差异巨大,后者则要重复造轮子。Spring AI 的定位是”Spring 风格”的 AI 抽象层:你写一次 ChatClient 代码,换模型只改配置、不动业务代码。这对已有 Spring 技术栈的团队尤其友好——无需为了接 AI 而另起一套技术体系。

更具体地说,没有统一抽象时,你会陷入”N 个模型 × M 个业务入口”的组合爆炸:每接一家新模型,就要为每个调用点适配一套新的请求与响应结构。Spring AI 把这种差异收敛到起步依赖与配置文件里,业务代码只面向 ChatClient 编程,新增模型供应商几乎零成本。

二、Spring AI 的核心抽象

理解三个核心概念即可上手:

  • ChatClient:对话入口,提供流式(fluent)API,负责拼装提示词、发起调用、解析响应。
  • Message:分 system(系统设定)、user(用户输入)、assistant(模型回复)三类消息。
  • ChatResponse / entity:响应对象;通过 .entity() 还能把模型输出直接反序列化成 Java 对象。

除了上述三个核心概念,还有两类关键组件值得了解:Advisor 是横切逻辑(如把检索片段注入提示词、记录调用日志、做内容审核),可像过滤器一样层层叠加;PromptTemplate 则把可变参数从提示词里抽离出来,用 {name} 占位符做渲染,便于复用与多语言切换。它们都遵循”注册即用”的 Spring 风格,几乎不需要额外胶水代码。

三、5 分钟跑通第一个对话

以 Spring Boot 3 + OpenAI 为例,先引入 BOM 与起步依赖:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>1.0.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

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

在 application.yml 中配置密钥与基址:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com/v1

注入 ChatClient.Builder,几行代码即可暴露一个对话接口:

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatClient(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/ask")
    public String ask(@RequestParam String q) {
        return chatClient.prompt()
                .system("你是一个严谨的中文技术助手")
                .user(q)
                .call()
                .content();
    }
}

四、让模型返回结构化结果

模型默认返回纯文本,但业务通常需要 JSON 或可校验的对象。Spring AI 的 .entity() 会自动完成”提示词约束 + 反序列化”:

public record Answer(String summary, List<String> points) {}

Answer answer = chatClient.prompt()
        .user("用三句话总结 Spring AI 的核心能力")
        .call()
        .entity(Answer.class);

System.out.println(answer.summary());

五、给模型装上”工具”:Function Calling

只靠提示词,模型无法获取实时数据。通过 @Tool 把本地方法暴露给模型,它就能在需要时自动调用——这正是 大模型工具调用(Function Calling)实战 所讲的机制在 Java 侧的落地:

@Component
public class WeatherTools {
    @Tool(description = "查询某城市的当前天气")
    public String getWeather(@ToolParam("城市名称") String city) {
        // 这里调用真实天气 API
        return city + " 当前 22℃,晴";
    }
}

// 在对话中启用工具
chatClient.prompt()
        .user("北京现在天气怎么样?")
        .tools(new WeatherTools())
        .call()
        .content();

六、一行配置切换模型供应商

Spring AI 的可移植性体现在”换模型不动代码”。把起步依赖从 spring-ai-openai 换成 spring-ai-ollama 或 spring-ai-anthropic,再改对应配置即可。主流接入方式对比如下:

供应商起步依赖典型配置项适用场景
OpenAIspring-ai-openai-spring-boot-starterapi-key / base-url通用高质量对话
Ollamaspring-ai-ollama-spring-boot-starterbase-url / chat.model本地私有化部署
Anthropicspring-ai-anthropic-spring-boot-starterapi-key长上下文与推理
本地 vLLMspring-ai-openai(兼容模式)base-url 指向推理服务自建高吞吐推理,见 vLLM 部署实战

若你同时接入多家模型做成本与质量路由,可直接在网关层统一,参考 LiteLLM 多模型网关 的做法,把 Spring AI 当作客户端、把网关当作后端基座。

七、进阶:RAG 与多模型编排

当模型需要”基于私有知识回答”时,就要上 RAG(检索增强生成)。Spring AI 提供 VectorStore 与 QuestionAnswerAdvisor,把检索到的片段自动注入提示词。如果你更偏好低代码的编排方式,也可以先看 Dify + Ollama 本地部署 打通整体链路,再把关键节点用 Spring AI 代码化、沉淀到自有服务里。

八、生产落地的几个注意点

8.1 超时、重试与降级

大模型调用延迟高且偶发失败,务必配置连接超时、读写超时与指数退避重试;当模型不可用时,准备规则引擎或缓存兜底,避免整条链路雪崩。

在 Spring AI 中,底层 HTTP 调用由 Spring 的 RestClient 驱动,你可以通过自定义 RestClient.Builder Bean 统一设置超时;再叠加 Resilience4j 的熔断与限流,就能在不改动业务代码的前提下获得生产级韧性。建议把”超时 + 重试 + 降级”三件套作为接入大模型的默认底座。

8.2 成本与可观测

记录每次调用的 token 消耗与耗时,结合统一网关做预算控制。结构化日志加 TraceId 能帮你快速定位”哪次回答变慢”。

8.3 安全围栏

一旦开放工具调用,模型可能执行意外的外部动作。务必对工具做白名单、参数校验与权限隔离,并防范提示词注入,思路可参考 AI Agent 安全围栏。

九、流式输出与异步调用

对话式产品通常需要”逐字输出”的体验。Spring AI 的 .stream() 返回响应式流,配合 Spring WebFlux 即可实现边生成边返回:

@GetMapping(value = "/ask/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> askStream(@RequestParam String q) {
    return chatClient.prompt()
            .user(q)
            .stream()
            .content();
}

流式调用把首次 token 延迟(TTFT)降到最低,对长回答尤其友好;若使用的是阻塞式 Spring MVC,也可以在控制器里收集完整流后再一次性返回,权衡实时性与框架兼容性。无论哪种方式,ChatClient 的调用形态都保持一致,切换成本极低。

十、Spring AI 还是 LangChain4j

Java 生态里另一个热门选择是 LangChain4j。二者定位相近,但风格不同,可按团队习惯取舍:

维度Spring AILangChain4j
设计风格原生 Spring、注解与自动配置链式 API、框架无关性更强
上手成本已有 Spring 体系近乎零门槛需熟悉其 Chain/Agent 抽象
生态集成与 Spring Boot 全家桶无缝轻量、可嵌入任意 Java 应用
适用团队以 Spring 技术栈为主希望脱离 Spring 或混合栈

二者并非互斥:很多团队用 Spring AI 做主链路,需要特定编排能力时再引入 LangChain4j 的个别模块。选型的关键,是先想清楚”模型切换频率”与”是否重度依赖 Spring 生态”这两件事。

十一、小结

Spring AI 把”接大模型”这件原本碎片化的事,收束成一套符合 Spring 习惯的优雅 API:统一 ChatClient、结构化输出、工具调用、可移植的多供应商切换,几乎覆盖了 Java 团队落地 LLM 的高频需求。对于已有 Spring Boot 体系、又想快速把 AI 能力嵌进业务的团队,它值得作为首选起点。

上一篇 Flyway 实战:用版本化管理搞定数据库迁移与回滚
下一篇 Superset 实战:用开源 BI 搭建数据可视化看板
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]