Spring AI从零学习路线与工作原理总览
Spring AI 是 Spring 生态中的 AI 应用工程化框架。它不训练大模型,而是把模型调用、Prompt、结构化输出、Embedding、VectorStore、RAG、Chat Memory、Advisor、Tool Calling 和可观测性包装成 Spring 风格的抽象、Bean 与自动配置。
它解决的是“Java 商业系统怎样安全、可测试、可替换、可观测地使用模型”,不是“怎样让模型本身变聪明”。模型仍可能幻觉、超时、限流、生成非法 JSON 或请求危险工具;Spring AI 提供工程接口,但业务边界、权限、评估和兜底仍要由应用负责。
本页是 Spring AI 专栏入口。你会先理解一次请求经过哪些对象、Spring Boot 为什么能够自动创建客户端、最小接口怎样运行、同步与流式有什么区别,再进入 ChatClient、Prompt、RAG、Tool Calling 和生产治理专页。
学习目标
完成本页后,你应该能够:
- 说明 Spring AI 与 Spring、Spring Boot、模型厂商 SDK 的关系。
- 区分
ChatModel、ChatClient、Prompt、Message、ChatOptions、ChatResponse和Generation。 - 解释 Starter、BOM、自动配置和条件装配如何生成模型 Bean。
- 解释从 Controller 到模型 HTTP API,再到响应解析的完整链路。
- 写出同步聊天和 SSE 流式响应的最小 Demo。
- 说明 Advisor 链怎样添加 Memory、RAG、安全、日志等横切能力。
- 区分 Embedding、VectorStore、RAG 和 Tool Calling 的职责。
- 理解 Spring AI 1.x、2.x Starter 命名和 API 可能不同,知道怎样避免版本混用。
- 知道生产项目为什么还必须补权限、限流、超时、评估、成本和审计。
- 根据启动失败、401、429、超时、空响应和解析失败选择第一批证据。
一、Spring AI在整个系统中的位置
flowchart TD
A["业务Controller/Service"] --> B["Spring AI应用抽象"]
B --> C["ChatClient与Advisor链"]
B --> D["ChatModel/EmbeddingModel"]
B --> E["VectorStore与RAG"]
B --> F["Tool Calling"]
D --> G["模型厂商适配器"]
G --> H["云模型API或本地推理服务"]
E --> I["向量数据库"]
F --> J["受控业务Service"]| 层 | 职责 | 不负责什么 |
|---|---|---|
| Spring Boot | Bean、配置、Web、Actuator、生命周期 | 不定义大模型推理算法 |
| Spring AI | 模型抽象、Prompt、Advisor、RAG、Tool API | 不保证模型回答事实正确 |
| Provider Adapter | 把通用请求转换成厂商协议 | 不决定业务权限和场景边界 |
| 模型服务 | Tokenize、推理、逐Token生成 | 不天然知道企业实时私有数据 |
| 业务系统 | 身份、权限、数据、事务、审计、兜底 | 不应让模型直接绕过它操作数据库 |
Spring AI 位于 AI 栏目而不是 Java 主线:Java/Spring Boot 是前置工程基础,模型、RAG、Agent 和 Spring AI 属于 AI 应用体系。
二、为什么不直接到处写HTTP请求
一个 Demo 可以直接调用:
httpClient.post()
.uri(modelUrl)
.header("Authorization", "Bearer " + apiKey)
.bodyValue(payload)
.retrieve();当项目变大后会出现:
- 不同厂商请求/响应结构散落在业务代码。
- API Key、Base URL、模型名和超时配置重复。
- 同步、流式、Tool Calling、Embedding 各自造轮子。
- Prompt 拼接和结构化输出缺少统一规范。
- 切换模型要修改大量 Controller/Service。
- Token、耗时、错误和 Trace 无法统一观测。
Spring AI 通过接口和适配器收敛这些变化。但“统一接口”不代表所有模型能力完全等价:Tool Calling、图片输入、JSON Schema、Token 统计、Reasoning 字段和错误码仍可能存在厂商差异。
三、核心对象关系
flowchart TD
A["ChatClient:面向应用的流式API"] --> B["Prompt:一次模型输入"]
B --> C["System/User/Assistant Message"]
B --> D["ChatOptions:模型名、温度、最大输出等"]
A --> E["Advisor Chain:调用前后增强"]
E --> F["ChatModel:模型调用抽象"]
F --> G["Provider请求"]
G --> H["ChatResponse"]
H --> I["Generation与AssistantMessage"]
H --> J["Usage/Metadata"]3.1 ChatModel
模型能力的底层抽象。接受 Prompt,返回 ChatResponse。不同 Provider Starter 提供具体实现。
3.2 ChatClient
面向应用的 Fluent Client。负责更方便地组合 system/user 文本、模板参数、Options、Advisor、Tool 和输出转换,再调用 ChatModel。
它不是远程模型本身,也不应该直接散落在 Controller 中承载业务权限和数据库操作。
3.3 Prompt
一次完整模型输入,通常包含多条 Message 和 ChatOptions。Prompt 不只是用户一句话,还包含系统规则、历史、RAG 资料和工具结果。
3.4 Message
常见角色:
- System:定义角色、规则和边界。
- User:当前用户输入。
- Assistant:历史模型输出或工具调用意图。
- Tool:工具执行结果,具体对象模型随版本适配。
3.5 ChatOptions
模型参数,例如 model、temperature、maxTokens。不是所有 Provider 都支持相同参数或相同范围。
3.6 ChatResponse和Generation
ChatResponse 通常包含一个或多个候选 Generation、元数据和 Token Usage。只调用 .content() 很方便,但会丢失 finish reason、usage、tool call 等诊断信息。
四、Spring Boot自动配置为什么能创建Bean
加入 Starter 后,Spring Boot 启动大致执行:
flowchart TD
A["Maven/Gradle引入Provider Starter"] --> B["自动配置类进入候选集合"]
B --> C["读取spring.ai.*配置属性"]
C --> D["检查Classpath类和Conditional条件"]
D --> E{"是否满足创建条件"}
E -->|"否"| F["Bean不创建或启动校验失败"]
E -->|"是"| G["创建Provider API Client"]
G --> H["创建ChatModel/EmbeddingModel Bean"]
H --> I["创建或注入ChatClient.Builder"]
I --> J["业务配置构建ChatClient"]自动配置不是魔法,依赖四类输入:
- Classpath 中存在正确 Starter 和传递依赖。
- Spring Boot/Spring AI 版本兼容。
- 配置属性能够绑定。
@ConditionalOnClass、@ConditionalOnMissingBean、Provider 开关等条件满足。
如果 ChatClient.Builder 注入失败,先查看 Condition Evaluation Report 和依赖树,不要先怀疑 API Key。
五、BOM和Starter版本为什么必须成套
Spring AI 版本迭代中,Artifact 名称和 API 会变化。常见差异示意:
| 版本线 | 常见Starter命名示意 | 风险 |
|---|---|---|
| 较早1.x文档 | spring-ai-openai-spring-boot-starter | 与新文档的包/API混用可能找不到类 |
| 新2.x文档 | spring-ai-starter-model-openai | 需要匹配对应Boot和BOM |
不能同时复制两套教程。项目必须选定一条版本线,并使用 BOM 统一 Spring AI 模块版本。
2.x 风格示例:
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.0</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-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>2.0.0 是本知识库当前示例基线,不代表永远适合所有项目。复制前确认项目 JDK、Spring Boot 和官方兼容矩阵。JDK 8 老项目通常不能直接使用新 Spring AI 版本,应评估升级、独立 AI 服务或兼容 HTTP Client 层。
六、查看依赖冲突的证据
Maven:
mvn dependency:tree -Dincludes=org.springframework.ai检查:
- 是否同时出现多个 Spring AI 版本。
- BOM 是否真正管理到每个模块。
- 是否混入 1.x 和 2.x Starter。
- Spring Boot 管理的 Jackson/Reactor/HTTP Client 是否冲突。
- 运行时 Classpath 是否与 IDE 编译 Classpath 一致。
常见异常:
ClassNotFoundException
NoSuchMethodError
NoClassDefFoundError
BeanDefinitionOverrideException这些通常首先指向依赖/版本问题,不是模型推理问题。
七、最小配置
spring:
ai:
openai:
api-key: ${MODEL_API_KEY}
base-url: ${MODEL_BASE_URL:https://api.openai.com}
chat:
options:
model: ${MODEL_NAME}
temperature: 0.2注意:
- API Key 只放环境变量、Secret Manager 或受控配置中心,不提交 Git。
- OpenAI-compatible 服务不代表完全兼容所有路径和字段。
base-url是否需要包含/v1取决于适配器与目标服务约定。- temperature 低只能减少随机性,不能保证事实正确。
- 模型名错误通常在第一次远程调用时暴露,不一定阻止应用启动。
八、创建ChatClient Bean
@Configuration
public class AiClientConfiguration {
@Bean
ChatClient businessChatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("""
你是企业技术助手。
只根据用户提供的事实和已授权资料回答。
不确定时明确说明不知道,不得编造。
""")
.build();
}
}文本块需要 JDK 15+。JDK 8 项目应使用普通字符串:
String systemText = "你是企业技术助手。"
+ "只根据已授权资料回答。"
+ "不确定时明确说明不知道。";defaultSystem 是默认规则,不是不可覆盖的安全边界。高风险权限必须由 Java 代码和数据查询层强制执行。
九、同步聊天完整Demo
DTO:
public record ChatRequest(
@NotBlank
@Size(max = 2000)
String message) {
}
public record ChatResult(
String answer,
String requestId) {
}JDK 8 使用普通 POJO 替代 record。
Service:
@Service
public class TechnicalAssistantService {
private final ChatClient chatClient;
public TechnicalAssistantService(ChatClient chatClient) {
this.chatClient = chatClient;
}
public ChatResult ask(String message, String requestId) {
String answer = chatClient.prompt()
.user(user -> user.text("""
请回答下面的技术问题:
{question}
要求:
1. 先给结论;
2. 再解释原因;
3. 不知道时明确拒答。
""")
.param("question", message))
.call()
.content();
if (answer == null || answer.trim().isEmpty()) {
throw new IllegalStateException("模型返回空内容");
}
return new ChatResult(answer, requestId);
}
}Controller:
@RestController
@RequestMapping("/api/ai")
public class TechnicalAssistantController {
private final TechnicalAssistantService assistantService;
public TechnicalAssistantController(
TechnicalAssistantService assistantService) {
this.assistantService = assistantService;
}
@PostMapping("/chat")
public ChatResult chat(
@Valid @RequestBody ChatRequest request,
@RequestHeader(value = "X-Request-Id", required = false)
String requestId) {
String effectiveRequestId = requestId == null
? UUID.randomUUID().toString()
: requestId;
return assistantService.ask(
request.message(),
effectiveRequestId);
}
}Controller 只接收参数和转换响应。场景路由、RAG 权限、Tool 权限、审计和降级放 Service/专门组件。
十、一次同步请求的内部过程
flowchart TD
A["Controller校验message"] --> B["Service调用chatClient.prompt"]
B --> C["合并defaultSystem和本次User模板"]
C --> D["绑定question模板变量"]
D --> E["执行Advisor调用前链"]
E --> F["构造Prompt和ChatOptions"]
F --> G["ChatModel转换为Provider请求"]
G --> H["HTTP Client发送并等待模型"]
H --> I["Provider完成逐Token推理"]
I --> J["适配器解析ChatResponse和Metadata"]
J --> K["执行Advisor调用后链"]
K --> L["content提取文本并返回"].call() 是阻塞式业务语义。底层实现即使使用异步 HTTP Client,当前 Web 请求仍要等待完整模型输出。
十一、为什么不能只取content
需要排查和成本治理时,应读取完整响应。具体 API 以版本为准,思路如下:
ChatResponse response = chatClient.prompt()
.user(message)
.call()
.chatResponse();
if (response == null || response.getResult() == null) {
throw new IllegalStateException("模型未返回Generation");
}
String content = response.getResult()
.getOutput()
.getText();
ChatResponseMetadata metadata = response.getMetadata();可观察:
- 实际模型名。
- Prompt/Completion/Total Token。
- Finish Reason。
- Provider Request ID。
- Tool Call 元数据。
不同版本中 getText()/getContent() 和 Metadata API 可能不同,复制时以当前 Javadoc 为准。
十二、流式响应是什么
模型仍然逐 Token 推理,流式只是把已生成片段尽早发送给客户端,降低首字等待时间,不会必然降低模型总计算时间。
flowchart TD
A["浏览器建立SSE连接"] --> B["Controller返回Flux"]
B --> C["ChatClient.stream发起流式模型请求"]
C --> D["Provider逐步返回Token/Chunk"]
D --> E["应用按背压和连接状态转发"]
E --> F["浏览器增量渲染"]
F --> G["完成/错误/取消时清理资源"]WebFlux 示例:
@RestController
@RequestMapping("/api/ai")
public class StreamingChatController {
private final ChatClient chatClient;
public StreamingChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping(
value = "/chat/stream",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(
@RequestParam
@Size(max = 2000)
String message) {
return chatClient.prompt()
.user(message)
.stream()
.content()
.timeout(Duration.ofSeconds(60));
}
}生产还要处理:
- 客户端断开后取消上游订阅。
- 网关/Nginx 缓冲和超时。
- SSE 心跳。
- 流中途失败后不能改 HTTP 状态码的边界。
- 内容安全检查是逐块还是完整后执行。
- Token/成本在流完成或取消时怎样记录。
十三、Advisor是什么
Advisor 在 ChatClient 调用前后增强请求,类似 AI 调用链中的中间件,但不要简单等同于 Servlet Filter 或 Spring AOP。
flowchart TD
A["原始用户请求"] --> B["安全/租户Advisor"]
B --> C["Memory Advisor"]
C --> D["RAG Advisor"]
D --> E["观测/审计Advisor"]
E --> F["ChatModel"]
F --> G["响应按Advisor链返回处理"]顺序会影响结果:
- 先做权限,再检索,避免召回越权资料。
- 问题改写应明确使用哪些历史。
- RAG 上下文添加后再统计 Prompt Token。
- 日志不能记录未脱敏 Prompt。
不要把所有业务写成一个巨型 Advisor。权限、检索、记忆、观测各自保持可测试职责。
十四、Memory不等于模型记住了用户
模型 API 通常是无状态的。Chat Memory 是应用保存历史,并在下一次请求时重新放入 Prompt。
flowchart TD
A["本轮用户消息"] --> B["按conversationId读取历史"]
B --> C["裁剪/摘要/权限校验"]
C --> D["历史与本轮问题拼成Prompt"]
D --> E["调用模型"]
E --> F["保存本轮User/Assistant消息"]风险:历史无限增长、Token 成本上升、不同用户 conversationId 越权、旧敏感数据进入新请求、摘要丢失事实。Memory 专栏内容在生产路线中继续展开。
十五、Embedding、VectorStore和RAG关系
flowchart TD
A["企业文档"] --> B["解析、清洗、切Chunk"]
B --> C["EmbeddingModel生成向量"]
C --> D["VectorStore保存向量和权限元数据"]
E["用户问题"] --> F["同一Embedding空间生成查询向量"]
F --> G["按权限过滤并召回Chunk"]
D --> G
G --> H["重排、阈值和引用选择"]
H --> I["RAG Advisor/Prompt加入资料"]
I --> J["ChatModel基于资料生成回答"]- Embedding 负责语义表示。
- VectorStore 负责向量存储和相似检索。
- RAG 负责“检索资料 + 组装上下文 + 生成 + 引用/拒答”的完整链路。
向量相似不等于资料有答案,RAG 也不能自动解决权限和幻觉。详见 RAG 深层页面。
十六、Tool Calling关系
模型只产生“想调用哪个工具及参数”的结构化意图。真正的 Java 方法由后端执行:
flowchart TD
A["用户请求查询资产"] --> B["模型生成Tool Call"]
B --> C["Spring AI解析工具名和参数"]
C --> D["后端校验身份、权限、参数和风险"]
D --> E["执行Java业务Service"]
E --> F["最小化工具结果"]
F --> G["结果回传模型继续生成"]模型不能决定用户是否有权限,也不能直接接受其生成的 tenantId/userId 作为可信身份。完整内容见:Spring AI Tool Calling。
十七、模型适配不等于无成本切换
统一 ChatModel 接口降低代码耦合,但切换模型仍需回归:
- Prompt 指令遵循差异。
- Tokenizer 和上下文窗口差异。
- Tool Calling Schema 支持差异。
- JSON/结构化输出稳定性。
- 图片、音频等多模态能力。
- Safety Filter 和拒答差异。
- 价格、TTFT、吞吐、限流与地区合规。
切换模型必须跑固定评估集和安全门槛,不能只看接口编译通过。
十八、生产请求应有哪些层
flowchart TD
A["认证用户请求"] --> B["参数、大小和内容类型校验"]
B --> C["租户、场景和数据权限"]
C --> D["限流、并发和成本预算"]
D --> E["场景路由:直接/RAG/Tool/异步"]
E --> F["Prompt/Advisor/模型调用"]
F --> G["输出解析、Schema和业务规则校验"]
G --> H["引用、安全和敏感信息检查"]
H --> I["返回或人工审核"]
I --> J["记录版本、Token、耗时、结果和反馈"]Spring AI 主要帮助 E–G 的框架集成;A–D、H–J 仍需要业务工程。
十九、错误分层
| 层 | 典型错误 | 第一批证据 |
|---|---|---|
| 依赖/启动 | Bean不存在、NoSuchMethodError | dependency tree、Condition Report |
| 配置 | API Key/Base URL/model未配置 | 脱敏配置来源、绑定结果 |
| 网络/TLS | DNS、握手、连接超时 | HTTP Client错误、DNS/TLS证据 |
| Provider认证 | 401/403 | Provider requestId、密钥权限/区域 |
| 限流配额 | 429 | Retry-After、配额、并发、Token速率 |
| 模型服务 | 5xx、过载 | Provider错误码、模型、区域、时间线 |
| Prompt/上下文 | 超长、指令冲突 | Token统计、Prompt版本、截断记录 |
| 输出 | 空内容、非法JSON | 原始响应脱敏样本、finish reason |
| RAG | 无召回/错召回/越权 | query、filter、TopK、score、chunk版本 |
| Tool | 参数错、越权、超时、重复写 | toolCallId、Schema、权限、幂等记录 |
二十、故障一:ChatClient.Builder注入失败
排查:
- 当前版本是否真的提供该自动配置 Bean。
- 是否引入了正确 Provider Starter,而不是只有 core 包。
mvn dependency:tree是否混用版本。- 自动配置是否被 exclude。
- 是否定义了冲突自定义 Bean。
- Condition Evaluation Report 哪个条件未满足。
二十一、故障二:请求返回401或403
检查:
- Key 是否从正确 Secret 注入,是否有不可见空格/换行。
- Base URL 和 Key 是否属于同一 Provider/区域。
- 模型是否需要额外项目、组织或资源权限。
- 代理是否移除了 Authorization Header。
- 不要把完整 Key 打进日志。
二十二、故障三:429后重试导致雪崩
错误做法:所有线程立即固定间隔重试三次。流量高峰会把一次 429 放大为更多请求。
正确思路:
- 尊重 Retry-After。
- 指数退避加随机抖动。
- 设置总重试预算和截止时间。
- 区分请求速率、Token速率和账户配额。
- 对非幂等 Tool 写操作不自动重试。
- 限流、排队、模型降级和友好失败组合。
二十三、故障四:模型返回空内容
可能原因:
- 响应主要是 Tool Call,而不是文本。
- Safety Filter 拦截。
- Finish Reason 表示长度截断。
- Provider 兼容层字段映射不完整。
- 流式响应被提前取消。
- 代码只读第一个 Generation,但实际结构不同。
需要看完整 ChatResponse/Provider 元数据,不只判断 .content()==null。
二十四、可观测性最小字段
结构化日志/Trace:
requestId, traceId, tenantId(脱敏/受控), scene,
promptVersion, modelProvider, modelName, modelParameters,
ragIndexVersion, embeddingModel, topK, retrievedChunkIds,
toolNames, toolResultCodes,
promptTokens, completionTokens, totalTokens,
ttftMs, totalDurationMs, finishReason, errorCode,
safetyResult, evaluationSampleFlag不记录完整 API Key、密码、医疗原文、身份证、未脱敏 Prompt 和完整工具返回值。
指标 Label 只使用低基数 scene/provider/model/result,不使用 userId、requestId、conversationId。
二十五、完整学习顺序
- 当前总览:对象、自动配置、版本、首个同步/流式请求。
- 从零到生产级掌握:串完整工程主线。
- 快速入门:验证依赖、配置和最小接口。
- ChatClient:深入同步、流式、Message、Options和响应。
- Prompt与结构化输出:模板、Schema、校验和注入风险。
- RAG知识库:建库、检索、权限、重排、引用和排查。
- Tool Calling:工具Schema、后端执行、权限、幂等和审计。
- 生产化治理:限流、超时、降级、观测、成本和评估。
- 商业生产场景:企业知识库、异常分析和业务工具。
- 从零到精通验收清单:逐项验收。
AI 通用原理先看:
二十六、常见误区
| 误区 | 准确结论 | 后果 |
|---|---|---|
| Spring AI用来训练模型 | 它是AI应用工程化框架 | 学错方向 |
| 引入Starter就自动生产可用 | 只完成Bean和协议接入 | 无权限、评估、兜底 |
| ChatClient等于模型 | 它是应用客户端,最终调用远程/本地模型 | 排障层级混乱 |
| 统一接口让模型无损切换 | 能力、参数和效果仍有差异 | 切换后质量/Tool退化 |
| temperature=0就不会幻觉 | 只降低随机性,不保证事实 | 高风险回答误用 |
| defaultSystem是安全边界 | Prompt规则可被攻击/冲突 | 越权工具和泄密 |
| 流式让模型计算更快 | 主要改善TTFT体验 | 总耗时/成本误判 |
| Memory是模型永久记忆 | 应用每次重新注入历史 | 上下文膨胀和越权 |
| RAG召回相似文档就够 | 还需权限、重排、阈值、引用和评估 | 错答/越权 |
| Tool参数来自模型可直接信任 | 模型输出是不可信输入 | 越权和错误写操作 |
| 混用1.x/2.x教程没关系 | Starter/API可能已变化 | 编译或启动失败 |
二十七、面试标准回答
Spring AI是什么
Spring AI是Spring生态的AI应用工程化框架,用Starter和自动配置创建ChatModel、EmbeddingModel、VectorStore等Bean,用ChatClient、Prompt、Advisor、RAG和Tool Calling把模型能力接入Java业务。它不训练模型,也不保证回答正确;权限、数据、事务、评估、安全、成本和兜底仍由业务系统负责。
ChatClient和ChatModel有什么区别
ChatModel是底层模型调用抽象,接受Prompt并返回ChatResponse;ChatClient是面向应用的Fluent API,在ChatModel之上组合system/user消息、模板参数、Options、Advisor、Tool和输出转换。ChatClient方便业务使用,但实际Provider能力差异仍要评估。
Spring AI自动配置原理是什么
Provider Starter把自动配置类和实现放入Classpath,Spring Boot读取spring.ai配置,通过Conditional检查类、属性和缺失Bean条件,创建Provider API Client及ChatModel/EmbeddingModel,再提供ChatClient.Builder。注入失败先查BOM/Starter依赖树和Condition Report,而不是先查模型Key。
Advisor是什么
Advisor是ChatClient调用前后的增强链,可加入Memory、RAG、安全、日志和观测。顺序会改变输入和结果,例如权限应在检索前生效,RAG加入上下文后再统计Token。Advisor是工程扩展点,不应把所有业务逻辑堆进一个巨型Advisor。
为什么Spring AI版本不能混用
Spring AI不同版本线的Starter名称、包、配置和API可能变化,例如旧1.x和新2.x风格Artifact不同。应选择与JDK/Spring Boot兼容的BOM统一管理全部模块,用dependency tree确认只有一套版本,再以该版本官方文档和Javadoc为准。
二十八、学习实验与验收
- 用 BOM 和 Provider Starter 启动应用,打印 Condition Report 解释 Bean 来源。
- 故意混入另一个 Spring AI 版本,观察 dependency tree 和异常,再统一版本。
- 调用 ChatModel 和 ChatClient,比较代码职责和完整响应元数据。
- 编写同步 Controller,限制输入长度并隐藏 API Key。
- 编写 SSE 流式接口,模拟客户端中断和上游超时。
- 添加两个 Advisor,交换顺序并观察 Prompt/Token变化。
- 使用错误 Base URL、Key、模型名分别观察网络、401和Provider错误。
- 模拟 429,验证指数退避、抖动和总重试预算。
- 让模型返回 Tool Call/空文本,证明不能只检查content。
- 切换两个模型,用固定评估集比较结构化输出、RAG和Tool能力。
验收时必须能回答:
- Spring AI、Spring Boot和模型服务分别负责什么?
- ChatModel、ChatClient、Prompt、Message和ChatResponse怎样关联?
- Starter和自动配置怎样产生Bean?
- 为什么BOM仍不能保证Boot/JDK一定兼容?
.call()和.stream()在延迟和错误处理上有什么区别?- Advisor顺序为什么影响权限、RAG和Token统计?
- Memory为什么仍然消耗上下文Token?
- RAG和Tool Calling为什么都不能让模型决定权限?
- 401、429、空内容和Bean不存在分别从哪层取证?
- 为什么模型切换必须做回归评估?
