Spring AI 2.0 学习指南企业级智能应用开发实践 环境准备 ⌂ 全部教程

APPENDIX速查手册与排错表

这一页是你学习过程中最常回来翻的。命令、配置模板、异常对照、术语解释,都集中在这里。 建议收藏这个页面的锚点。

A.0Spring AI 1.x → 2.0 破坏性变更速查表

这是全站最应该打印出来贴在显示器旁的一页。 2.0 是架构级重构,下面每一行都是"照抄旧代码会出问题"的地方。左列是 1.x 的写法,右列是 2.0 的正确写法。

一、底座与依赖

项1.x2.0
Spring Boot3.x4.0 / 4.1(与 3.x 不兼容,不能混用)
Spring Framework6.x7.0,Jakarta EE 11
Java17+17+(官方支持到 26)。
Java 21 只是从源码构建 Spring AI 时的要求,不是运行时要求
JacksonJackson 2Jackson 3(包名 com.fasterxml.jackson → tools.jackson,日期序列化默认行为也变了)
RAG Advisor 模块spring-ai-advisors-vector-storespring-ai-vector-store-advisor(改名)
MCP 传输模块 groupIdio.modelcontextprotocol.sdkorg.springframework.ai
MCP Java SDK0.18.x1.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.x2.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.x2.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.x2.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.x2.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-optionspring.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.x2.0
服务端注解用 @Tool 暴露@McpTool / @McpToolParam / @McpResource / @McpPrompt / @McpComplete,注解扫描自动注册
注解包路径org.springaicommunity.mcp.annotation.*org.springframework.ai.mcp.annotation.*
默认传输SSEStreamable HTTP(SSE 已 deprecated,STDIO 用于本地)
客户端配置写法外部 JSON + servers-configurationspring.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,不要为了追新强行迁移。
常见困惑:为什么 start.spring.io 里选不到 Spring Boot 3.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。

