A.0Spring AI 1.x → 2.0 破坏性变更速查表
这是全站最应该打印出来贴在显示器旁的一页。 2.0 是架构级重构,下面每一行都是"照抄旧代码会出问题"的地方。左列是 1.x 的写法,右列是 2.0 的正确写法。
一、底座与依赖
| 项 | 1.x | 2.0 |
|---|---|---|
| Spring Boot | 3.x | 4.0 / 4.1(与 3.x 不兼容,不能混用) |
| Spring Framework | 6.x | 7.0,Jakarta EE 11 |
| Java | 17+ | 17+(官方支持到 26)。 Java 21 只是从源码构建 Spring AI 时的要求,不是运行时要求 |
| Jackson | Jackson 2 | Jackson 3(包名 com.fasterxml.jackson → tools.jackson,日期序列化默认行为也变了) |
| RAG Advisor 模块 | spring-ai-advisors-vector-store | spring-ai-vector-store-advisor(改名) |
| MCP 传输模块 groupId | io.modelcontextprotocol.sdk | org.springframework.ai |
| MCP Java SDK | 0.18.x | 1.0.x 起(随 2.0.0 发布) |
| 已移除的模块 | Azure OpenAI / openai-sdk / OCI GenAI / Vertex / CosmosDB / Hanadb | 移出主仓库,改由厂商或外置仓库维护 |
| 模型供应商(核心) | 十几种,各自实现 | 收敛为 7 家:OpenAI、Anthropic、Bedrock、Google GenAI、Mistral、DeepSeek、Ollama。 OpenAI / Anthropic / Google 改用厂商官方 SDK |
| 不再内置的供应商 | 智谱 AI、Azure OpenAI、OCI、Vertex | 智谱需引其独立仓库;Azure OpenAI 并入 spring-ai-openai;MiniMax 走 Anthropic 兼容协议 |
二、工具调用(改动最大的一块)
| 项 | 1.x | 2.0 |
|---|---|---|
| 执行循环在哪 | 藏在每个 ChatModel 实现内部,无法干预 | ToolCallingAdvisor,Advisor 链中的一等公民,可观察、可组合、可替换 |
| 关闭自动执行 | .internalToolExecutionEnabled(false)属性 spring.ai.*.chat.internal-tool-execution-enabled |
两者都已删除。改用: 单次 → .advisors(AdvisorParams.toolCallingAdvisorAutoRegister(false))全局 → spring.ai.chat.client.tool-calling.enabled=false |
| 函数式工具注册 | 裸 Function bean + .toolNames("name")( SpringBeanToolCallbackResolver 按名字解析) |
toolNames() 已删除。必须注册显式 ToolCallback:FunctionToolCallback.builder(name, fn).inputType(X.class).build() |
FunctionCallback | 存在 | 已彻底移除(1.0 起由 ToolCallback 取代) |
ToolCallAdvisor | 类名 | 改名为 ToolCallingAdvisor |
| Advisor 数量约束 | 无 | 链中只能有一个 ToolAdvisor,重复注册直接报错 |
| 工具调用次数上限 | 无(可无限调用) | 默认生效:单工具 40 次 / 合计 150 次,可用 spring.ai.tools.limits.* 调整 |
| 工具解析回退 | 默认开启 | 默认关闭:只有请求里显式附带的工具可执行(安全默认值) |
@Tool 方法可见性 | 有约束 | 放宽:public / package-private / private、静态或实例方法都可 |
| 工具 span 名 | tool_call <名> | execute_tool <名>;gen_ai.operation.name 从 framework 改为 execute_tool★ 按 span 名配的告警规则会静默失效,升级后必须回归 |
三、结构化输出
| 项 | 1.x | 2.0 |
|---|---|---|
| 校验与重试 | 自己写 try/catch + 重试 + 把错误回喂模型 | .entity(X.class, spec -> spec.validateSchema())框架自动带错误信息重试,默认最多 3 次 |
| 厂商原生约束 | 无统一入口 | spec.useProviderStructuredOutput():把 schema 作为 API 级约束发给厂商,比写在提示词里硬 |
BeanOutputConverter | 有 postProcessSchema(JsonNode) 扩展点 | 该扩展点已移除;schema 生成改由 JsonSchemaGenerator,会带 OpenAPI 风格 format 提示 |
| 可选字段判定 | Kotlin 可空字段会被当必填 | 已修正;@JsonProperty(required=false) 也不再算必填 |
四、会话记忆
| 项 | 1.x | 2.0 |
|---|---|---|
| conversationId | 有默认值(ChatMemory.DEFAULT_CONVERSATION_ID = "default"),忘了传会静默混用 |
必填。每次调用必须显式传 .param(ChatMemory.CONVERSATION_ID, id),缺失或传 null 立刻抛 IllegalArgumentException。
注意区分两个东西:参数键常量 CONVERSATION_ID 仍然存在、必须使用;
被删除的是默认值常量 DEFAULT_CONVERSATION_ID(2.0 不再提供「不传就用 default」这条退路,正是为了避免多用户静默串话) |
| Advisor builder | .conversationId("x") | 该方法已删除,改为每次调用传参 |
PromptChatMemoryAdvisor | 存在(省 Token,信息有损) | 整个类被移除,统一用 MessageChatMemoryAdvisor |
| 默认顺序常量 | DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER = HIGHEST_PRECEDENCE + 1000 | 改为 + 200;ToolCallingAdvisor 在 + 300默认记忆在工具循环之外,看不到中间工具消息 |
| JDBC 记忆表结构 | 靠 timestamp 排序 | 新增 sequence_id BIGINT 列与索引(MySQL 时间戳精度只到秒,同秒消息排序不确定)。旧库需回填 |
| 替代方案 | — | 社区项目 spring-ai-session 用事件溯源重做记忆,支持全部消息类型与自动压缩,计划 2.1 替换内置实现 |
五、配置与 Options
| 项 | 1.x | 2.0 |
|---|---|---|
.options 层级 |
spring.ai.openai.embedding.options.model |
已移除:spring.ai.openai.embedding.model范围:embedding / image / audio / moderation / OCR。chat 层仍保留 .options |
| Ollama 思考开关 | spring.ai.ollama.chat.think-option | spring.ai.ollama.chat.think |
| Options 可变性 | 有 setter、copy()、*Options#fromOptions() |
严格不可变,必须用 builder;改值用 .mutate()。集合返回不可修改视图,改它抛 UnsupportedOperationException |
| Options 合并 | ModelOptionUtils.merge() | 移到 ChatClient 层:ChatClient.Builder#combineWith() |
ChatModel 取默认选项 | getDefaultOptions() | 弃用,改 getOptions() |
| 默认 temperature | 模型实现里内置默认值 | 默认值被移除,需要时显式配置,否则可能报错或行为变化 |
| OpenAI 默认模型 | gpt-4o 系列 | 默认改为 gpt-5-mini,依赖隐式默认值的代码行为会变 |
| JSON 工具类 | ModelOptionsUtils 的 JSON 方法、JsonParser |
改用 JsonHelper / JacksonUtils / JsonSchemaUtils |
六、MCP
| 项 | 1.x | 2.0 |
|---|---|---|
| 服务端注解 | 用 @Tool 暴露 | @McpTool / @McpToolParam / @McpResource / @McpPrompt / @McpComplete,注解扫描自动注册 |
| 注解包路径 | org.springaicommunity.mcp.annotation.* | org.springframework.ai.mcp.annotation.* |
| 默认传输 | SSE | Streamable HTTP(SSE 已 deprecated,STDIO 用于本地) |
| 客户端配置写法 | 外部 JSON + servers-configuration | spring.ai.mcp.client.stdio.connections.<名>.command/args |
| 工具是否自动可用 | 较宽松 | 不会自动注册到 ChatClient,必须显式传 SyncMcpToolCallbackProvider 给 .tools() / .defaultTools() |
| 服务端入参校验 | 默认关闭 | 默认开启,失败返回 isError=true;用 validateToolInputs(false) 关闭 |
| 暴露面控制 | — | 新增 McpToolFilter bean;spring.ai.mcp.server.expose-mcp-client-tools |
| 安全与观测 | 需自己接 | 内置 Micrometer span / OpenTelemetry 指标;OAuth 2.0 与 API Key 通过 mcp-security 项目 |
七、2.0 新增、值得专门学的三个能力
① 渐进式工具披露
ToolSearchToolCallingAdvisor。工具上百个时不再全量塞给模型,
而是给它一个 toolSearchTool 按需检索。
官方实测省 34%~64% token,准确率无可测下降。
加 starter + 一行配置即可启用(第 7 章)。
② 结构化思考
AugmentedToolCallbackProvider + AgentThinking。
给工具 schema 动态追加 innerThought 字段,
模型调工具时顺便把推理填进来,你的代码拿到的是类型安全对象,
不用再从回复文本里用正则抠(第 9 章)。
③ 可组合的循环钩子
继承 ToolCallingAdvisor 重写
doInitializeLoop / doBeforeCall /
doAfterCall / doFinalizeLoop,
就能实现审批门、预算闸、事件推送,不必推翻整个循环(第 9 章)。
- 新项目 / 教学项目 → 直接 Spring Boot 4.1 + Spring AI 2.0,没有历史包袱。
- 存量 Boot 3.5.x 项目 → 先评估 Boot 4 的迁移成本(Boot 3.5 与 Framework 6.2 已于 2026-06-30 EOL), 在迁移完成前继续用 Spring AI 1.1.x 维护版本。
- 只想小修小补 → 留在 1.1.x,不要为了追新强行迁移。
Spring Initializr 只列出仍在 OSS 支持期内的版本线。3.5.x 已于 2026-06-30 停止 OSS 支持,所以被移出了下拉列表。
实测官方 start.spring.io 当前提供的选项只有 4.x:
4.2.0 (M1/SNAPSHOT)、4.1.2 (SNAPSHOT)、
4.1.1(默认)、4.0.9 (SNAPSHOT)、4.0.8。
Java 可选 17 / 21 / 25 / 26。
网上写着「Project 选 Maven,Language Java,Spring Boot 选当前稳定 3.x」的教程, 都写于 4.x 发布之前,已经过时。照做的话 Spring AI 2.0 根本用不了 (2.0 基于 Spring Framework 7.0 与 Jakarta EE 11,与 Boot 3.x 不兼容)。 选 4.1.1。
| 分支 | 首次发布 | OSS 支持结束 | Java 范围 | 状态 |
|---|---|---|---|---|
4.2.x | 2026-11(计划) | 2027-12 | — | 未发布 |
4.1.x | 2026-06 | 2027-07 | 17 ~ 26 | ✅ 当前推荐 |
4.0.x | 2025-11 | 2026-12 | 17 ~ 25 | 维护中(快到期) |
3.5.x | 2025-05 | 2026-06(已 EOL) | 17 ~ 25 | ❌ 已停止 OSS |
3.4.x | 2024-11 | 2025-12(已 EOL) | 17 ~ 24 | ❌ 已停止 OSS |
规律:每个小版本约有 12 个月 OSS 支持,Spring Boot 没有「LTS」概念
(这点和 Java 不同,容易误解)。所以「当前稳定版」是会变的——请以
spring.io/projects/spring-boot 的支持页为准,不要照抄教程里的版本号。
A.1命令速查
Docker / 基础设施
# ---------- 启动与停止 ----------
cd D:/springai-lab
docker compose up -d # 后台启动全部服务
docker compose up -d mysql redis pgvector zipkin # 只启动核心四个
docker compose ps # 查看状态(等 STATUS 变 healthy)
docker compose logs -f milvus # 跟踪某个服务日志
docker compose restart app # 重启应用容器
docker compose down # 停止并删除容器(保留数据卷)
docker compose down -v # ★ 同时删除数据卷(数据全丢,慎用)
# ---------- 连通性验证 ----------
docker exec -it sai-mysql mysql -uroot -proot123456 -e "SELECT VERSION();"
docker exec -it sai-redis redis-cli -a redis123456 ping # 期望 PONG
docker exec -it sai-pgvector psql -U ai -d vectordb -c "\dx" # 看 vector 扩展
curl http://localhost:9411/health # Zipkin
curl http://localhost:9091/healthz # Milvus
# ---------- 清理(磁盘不够时) ----------
docker system df # 看磁盘占用
docker system prune -a # 清理无用镜像与容器(不会删卷)
docker volume ls # 列出数据卷
Maven
mvn -v # 版本 + JDK 版本
mvn clean compile # 编译
mvn clean package -DskipTests # 打包(跳过测试,快)
mvn spring-boot:run # 直接运行
# 看 Spring AI 依赖树(排查"依赖没进来")
mvn -q dependency:tree -Dincludes=org.springframework.ai
# 强制更新依赖(镜像换了之后用)
mvn -U clean package -DskipTests
# 清本地仓库里某个包的缓存(解决"jar 损坏")
rm -rf ~/.m2/repository/org/springframework/ai
# 只跑某个测试类 / 某个方法
mvn test -Dtest=RagEvaluationTest
mvn test -Dtest=RagEvaluationTest#跑一遍RAG评测并输出报告
接口验证(curl)
# 普通 POST
curl -X POST http://localhost:8080/api/v1/chat/ask \
-H "Content-Type: application/json" \
-d '{"message":"退款要几天到账"}'
# 流式(-N 关闭缓冲才能看到逐字效果)
curl -N -X POST http://localhost:8080/api/v1/chat/stream \
-H "Content-Type: application/json" \
-d '{"message":"从1数到10"}'
# GET 带参数
curl "http://localhost:8080/api/v1/vector/search?q=退款&topK=5&threshold=0.5"
# 只看 HTTP 状态码
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/actuator/health
# 格式化 JSON 输出(装了 jq 的话)
curl -s ... | jq .
# 直连模型厂商 API 验证密钥(排除代码因素)
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"你好"}]}'
数据库
# ---------- PostgreSQL / pgvector ----------
docker exec -it sai-pgvector psql -U ai -d vectordb
-- 看每个租户灌了多少块
SELECT metadata->>'tenantId' AS tenant, COUNT(*)
FROM vector_store GROUP BY 1;
-- 看某个来源的块数与内容预览
SELECT id, LEFT(content, 60) FROM vector_store
WHERE metadata->>'source' = 'refund-policy.md' LIMIT 5;
-- 清空某个租户的数据(重建索引前)
DELETE FROM vector_store WHERE metadata->>'tenantId' = 't1';
-- 看索引是否建上(HNSW)
SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'vector_store';
# ---------- MySQL ----------
docker exec -it sai-mysql mysql -uroot -proot123456 springai_lab
-- 看会话记忆(排查"记忆串味")
SELECT conversation_id, type, LEFT(content, 50) AS preview, timestamp
FROM spring_ai_chat_memory
WHERE conversation_id = 't1|u1001|s1'
ORDER BY timestamp DESC LIMIT 10;
-- 成本归因:按意图统计
SELECT intent, COUNT(*), ROUND(AVG(cost_yuan), 4) AS avg_cost
FROM qa_audit GROUP BY intent ORDER BY avg_cost DESC;
-- 找出"检索没命中"的问题(知识盲区)
SELECT question, COUNT(*) FROM qa_audit
WHERE citation_count = 0
GROUP BY question ORDER BY COUNT(*) DESC LIMIT 20;
A.2配置模板全家桶
这是一份可以直接用的完整 application.yml,把课程里所有章节需要的配置都收进来了。 用不到的段落注释掉即可。
spring:
application:
name: springai-lab
profiles:
active: dev # dev / local / prod
# ==================== 模型配置 ====================
ai:
openai:
api-key: ${OPENAI_API_KEY:}
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
temperature: 0.7
max-tokens: 2048
connect-timeout: 30s
read-timeout: 120s
embedding:
# ★ 2.0 移除了 .options 这一层,直接写 model
model: text-embedding-3-small
ollama:
base-url: http://localhost:11434
chat:
options:
model: qwen2.5:7b
embedding:
model: nomic-embed-text
init:
pull-model-strategy: never
# ==================== 2.0:工具调用循环与工具检索 ====================
chat:
client:
tool-calling:
# 默认 true:ChatClient 自动注册 ToolCallingAdvisor 处理工具循环。
# 自研 Agent 要自己驱动循环时设为 false,或单次调用用
# AdvisorParams.toolCallingAdvisorAutoRegister(false)。
enabled: true
# advisor-order: 100 # 默认 HIGHEST_PRECEDENCE + 300
tool-search-advisor:
# 工具 > 50 个(尤其接了多个 MCP Server)时打开,省 34%~64% token
enabled: false
tool-index-type: regex # regex / lucene / vector
tools:
limits:
# 2.0 新增的默认保护:单工具每轮 40 次、合计 150 次
max-calls-per-tool-default: 40
max-total-tool-calls: 150
on-limit-exceeded: THROW # 或 RETURN_ERROR_RESPONSE
resolution:
fallback:
# 默认 false:只有本次请求显式附带的工具可执行。
# 打开会让"模型报出任何工具名就能执行",放大越权风险,生产保持 false。
enabled: false
# ==================== 重试(第 6 章) ====================
retry:
max-attempts: 3
on-client-errors: false # ★ 4xx 不重试
backoff:
initial-interval: 2s
multiplier: 2
max-interval: 10s
# ==================== 会话记忆(第 6 章) ====================
chat:
memory:
repository:
jdbc:
initialize-schema: always
# redis:
# time-to-live: 24h
# ==================== 向量库(第 4 章) ====================
vectorstore:
pgvector:
initialize-schema: true
table-name: vector_store
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536 # ★ 必须等于模型输出维度
max-document-batch-size: 10000
# milvus:
# client: { host: localhost, port: 19530 }
# collection-name: vector_store
# embedding-dimension: 1536
# index-type: IVF_FLAT
# metric-type: COSINE
# initialize-schema: true
# ==================== MCP 客户端(第 8 章) ====================
mcp:
client:
enabled: true
name: springai-lab
version: 1.0.0
initialized: true
toolcallback:
enabled: true # ★ 自动把 MCP 工具注册为 ToolCallback
stdio:
servers-configuration: classpath:mcp-servers.json
request-timeout: 60s
# ==================== 可观测性(第 10 章) ====================
chat:
observations:
log-prompt: false # 生产必须 false!
log-completion: false
# ==================== 数据源 ====================
datasource:
url: jdbc:mysql://localhost:3306/springai_lab?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 5000
max-lifetime: 1200000
data:
redis:
host: localhost
port: 6379
password: redis123456
timeout: 3s
# ==================== 上传限制(知识库文档) ====================
servlet:
multipart:
max-file-size: 50MB
max-request-size: 100MB
server:
port: 8080
shutdown: graceful # 优雅停机,避免正在处理的请求被中断
tomcat:
threads:
max: 200
connection-timeout: 10s
# ==================== 管理端点 ====================
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
tracing:
sampling:
probability: 0.2 # 生产 0.1~0.2;开发用 1.0
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
# ==================== Resilience4j(第 6 章) ====================
resilience4j:
retry:
instances:
aiChat:
max-attempts: 3
wait-duration: 1s
retry-exceptions:
- org.springframework.web.client.ResourceAccessException
circuitbreaker:
instances:
aiChat:
sliding-window-size: 20
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
# ==================== 业务自定义配置 ====================
app:
ai:
enabled: true # ★ 应急开关:false 则 AI 功能整体关闭
knowledge:
docs-path: classpath:docs/*.md
top-k: 5
similarity-threshold: 0.5
chunk-size: 600
agent:
max-steps: 8
max-tokens: 30000
max-millis: 120000
pending-action-expire-minutes: 15
high-risk-tools: submitRefund,createTicket,sendEmail,cancelOrder
pricing:
models:
deepseek-chat: { input: 1.0, output: 2.0 }
qwen-plus: { input: 0.8, output: 2.0 }
ollama-local: { input: 0.0, output: 0.0 }
# ==================== 日志 ====================
logging:
level:
root: info
com.example.springai: info
org.springframework.ai: warn # 生产用 warn,开发用 debug
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-}] %logger{36} - %msg%n"
file:
name: logs/springai-lab.log
logback:
rollingpolicy:
max-file-size: 100MB
max-history: 30
total-size-cap: 5GB
A.3异常排查对照表(按报错关键词找)
| 报错关键词 | 章节 | 原因与解决 |
|---|---|---|
No qualifying bean of type 'ChatModel' | 1 | 缺 starter 或 api-key 为空导致自动配置未生效 |
401 Unauthorized | 1 | 密钥错/过期,或环境变量未被读取(IDEA 启动配置勾选 Include system env) |
404 Not Found(模型接口) | 1 | base-url 多写了 /v1,简化到纯域名 |
429 Too Many Requests | 1/6 | 触发限流:加退避重试、降低并发、升级配额 |
SocketTimeoutException | 1 | 读超时太短,设 read-timeout: 120s |
Not enough variable values available | 2 | 模板占位符没传参数;或提示词里的 JSON 花括号需转义/换定界符 |
JsonParseException: Unexpected character | 3 | 模型用 ```json 包裹了输出,或前面有解释文字。提示词明确「只输出 JSON」 |
Cannot deserialize value ... from String | 3 | 模型带单位("899元")、日期格式不符。字段描述里写死格式 |
| 对象字段全是 null | 3 | JSON key 与 Java 字段名不一致,用 @JsonProperty 映射 |
expected 1536 dimensions, not 1024 | 4 | 建表维度与模型维度不一致,改 dimensions 或换模型(需重灌) |
extension "vector" is not available | 4 | 用了普通 postgres 镜像,必须用 pgvector/pgvector:pg16 |
relation "vector_store" does not exist | 4 | 设 initialize-schema: true,或手工执行 init SQL 后重启 |
| 检索结果永远为空 | 4/5 | 阈值太高 → 先设 0 看分数分布;或查询语言与语料语言不匹配 |
| 检索结果大量重复 | 4 | 重复灌库,灌前先 delete(filter) 做幂等 |
| 元数据过滤语法报错 | 4 | 字符串必须用单引号;元数据类型要统一 |
allowEmptyContext 抛异常 | 5 | 默认 false,检索为空会抛异常。设 true 并在提示词里定义空上下文回答 |
| 回答里出现知识库没有的信息 | 5 | 提示词没禁止编造 / 阈值太低捞进无关片段 / temperature 太高 |
| A 用户看到 B 用户的对话 | 2/6 | conversationId 固定值或未传;统一走 ConversationIds.of() |
| 重启后记忆丢失 | 6 | 用了 InMemory 实现,换 JDBC/Redis |
| 熔断一直不恢复 | 6 | 下游真故障,需人工介入检查密钥/配额/网络 |
| 降级方法未生效 | 6 | 降级方法必须 public,参数一致,末尾多一个 Throwable |
| 模型不使用工具 | 7 | 描述太模糊 / 模型不支持 function calling / 工具太多(>15) |
| 模型用错工具 | 7 | 工具职责重叠,合并或在描述里写「不要用于……」 |
| 工具异常导致 500 | 7 | 配 DefaultToolExecutionExceptionProcessor.alwaysThrow(false) |
| 工具被重复执行 | 7/9 | 自动注册的 ToolCallingAdvisor 执行了一次、你的循环又执行一次 → 设 AdvisorParams.toolCallingAdvisorAutoRegister(false);或写操作未幂等 |
| MCP 客户端「无法解析消息」 | 8 | Server stdout 被日志污染:设 web-application-type: none,禁止 System.out |
| MCP 工具列表为空 | 8 | 缺 ToolCallbackProvider bean,或 toolcallback.enabled=false |
| Agent 只调一次工具就结束 | 9 | 没关掉自动工具调用;或提示词没说明可多步 |
| Agent 死循环 | 9 | 没设最大步数;加重复调用检测 |
| Agent 卡住不动 | 9 | 工具返回空或异常,模型无法判断下一步;返回要带明确状态 |
| 确认接口「编号不存在」 | 9 | 待确认操作存在内存里,重启丢了 → 持久化 |
| Zipkin 看不到 trace | 10 | 采样率 0 / 端点地址错 / 缺依赖三者之一 |
| Prometheus 内存暴涨 | 10 | 指标标签基数过高(放了 userId/query),删掉高基数标签 |
| MDC 里 traceId 串了 | 10 | finally 里没 MDC.remove() |
| 容器内连不上数据库 | 11 | 用了 localhost,必须用 compose 里的 service 名 |
| 应用启动时数据库连接失败 | 11 | 用 depends_on: condition: service_healthy 等依赖真正就绪 |
| 容器日志时间差 8 小时 | 11 | 容器默认 UTC,加 tzdata 并设置 Asia/Shanghai |
A.4核心 API 速查
| 接口 / 类 | 关键方法 | 章节 |
|---|---|---|
ChatModel | call(Prompt) / call(String) / stream(Prompt) | 1 |
ChatClient | prompt().user(...).call().content() / .stream() / .entity(Class) | 1 |
ChatClient.Builder | defaultSystem / defaultAdvisors / defaultTools / defaultOptions | 1 |
PromptTemplate | builder().template(...).build() → create(Map) | 2 |
Message 家族 | SystemMessage / UserMessage / AssistantMessage / ToolResponseMessage | 2 |
Media | new Media(MimeType, Resource/URI/byte[]) | 2 |
ChatMemory | add / get / clear;ChatMemory.CONVERSATION_ID | 2/6 |
BeanOutputConverter<T> | getFormat() / convert(String) | 3 |
CallAdvisor | adviseCall(request, chain) / getOrder() / getName() | 3 |
EmbeddingModel | embed(String) / embedForResponse(List) | 4 |
VectorStore | add / delete / similaritySearch(SearchRequest) | 4 |
SearchRequest | builder().query().topK().similarityThreshold().filterExpression() | 4 |
Document | new Document(text, metadata);getText/getMetadata/getScore | 4 |
TokenTextSplitter | builder().withChunkSize().withKeepSeparator()...build();apply(docs) | 5 |
QuestionAnswerAdvisor | builder(vectorStore).searchRequest().promptTemplate().build() | 5 |
RetrievalAugmentationAdvisor | builder().documentRetriever().queryTransformers().queryAugmenter() | 5 |
ModelRouter(自定义) | route(TaskType);注入 Map<String, ChatClient> | 6 |
@Tool / @ToolParam | 注解方法即可;returnDirect 支持结果直出 | 7 |
ToolCallbacks | ToolCallbacks.from(bean...) | 7 |
ToolContext | 方法参数注入;getContext().get("userId") | 7 |
ToolExecutionExceptionProcessor | DefaultToolExecutionExceptionProcessor.builder().alwaysThrow(false) | 7 |
MethodToolCallbackProvider | builder().toolObjects(bean).build() | 8 |
McpSyncClient | listTools() / callTool(CallToolRequest) / getServerInfo() | 8 |
ToolCallingChatOptions | builder().toolCallbacks(...)。注意:internalToolExecutionEnabled() 已在 2.0 删除,关闭自动调用改用 AdvisorParams.toolCallingAdvisorAutoRegister(false) | 9 |
AdvisorParams | AdvisorParams.toolCallingAdvisorAutoRegister(false)(自定义 Agent 循环时用) | 9 |
ObservationRegistry | Observation.createNotStarted(name, registry).observe(...) | 10 |
MeterRegistry | counter(...) / timer(...) / summary(...) | 10 |
RelevancyEvaluator | builder().chatClientBuilder(...).build();evaluate(request) | 10 |
FactCheckingEvaluator | 同上,用于查幻觉 | 10 |
A.5提示词模板库(直接抄)
① 通用系统提示骨架
你是{公司}{角色},服务于{服务对象}。
## 你的职责
{用 2~3 条列出能做的事}
## 硬性规则
1. {最重要的一条:什么绝对不能做}
2. {信息不足时怎么办}
3. {越界问题时怎么办}
## 输出风格
{长度限制、语气、格式要求}
## 兜底
如果{无法处理的情况},请{具体动作}。
② RAG 问答模板
请严格依据下方【参考资料】回答用户问题。
【参考资料】
{question_answer_context}
【回答要求】
1. 只使用参考资料中的信息;资料不足时回答"根据现有资料无法确定"。
2. 结论后用 [序号] 标注来源。
3. 用自己的话组织,不要照抄原文。
4. 回答不超过 300 字。
【用户问题】
{query}
③ 信息抽取模板
你是一名信息抽取助手。请从下面的文本中提取结构化信息。
【抽取规则】
1. 找不到的字段返回 null(列表返回 []),严禁编造或推测。
2. {字段级特殊规则,如"金额只输出数字,不带单位"}。
3. 只输出 JSON 对象本身,不要解释,不要代码块标记。
【待抽取文本】
{input}
{format}
④ 意图分类模板
你是一个意图分类器。请判断用户问题属于哪一类。
【分类规则】
1. {类别 A 的特征} → CATEGORY_A
2. {类别 B 的特征} → CATEGORY_B
3. {类别 C 的特征} → CATEGORY_C
【注意】
- {歧义情况的判定规则,如"同时涉及 A 和 B 时归为 B"}。
- 不要输出解释,只输出 JSON。
【用户问题】
{question}
【最近对话】
{recentHistory}
⑤ Agent 任务规划模板
你是任务规划专家。请把用户需求拆解成可执行的步骤。
【可用工具】
{toolList}
【输出格式】
严格输出 JSON 数组:step / action / args / desc / risk
risk 取值 low(查询)medium(修改数据)high(删除·付款·发信)
【规则】
1. 步骤控制在 2~6 步,不要过度拆分。
2. 只使用列出的工具,禁止编造。
3. 不要输出 JSON 以外的内容。
【用户需求】
{requirement}
⑥ 对话摘要模板
请把下面的对话压缩成结构化摘要。
摘要必须保留(如果存在):
- 用户身份关键信息
- 涉及的所有编号、金额、日期
- 用户核心诉求与已承诺方案
- 尚未解决的问题
摘要不超过 150 字,要点式,不要客套话。
【已有摘要】
{previousSummary}
【新增对话】
{historyText}
A.6术语表
| 术语 | 解释 |
|---|---|
| LLM | Large Language Model,大语言模型。本课程的「模型」默认指它 |
| Token | 模型的计费与长度单位。中文约 1 字 ≈ 0.7~1 token。输入输出都计费 |
| Prompt | 提示词。一次请求的完整输入,包含消息列表与参数 |
| System / User / Assistant Message | 消息的三种角色:规则设定 / 用户输入 / 模型输出 |
| Temperature | 采样温度 0~2。越低越确定(抽取、分类用 0),越高越发散(创作可 0.8+) |
| Context Window | 上下文窗口。一次请求能容纳的最大 Token 数(含输入输出) |
| Embedding | 嵌入。把文本转成高维向量的过程;语义相近的文本向量距离也近 |
| 向量维度 | 向量的长度(如 1536)。必须与模型一致,且换模型必须重灌 |
| 余弦相似度 | 只比较向量方向、忽略长度的相似度度量。文本检索的标准选择 |
| ANN / HNSW / IVF | 近似最近邻及其常用索引结构。用少量召回率损失换取数量级的速度提升 |
| Chunk | 文本切分后的片段。RAG 中向量化的基本单位 |
| RAG | 检索增强生成。先检索资料再让模型基于资料回答 |
| TopK | 检索返回的最相似结果条数 |
| Similarity Threshold | 相似度阈值。低于它的检索结果被丢弃,是防幻觉的关键参数 |
| Rerank | 重排。对初步召回的结果做二次精排,提升精度 |
| Hybrid Search | 混合检索。向量检索 + 关键词检索(BM25)合并结果 |
| Tool Calling | 工具调用。模型输出「调用意图」,由你的代码真正执行 |
| Function Calling | 工具调用的另一种叫法(模型侧的机制名) |
| MCP | Model Context Protocol。把工具标准化的协议,支持跨进程跨语言 |
| Agent | 智能体。能自主决定多步执行的程序(模型 + 工具 + 循环 + 刹车) |
| ReAct | Reason + Act。思考→行动→观察的循环范式 |
| Planner | 规划器。先出完整步骤清单再执行 |
| HITL | Human-in-the-loop。人工确认环节,高风险操作必做 |
| Advisor | Spring AI 的切面机制,用于日志、记忆、RAG、限流等横切逻辑 |
| Observation / Span / Trace | 可观测性概念:一次操作 / 链路中的一个环节 / 一条完整链路 |
| LLM-as-a-Judge | 用模型给模型输出打分,用于效果评估 |
| Faithfulness(忠实度) | 回答是否有资料支撑的度量,是查幻觉的核心指标 |
| 幻觉 Hallucination | 模型编造看似合理但错误或不存在的信息 |
| 提示词注入 | 用户通过输入试图覆盖系统指令的攻击方式 |
| 越权 | 访问了当前身份无权访问的数据或功能 |
| 幂等 | 同一个请求执行多次与执行一次效果相同。写操作必须幂等 |
| 降级 Degradation | 主能力不可用时,退回到一个仍可用的替代方案 |
| 熔断 Circuit Breaker | 失败率过高时主动拒绝请求,给下游恢复时间 |
| 脱敏 | 日志/数据中隐藏敏感信息(手机号、身份证、密钥) |
A.7学习资源地图
| 资源 | 地址 | 说明 |
|---|---|---|
| Spring AI 官方文档 | https://docs.spring.io/spring-ai/reference/ | ★ 遇到 API 变化第一时间查这里 |
| Spring AI GitHub | https://github.com/spring-projects/spring-ai | 源码与示例;spring-ai-examples 目录很值得读 |
| Maven Central | https://central.sonatype.com | 确认依赖坐标与最新版本号 |
| Spring Initializr | https://start.spring.io | 生成项目骨架 |
| MCP 官方文档 | https://modelcontextprotocol.io | 协议规范与官方 Server 列表 |
| MCP Inspector | @modelcontextprotocol/inspector | 调试 MCP Server 的可视化工具 |
| pgvector | https://github.com/pgvector/pgvector | 索引类型与参数调优参考 |
| Milvus 文档 | https://milvus.io/docs | 索引选型与性能调优 |
| Ollama | https://ollama.com/library | 本地可用的模型列表(含中文模型) |
| Prompt Engineering Guide | https://www.promptingguide.ai | 提示词技巧系统梳理,有中文版 |
| Micrometer Tracing | https://docs.micrometer.io/tracing/reference/ | 链路追踪配置参考 |
spring-ai-examples 找可运行示例 → ④ 最后才去搜索引擎。
前三步能解决 90% 的「找不到符号」问题,而且不会看到过时的博客内容。
A.8上线自检清单(打印出来贴在工位)
🔐 安全
- 密钥全部走环境变量,仓库里搜不到
sk- - 身份从登录态取,不接受前端传的 userId
- 多租户检索带
tenantId过滤 - 高风险操作 100% 人工确认
- 确认接口校验归属与有效期
- 日志脱敏,Prompt 内容不明文记录
- 工具有超时、有返回条数限制
- 写操作幂等
🛡️ 稳定性
- 所有外部调用都有超时
- 只重试 5xx 与网络超时(4xx 不重试)
- 配了熔断与降级,且降级路径已实测
- 有应急开关能一键关闭 AI 功能
- 连接池上限合理,不超过数据库承载
- 容器化部署用 service 名而非 localhost
depends_on用service_healthy- 优雅停机已开启
📊 可观测性
- Trace 链路可在 Zipkin 查到
- 指标端点可访问,标签基数受控
- 每条请求落审计表(含 Token 与成本)
- 响应头带 traceId
- 日志 pattern 包含
%X{traceId} - 生产采样率降到 0.1~0.2
- 有成本看板与告警阈值
🎯 效果
- 评测集 ≥ 30 条,含知识库外问题
- 评测能一键跑,输出 Markdown 报告
- 指标达标(命中率 ≥ 80%、通过率 ≥ 75%)
- 质量门禁已接入 CI
- Prompt 外置到文件并纳入版本管理
- 检索阈值经数据调优,不是拍脑袋
- 有真实反馈闭环(点赞/点踩)
这 11 章的内容,本质上是在回答一个问题:怎么把一个「能跑通」的 AI Demo,变成「敢上生产」的系统。
答案不在某个高深的技术里,而在这些看起来很朴素的工程细节中: 身份要从服务端取、检索要有阈值、高风险操作要人工确认、每个 Agent 循环都要有刹车、 每次改动都要跑评测、每个功能都要能被观测。
把这份指南里的每一个「验收标准」都真正做到了,你就已经具备企业级 AI 应用开发的实战能力。 剩下的,交给时间和项目去打磨。