第 2 章 · SpringAI环境搭建与 Hello World

0 阅读2分钟

版本:Spring AI 2.0.1 · Spring Boot 4.x · JDK 17+(推荐 21) 目标:用 BOM + model starter + 最小配置,跑通一次同步调用一次流式调用。 完整 starter 清单见 第 17 章;本章只走最小可用路径。

四步心智模型:

引入 spring-ai-bom
  → 加 spring-ai-starter-model-*(本章用 openai 或 ollama)
    → 配置 api-key / base-url / model
      → ChatClient.call / stream 跑通

2.1 准备工作

要求
JDK17+(日常推荐 21
Spring Boot4.0 / 4.1(与 Spring AI 2.0.x 对齐)
构建Maven 3.9+ 或 Gradle 8+
模型后端OpenAI 兼容 API Key,本机 Ollama

本章不需要 Docker、向量库、Redis。若本机有 Spring AI 上游源码,只适合对照接口,不要当工程依赖源。

密钥放入环境变量(不要写进仓库):

# Linux / macOS
export OPENAI_API_KEY=sk-...
​
# Windows PowerShell
$env:OPENAI_API_KEY = "sk-..."

2.2 用 BOM 把版本钉住

Spring AI 各模块必须齐步走。手写零散版本(一部分 2.0.1、一部分旧版)容易出现 NoSuchMethodError 或配置前缀对不上。BOM import 是默认做法,不是可选项。

Maven(最小可运行片段)

父工程用 Boot 父 POM(或自行 import spring-boot-dependencies),再 import Spring AI BOM:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>4.0.0</version> <!-- 以你选用的 Boot 4.x 为准 -->
  <relativePath/>
</parent><properties>
  <java.version>21</java.version>
  <spring-ai.version>2.0.1</spring-ai.version>
</properties><dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>${spring-ai.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement><dependencies>
  <!-- 同步 HTTP:MVC -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- 流式 Hello 返回 Flux:加 WebFlux(可与 web 并存做演示) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <!-- 不要写 <version>,交给 BOM -->
  </dependency>
</dependencies>

Gradle(Kotlin DSL)

dependencies {
  implementation(platform("org.springframework.ai:spring-ai-bom:2.0.1"))
  implementation("org.springframework.boot:spring-boot-starter-web")
  implementation("org.springframework.boot:spring-boot-starter-webflux")
  implementation("org.springframework.ai:spring-ai-starter-model-openai")
}

2.3 starter 实际带来了什么

spring-ai-starter-model-openai 为例:

spring-ai-starter-model-openai
  ├─ openai 实现(如 OpenAiChatModel)
  ├─ autoconfigure-model-openai(条件装配 @Bean)
  └─ 传递依赖:model 抽象 / client-chat / commons …

启动成功后,容器里通常已有:

Bean用途
ChatModel与供应商对话的抽象
ChatClient.Builder组装默认 system / advisors / tools
Options / Observation 钩子模型参数与可观测性

按场景记几组常见 starter 即可(细节见第 17 章):

  • Model…-openai / …-ollama / …-anthropic

  • Memory…-chat-memory + repository

  • Vector…-vector-store-*

  • MCP…-mcp-server-webmvc

同时引入多个 model starter 时,容器里会有多个 ChatModel。不要裸 @Autowired ChatModel,改用 @Qualifier,或为每个模型各建一个 ChatClient Bean。


2.4 最小配置

OpenAI 及兼容网关

# application.yml
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com   # 兼容网关时只改这里与 key/model
      chat:
        options:
          model: gpt-4o-mini
          temperature: 0.7

注意:model 落在 spring.ai.openai.chat.options 下,不要写成 openai 根下的平铺字段(部分旧文会写错)。

Ollama(本地,无云端 Key)

依赖换成:

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama3.1

本机先执行:ollama pull llama3.1,并确认 ollama list 能看到该模型。

Hello 阶段先记住的默认值

配置 / 行为默认含义
spring.ai.tools.resolution.fallback.enabledfalse只执行当前请求 / defaultTools 上挂的工具
OpenAI strictfalse需要严格 JSON schema 时再显式打开
ChatClient.Builder自动配置提供业务里通常 @Bean ChatClient 补默认 system

2.5 工程骨架

建议包结构:

src/main/java/com/example/demo/
  DemoApplication.java
  config/AiConfig.java
  web/HelloController.java
src/main/resources/
  application.yml
package com.example.demo;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
package com.example.demo.config;
​
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
@Configuration
public class AiConfig {
​
    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        // Builder 由自动配置注入;这里只补默认值并 build
        return builder
                .defaultSystem("你是简洁准确的中文助手,回答控制在必要篇幅内。")
                .build();
    }
}

为什么要 @Bean ChatClient:Controller 直接注入成品客户端,避免每个接口重复写 system / advisors。需要按请求覆盖时,再在 prompt() 链上临时 .system(...) / .advisors(...)


2.6 Hello World:同步调用

package com.example.demo.web;
​
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping("/demo")
public class HelloController {
​
    private final ChatClient chatClient;
​
    public HelloController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
​
    @GetMapping("/sync")
    public String sync(
            @RequestParam(defaultValue = "用一句话解释依赖注入") String q) {
        return chatClient.prompt()
                .user(q)
                .call()
                .content();
    }
}

调用链拆开看:

prompt()     → ChatClientRequestSpec
.user(q)     → 写入 UserMessage
.call()      → 走 Advisor 链,末端到达 ChatModel.call
.content()   → 取出助手文本(空结果时要警惕,生产需判空)

需要 Usage、finishReason 时:

ChatResponse response = chatClient.prompt()
        .user(q)
        .call()
        .chatResponse();
​
String text = response.getResult().getOutput().getText();
var usage = response.getMetadata().getUsage();

更短的写法(整段当作 user 文本):

chatClient.prompt(q).call().content();

验证:

curl "http://localhost:8080/demo/sync?q=你好"

2.7 Hello World:流式调用

import org.springframework.http.MediaType;
import reactor.core.publisher.Flux;
​
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(
        @RequestParam(defaultValue = "讲一个很短的冷知识") String q) {
    return chatClient.prompt()
            .user(q)
            .stream()
            .content();
}

要点:

  1. 依赖:返回 Flux 做 SSE,本章示例已加 spring-boot-starter-webflux。若坚持纯 MVC,改用 SseEmitter 桥接(见第 12 章),不要在请求线程上 blockLast()

  2. 出口.stream().content() 是文本增量;要 finishReason / 分块元数据用 .stream().chatResponse()

  3. 范围:Hello 阶段不要挂 Tool。流式下的工具循环更复杂,放到工具 / 流式专章。

验证(-N 关闭缓冲,才能看到逐块输出):

curl -N "http://localhost:8080/demo/stream?q=你好"

2.8 补充:Options、手工组装与多模型

请求级 Options

import org.springframework.ai.openai.OpenAiChatOptions;
​
@GetMapping("/sync-options")
public String syncOptions(@RequestParam String q) {
    return chatClient.prompt()
            .user(q)
            .options(OpenAiChatOptions.builder()
                    .temperature(0.2)
                    .maxTokens(256)
                    .build())   // 记得 build()
            .call()
            .content();
}

.options(...) 的入参类型以 IDE 中 ChatClientRequestSpec#options 为准;OpenAI 路径通常传 OpenAiChatOptions

临时覆盖 system

String answer = chatClient.prompt()
        .system("你是气象解说员,只用短句。")
        .user("用科普口吻说明什么是露点温度")
        .call()
        .content();

关闭自动配置时的手工组装(单测 / 自定义节点)

import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
​
ChatModel chatModel = OpenAiChatModel.builder()
        .openAiApi(OpenAiApi.builder().apiKey(apiKey).build())
        .defaultOptions(OpenAiChatOptions.builder()
                .model("gpt-4o-mini")
                .build())
        .build();
​
ChatClient client = ChatClient.create(chatModel);
String answer = client.prompt("ping").call().content();

生产仍建议 starter + yaml

多模型预期

  • 一个应用可有多个 ChatModel,用 Bean 名或 @Qualifier 区分。

  • 许多国产 / 代理网关仍用 spring-ai-starter-model-openai,改 base-urlapi-keymodel 即可。

  • 改了 base-url ≠ Image / Audio 一定可用;能力以该兼容端实际支持为准。


2.9 测试时怎么接模型

方式适用
真调用@SpringBootTest + 测试 Key(管费用与 CI 密钥)
Stub ChatModel单测业务编排,不花 token
Testcontainers向量库 / Redis 等周边;Chat Hello 通常不需要

最小 stub:

import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
​
ChatModel stub = prompt -> {
    AssistantMessage msg = new AssistantMessage("pong");
    return new ChatResponse(List.of(new Generation(msg)));
};
ChatClient client = ChatClient.create(stub);
assertThat(client.prompt("ping").call().content()).isEqualTo("pong");

Generation / ChatResponse 构造若随小版本调整,以你依赖中的签名为准;语义都是「固定返回一句助手消息」。)