Spring Boot 版本支持时间线(官方支持页,2026-09 核对):
分支首次发布OSS 支持结束Java 范围状态
4.2.x2026-11(计划)2027-12—未发布
4.1.x2026-062027-0717 ~ 26✅ 当前推荐
4.0.x2025-112026-1217 ~ 25维护中(快到期)
3.5.x2025-052026-06(已 EOL)17 ~ 25❌ 已停止 OSS
3.4.x2024-112025-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 Unauthorized1密钥错/过期,或环境变量未被读取(IDEA 启动配置勾选 Include system env)
404 Not Found(模型接口)1base-url 多写了 /v1,简化到纯域名
429 Too Many Requests1/6触发限流:加退避重试、降低并发、升级配额
SocketTimeoutException1读超时太短,设 read-timeout: 120s
Not enough variable values available2模板占位符没传参数;或提示词里的 JSON 花括号需转义/换定界符
JsonParseException: Unexpected character3模型用 ```json 包裹了输出,或前面有解释文字。提示词明确「只输出 JSON」
Cannot deserialize value ... from String3模型带单位("899元")、日期格式不符。字段描述里写死格式
对象字段全是 null3JSON key 与 Java 字段名不一致,用 @JsonProperty 映射
expected 1536 dimensions, not 10244建表维度与模型维度不一致,改 dimensions 或换模型(需重灌)
extension "vector" is not available4用了普通 postgres 镜像,必须用 pgvector/pgvector:pg16
relation "vector_store" does not exist4设 initialize-schema: true,或手工执行 init SQL 后重启
检索结果永远为空4/5阈值太高 → 先设 0 看分数分布;或查询语言与语料语言不匹配
检索结果大量重复4重复灌库,灌前先 delete(filter) 做幂等
元数据过滤语法报错4字符串必须用单引号;元数据类型要统一
allowEmptyContext 抛异常5默认 false,检索为空会抛异常。设 true 并在提示词里定义空上下文回答
回答里出现知识库没有的信息5提示词没禁止编造 / 阈值太低捞进无关片段 / temperature 太高
A 用户看到 B 用户的对话2/6conversationId 固定值或未传;统一走 ConversationIds.of()
重启后记忆丢失6用了 InMemory 实现,换 JDBC/Redis
熔断一直不恢复6下游真故障,需人工介入检查密钥/配额/网络
降级方法未生效6降级方法必须 public,参数一致,末尾多一个 Throwable
模型不使用工具7描述太模糊 / 模型不支持 function calling / 工具太多(>15)
模型用错工具7工具职责重叠,合并或在描述里写「不要用于……」
工具异常导致 5007配 DefaultToolExecutionExceptionProcessor.alwaysThrow(false)
工具被重复执行7/9自动注册的 ToolCallingAdvisor 执行了一次、你的循环又执行一次 → 设 AdvisorParams.toolCallingAdvisorAutoRegister(false);或写操作未幂等
MCP 客户端「无法解析消息」8Server stdout 被日志污染:设 web-application-type: none,禁止 System.out
MCP 工具列表为空8缺 ToolCallbackProvider bean,或 toolcallback.enabled=false
Agent 只调一次工具就结束9没关掉自动工具调用;或提示词没说明可多步
Agent 死循环9没设最大步数;加重复调用检测
Agent 卡住不动9工具返回空或异常,模型无法判断下一步;返回要带明确状态
确认接口「编号不存在」9待确认操作存在内存里,重启丢了 → 持久化
Zipkin 看不到 trace10采样率 0 / 端点地址错 / 缺依赖三者之一
Prometheus 内存暴涨10指标标签基数过高(放了 userId/query),删掉高基数标签
MDC 里 traceId 串了10finally 里没 MDC.remove()
容器内连不上数据库11用了 localhost,必须用 compose 里的 service 名
应用启动时数据库连接失败11用 depends_on: condition: service_healthy 等依赖真正就绪
容器日志时间差 8 小时11容器默认 UTC,加 tzdata 并设置 Asia/Shanghai

A.4核心 API 速查

接口 / 类关键方法章节
ChatModelcall(Prompt) / call(String) / stream(Prompt)1
ChatClientprompt().user(...).call().content() / .stream() / .entity(Class)1
ChatClient.BuilderdefaultSystem / defaultAdvisors / defaultTools / defaultOptions1
PromptTemplatebuilder().template(...).build() → create(Map)2
Message 家族SystemMessage / UserMessage / AssistantMessage / ToolResponseMessage2
Medianew Media(MimeType, Resource/URI/byte[])2
ChatMemoryadd / get / clear;ChatMemory.CONVERSATION_ID2/6
BeanOutputConverter<T>getFormat() / convert(String)3
CallAdvisoradviseCall(request, chain) / getOrder() / getName()3
EmbeddingModelembed(String) / embedForResponse(List)4
VectorStoreadd / delete / similaritySearch(SearchRequest)4
SearchRequestbuilder().query().topK().similarityThreshold().filterExpression()4
Documentnew Document(text, metadata);getText/getMetadata/getScore4
TokenTextSplitterbuilder().withChunkSize().withKeepSeparator()...build();apply(docs)5
QuestionAnswerAdvisorbuilder(vectorStore).searchRequest().promptTemplate().build()5
RetrievalAugmentationAdvisorbuilder().documentRetriever().queryTransformers().queryAugmenter()5
ModelRouter(自定义)route(TaskType);注入 Map<String, ChatClient>6
@Tool / @ToolParam注解方法即可;returnDirect 支持结果直出7
ToolCallbacksToolCallbacks.from(bean...)7
ToolContext方法参数注入;getContext().get("userId")7
ToolExecutionExceptionProcessorDefaultToolExecutionExceptionProcessor.builder().alwaysThrow(false)7
MethodToolCallbackProviderbuilder().toolObjects(bean).build()8
McpSyncClientlistTools() / callTool(CallToolRequest) / getServerInfo()8
ToolCallingChatOptionsbuilder().toolCallbacks(...)。注意:internalToolExecutionEnabled() 已在 2.0 删除,关闭自动调用改用 AdvisorParams.toolCallingAdvisorAutoRegister(false)9
AdvisorParamsAdvisorParams.toolCallingAdvisorAutoRegister(false)(自定义 Agent 循环时用)9
ObservationRegistryObservation.createNotStarted(name, registry).observe(...)10
MeterRegistrycounter(...) / timer(...) / summary(...)10
RelevancyEvaluatorbuilder().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术语表

术语解释
LLMLarge 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工具调用的另一种叫法(模型侧的机制名)
MCPModel Context Protocol。把工具标准化的协议,支持跨进程跨语言
Agent智能体。能自主决定多步执行的程序(模型 + 工具 + 循环 + 刹车)
ReActReason + Act。思考→行动→观察的循环范式
Planner规划器。先出完整步骤清单再执行
HITLHuman-in-the-loop。人工确认环节,高风险操作必做
AdvisorSpring 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 GitHubhttps://github.com/spring-projects/spring-ai源码与示例;spring-ai-examples 目录很值得读
Maven Centralhttps://central.sonatype.com确认依赖坐标与最新版本号
Spring Initializrhttps://start.spring.io生成项目骨架
MCP 官方文档https://modelcontextprotocol.io协议规范与官方 Server 列表
MCP Inspector@modelcontextprotocol/inspector调试 MCP Server 的可视化工具
pgvectorhttps://github.com/pgvector/pgvector索引类型与参数调优参考
Milvus 文档https://milvus.io/docs索引选型与性能调优
Ollamahttps://ollama.com/library本地可用的模型列表(含中文模型)
Prompt Engineering Guidehttps://www.promptingguide.ai提示词技巧系统梳理,有中文版
Micrometer Tracinghttps://docs.micrometer.io/tracing/reference/链路追踪配置参考
遇到 API 冲突时的排查顺序: ① 查官方文档当前的 API 名称 → ② 在 IDEA 里 Ctrl+Alt+B 看接口实现 → ③ 去 GitHub 的 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 应用开发的实战能力。 剩下的,交给时间和项目去打磨。