Spring AI 的 ChatClient 把系统要求、用户问题和模型调用组织成一个调用链。先用本地固定的 ChatModel 跑通它,可以看清框架传递了什么,也能在没有模型密钥时复现结果。
本例固定 Spring AI 2.0.2、JDK 21、Maven 3.9.9,使用 Spring Boot 4.1.2 parent 管理依赖。官方兼容说明列出 Spring AI 2.0.x 支持 Boot 4.0.x/4.1.x;这里运行的是普通 Java 入口,没有启动 Boot Web 应用或验证自动配置。
配置工程
下载完整示例工程,解压进入 spring-ai 目录,使用 JDK 21 和 Maven 3.9.9 运行:
mvn package dependency:copy-dependencies -DincludeScope=runtime
java -cp 'target/classes:target/dependency/*' site.ChatClientDemo
首次构建需要下载公开依赖。命令使用 macOS/Linux 类路径分隔符;Windows 未在本轮验证。 本轮容器运行输出:
[本地固定响应] 线程池复用工作线程并管理并发资源。
modelCalls=1
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>4.1.2</version><relativePath/></parent>
<groupId>site.cxykk.examples</groupId><artifactId>phase8-spring-ai</artifactId><version>1.0.0</version>
<properties><java.version>21</java.version><spring-ai.version>2.0.2</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>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-client-chat</artifactId></dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency>
</dependencies>
<build><plugins>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>3.8.1</version></plugin>
</plugins></build>
</project>
工程只引入 ChatClient 模块,没有模型厂商 starter。这个选择意味着程序不会自动发现 API 密钥或向外部模型发请求。
用一个本地模型看清调用关系
LocalChatModel 实现 Spring AI 的 ChatModel 接口,接收 Prompt 后直接返回固定文字。它只是测试替身,不具有理解或生成能力。
package site;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
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;
import org.springframework.ai.chat.prompt.Prompt;
/** A deterministic test fixture. It never generates text using an LLM. */
public final class LocalChatModel implements ChatModel {
private final Function<Prompt, String> response;
public final AtomicInteger calls = new AtomicInteger();
public Prompt lastPrompt;
public LocalChatModel(Function<Prompt, String> response) { this.response = response; }
@Override public ChatResponse call(Prompt prompt) {
calls.incrementAndGet();
lastPrompt = prompt;
return new ChatResponse(List.of(new Generation(new AssistantMessage(response.apply(prompt)))));
}
}
package site;
import org.springframework.ai.chat.client.ChatClient;
public final class ChatClientDemo {
public static void main(String[] args) {
var fixture = new LocalChatModel(prompt -> "[本地固定响应] 线程池复用工作线程并管理并发资源。");
var client = ChatClient.builder(fixture).defaultSystem("用一句话回答 Java 问题。").build();
String answer = client.prompt().user("什么是线程池?").call().content();
if (answer == null || answer.isBlank()) throw new IllegalStateException("没有文本回答");
System.out.println(answer);
System.out.println("modelCalls=" + fixture.calls.get());
}
}
defaultSystem 设置系统消息,user 提供用户问题,call().content() 走同步文本路径。自动测试检查两种消息仍然分别传到模型,并验证模型层异常会向调用者传播。固定响应不依赖问题内容,因此不能用这个例子评估回答质量。
接真实模型时仍需补哪些工作
真实接入需要选择相应 ChatModel 实现,核对厂商依赖、鉴权、模型名称、接口格式和超时。上一篇使用 Responses HTTP 格式,不表示所有 Spring AI 模型适配器都使用同一路径或支持相同功能。
如果通过 Spring Boot starter 创建客户端,还要单独验证自动配置、配置属性和真实模型连接。本文没有把这些未运行的步骤写成可直接复制的成功方案,也没有验证流式输出、重试、可观测性或生产部署。
接下来可以保留这个本地模型替身,把“问题前面增加资料”的过程独立测出来,再接真实模型。这就是下一篇最小 RAG 实验的范围。