2.10 常见问题排查

现象先查什么
找不到 ChatModel / Builder是否漏了 model starter;api-key 是否为空导致自动配置跳过;Boot 是否仍是 3.x
401 / 403环境变量名与 ${OPENAI_API_KEY} 是否一致;兼容网关是否要额外 Header
400:schema / strict / tools是否误开 strict(true) 却声明可选字段;Tool 名是否含空格等非法字符
.getResult() 为空先看 getResults();Hello 用 .content(),生产要处理空结果与异常
流式卡住或只有一块客户端是否未缓冲(curl -N);是否缺 WebFlux / SSE 配置
NoSuchMethodError是否手写了旧版 spring-ai-*;是否混入 Spring AI Alibaba 坐标 → 统一 BOM 2.0.1
Tool 挂了却不调2.0.1 fallback 默认关;确认挂在当前请求或 defaultTools(见第 8 章)

2.11 本章边界与验收

Docker Compose / Testcontainers 用于数据库、向量库等周边,不是 Chat Hello 的前提。

到这里应能:

  1. 用 BOM + model starter 建起最小工程(含密钥环境变量)

  2. 完成 /demo/sync/demo/stream 并完成 curl 验证

  3. 区分配置问题、鉴权问题与依赖冲突

下一章看一次调用里真正流动的对象:Message、Prompt、ChatResponse。