Spring AI 生产级实战-Spring AI 概念速通

Spring AI 概念速通:把 Prompt / 结构化输出 / RAG / Tool Calling 串成一条"可落地"的开发链路


1. 这节解决的真实问题是什么(场景化)

你在业务里接入大模型,真正卡你的通常不是"能不能调通",而是这些问题:

  • 接口要稳定:AI 调用超时、抖动、限流、偶发 429/5xx,你怎么重试/降级?
  • 输出要可用 :你要的是 Ticket{type, priority, summary},但模型回你一段散文;"让它输出 JSON"也不一定靠谱
  • 知识要新且可控 :模型训练数据会"过期",你需要把私有文档/数据库塞进它的上下文(RAG / prompt stuffing)
  • 要能办事:不仅回答,还要能查订单/改库存/拉工单------这就进入 Tool Calling(工具调用)
  • 要能评估:上线后你得知道回答是否贴合意图、是否"胡说八道",要有可自动化的评测入口

2. 官方在讲什么(你自己的话讲清楚)

下面把 concepts 这一页的"概念"翻译成工程语言(每条后面标注原文小节):

  1. 模型不止 Chat :Spring AI 讨论的是一类"模型家族"(语言/图像/音频/Embedding 等),你要先选输入输出形态再谈落地(Models) (Home)
  2. Prompt 不是一段字符串 :在很多模型里,prompt 由多条消息组成,并且有 role(system / user 等),system 用来"定规矩/设上下文"(Prompts) (Home)
  3. Prompt Template 是工程能力 :提示词要参数化,把用户输入、安全约束、业务上下文拼成可复用模板;Spring AI 使用 StringTemplate 作为模板引擎(Prompt Templates) (Home)
  4. Embedding = 可计算的语义坐标 :把文本/图像等变成向量,用"距离"做相似度,最典型用途就是语义检索与 RAG(Embeddings) (Home)
  5. Tokens 是成本与上限 :token 既决定"能塞多少上下文"(context window),也决定"要花多少钱"(Tokens = Money)------输入输出都会计费(Tokens) (Home)
  6. 结构化输出是"字符串→对象"的转换问题 :模型输出天然是 String;就算你要求 JSON,它也可能不稳定;因此需要"结构化输出转换"(Structured Output) (Home)
  7. 给模型"补知识"三条路 :Fine-tuning(重且贵)、Prompt stuffing(把相关数据塞进 prompt)、Tool Calling(让模型调用你的 API);Spring AI 明确把 prompt stuffing 视为 RAG 的一种实现路径(Bringing Your Data & APIs...) (Home)
  8. RAG 的工程核心在 ETL + 分块 :先把文档拆成语义边界合理的小块,再控制每块大小占 token 上限的一小部分;查询时把"问题 + 相似块"塞回 prompt(Retrieval Augmented Generation) (Home)
  9. Tool Calling 是一条对话链路 :你要把工具定义(名称/描述/入参 schema)随请求发给模型;模型返回要调用的工具与参数;应用执行后再把结果回传给模型生成最终答案(Tool Calling) (Home)
  10. 评估不是玄学 :Spring AI 提供 Evaluator API 来做基础评测策略(Evaluating AI responses) (Home)

必要的精确锚定:

  • 原文在 Tokens 里给了一个非常工程化的提醒:"Tokens = Money" (Home)

3. 最小可跑:10--20 分钟跑通

下面用一个很典型的落地任务:工单分诊(结构化输出)

输入:用户描述;输出:type/priority/summary 三字段 JSON(映射成 Java record)。

3.1 依赖/配置

依赖(Maven/Gradle 任选) :先用 OpenAI Chat 做例子(你也可以换成其他模型 starter)。官方给的 starter 坐标是 spring-ai-starter-model-openai (Home)

xml 复制代码
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

application.yml(最小)(示例字段名按 Spring AI OpenAI 模块的属性体系组织;你至少要配置 key 和 model):

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini   # 示例:按你实际可用模型改

实战建议(不是原文事实):开发环境用环境变量注入 key,生产用 Vault/KMS/Secret 管理,避免 key 落盘。

3.2 关键代码(Controller + record)

Spring AI 的 ChatClient 是最顺手的"落地入口":它用 fluent API 组装 prompt,支持同步/流式调用,并且能把输出映射成实体(entity())。 (Home)

java 复制代码
package com.example.ai;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/ticket")
public class TicketController {

    private final ChatClient chatClient;

    public TicketController(ChatClient.Builder builder) {
        this.chatClient = builder.build(); // Spring Boot 自动注入 builder :contentReference[oaicite:18]{index=18}
    }

    @PostMapping("/triage")
    public TicketTriage triage(@RequestBody TicketRequest req) {

        // system:定规则;user:放业务输入(roles 的概念来自 "Prompts"):contentReference[oaicite:19]{index=19}
        return this.chatClient.prompt()
                .system("你是资深客服分诊助手,只输出结构化结果,不要输出多余解释。")
                .user(u -> u.text("""
                        下面是用户的工单描述:
                        {text}

                        输出字段要求:
                        - type: {bug|question|billing|feature}
                        - priority: {P0|P1|P2|P3}
                        - summary: 20字以内中文摘要
                        """).param("text", req.text()))
                .call()
                .entity(TicketTriage.class); // 把模型输出映射为 Java record :contentReference[oaicite:20]{index=20}
    }

    public record TicketRequest(String text) {}
    public record TicketTriage(String type, String priority, String summary) {}
}

3.3 运行与验证步骤

  1. 启动应用(确保 OPENAI_API_KEY 已设置)
  2. 调用:
bash 复制代码
curl -X POST http://localhost:8080/ticket/triage \
  -H "Content-Type: application/json" \
  -d '{"text":"升级后无法登录,提示token无效,影响所有用户"}'
  1. 预期输出(示例):
json 复制代码
{"type":"bug","priority":"P0","summary":"升级后登录失败,提示token无效"}

3.4 常见报错 1--2 个及修复

  • 报错:返回无法映射为对象 / JSON 解析失败

    原因:模型输出不是稳定 JSON(官方在 concepts 里明确说"即使你要求 JSON,本质仍是 String,而且不保证 100% 准确") (Home)

    修复:见下文"坑位 1:结构化输出别只靠嘴"。

  • 报错:请求无响应/看起来没真正调用模型

    原因:call() 只是选择同步模式,真正触发执行是在你调用 content()/chatResponse()/entity() 等终结方法时(这点非常容易误判) (Home)

    修复:确保你链路末尾真的调用了终结方法;需要打点就围绕终结方法做。


4. 生产化版本:真正能上线的改造

下面我按"这篇文章的主题链路"挑 4 个工程点,给你能直接套的改造方向(含哪些是官方概念,哪些是实践建议)。

4.1 超时/重试:把抖动当常态

OpenAI chat 模块提供了统一的重试属性前缀 spring.ai.retry,并列出了 max-attempts、指数退避等参数 (Home)

yaml 复制代码
spring:
  ai:
    retry:
      max-attempts: 3
      backoff:
        initial-interval: 1s
        multiplier: 2
        max-interval: 10s
      on-client-errors: false

实战建议(推断):

  • 只对"可恢复"的错误重试(429/5xx),4xx 通常直接失败更合理;
  • 外面再包一层你自己的熔断/限流(Resilience4j / gateway),避免雪崩。

4.2 成本控制:用 token 计费思维做预算

官方把"Tokens = Money"写得非常直白,并强调 token 限制(context window)决定你能塞多少上下文 (Home)

你可以这样工程化:

  • 每次请求拿 ChatResponse 读 token 使用量(metadata 里有 token 数;并解释"托管模型按 token 计费") (Home)
  • 结合模型单价做实时成本统计(实践建议)
  • RAG 场景下严格控制"召回块数量 + 每块长度",否则成本会线性上涨(与 RAG 分块建议对齐) (Home)

4.3 结构化输出:从"让它输出 JSON"升级到"可验证的对象"

官方在 concepts 里指出:结构化输出往往需要专门的提示 + 转换 ,甚至可能需要多次交互来满足格式 (Home)

而在 ChatClient API 里,你可以:

  • 用 .entity() 直接映射 record (Home)
  • 对支持原生结构化输出的模型,通过 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT 打开(并说明有些模型对"对象数组"不原生支持) (Home)

4.4 Tool Calling:让 AI "能办事",但责任边界在你这

官方把 Tool Calling 的链路说得很清楚:定义工具 schema → 模型决定调用 → 你执行 → 结果回填 → 模型生成最终回答 (Home)

上线时你必须补的"工程护栏"(实践建议):

  • 对工具入参做强校验(JSON schema 只是起点)
  • 每个工具都要审计日志(谁触发、传了什么、改了什么)
  • 对高风险工具加权限与二次确认(例如"退款/删除数据")

5. 你一定会踩的坑(至少 3 条)

坑 1:你以为"输出 JSON"就等于"结构化"

  • 现象:接口偶发返回多余字段、中文解释、或 JSON 不闭合,导致反序列化失败

  • 原因:官方明确说模型输出传统上是 String,要求 JSON 也不保证 100% 准确 (Home)

  • 排查顺序:

    1. 先把原始模型输出完整打到日志(注意脱敏)
    2. 统计失败样本:是多了文本?还是字段错?还是数组/嵌套不稳定?
  • 修复方案:

    • 优先:用 ChatClient.entity() 映射实体,并用"字段约束提示 + 严格 system 规则"减少漂移 (Home)
    • 进阶:对支持的模型启用原生 structured output 参数(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT) (Home)
  • 小测试验证:

    • 写一个参数化测试,喂 20 条典型工单描述,断言 type/priority/summary 非空且枚举合法;失败就把原文输出落盘回归。

坑 2:Prompt 模板占位符和 JSON 大括号打架

  • 现象:你在 prompt 里放 JSON 示例,结果 {} 被当成模板变量,渲染时报错或内容被替换

  • 原因:ChatClient 内部使用 PromptTemplate + TemplateRenderer,默认变量语法是 {}(StringTemplate) (Home)

  • 排查顺序:

    1. 定位是模板渲染阶段报错,还是模型输出阶段异常
    2. 搜索 prompt 内是否出现了未绑定的 {xxx}
  • 修复方案:

    • 改分隔符,例如用 < > 作为占位符(官方在 ChatClient 文档里给了示例) (Home)
  • 小测试验证:

    • 对包含 JSON 示例的 prompt 做单测:渲染结果必须包含原始 JSON 字符串不变。

坑 3:RAG 一上来就"塞一堆文档",token 爆炸 + 成本爆炸

  • 现象:请求变慢、429、账单飙升,回答还不一定更准

  • 原因:官方强调 token 有上限(context window)且直接影响计费;RAG 分块还要求每块大小只是 token 上限的一小部分 (Home)

  • 排查顺序:

    1. 打印每次召回的文档块数量、每块字符数/估算 token
    2. 观察 ChatResponse 的 token 使用量(用它做仪表盘) (Home)
  • 修复方案:

    • 限制 topK、限制 chunk 大小、优先语义边界切分(段落/表格/代码方法不要拦腰切) (Home)
  • 小测试验证:

    • 固定一组问题,比较 topK=3/5/8 的正确率与 token 成本,选"性价比拐点"。

坑 4:你以为 call() 就已经发请求了

  • 现象:你加了埋点/计时,发现"call 了但没耗时",或链路里没看到外部请求
  • 原因:官方写得很明确:call() 不触发执行,真正调用发生在 content()/chatResponse()/responseEntity() 等终结方法 (Home)
  • 排查顺序:把埋点放到终结方法附近;或者直接返回 ChatResponse 看 metadata
  • 修复方案:统一封装一个 AiGateway,只暴露"终结后的结果",把时延/异常/成本统计放里面(实践建议)
  • 小测试验证:mock 外部模型(或用 stub server),断言终结方法被调用时才触发一次请求。

6. Checklist(10 条以内)

  • 明确本次能力目标:纯聊天 / 结构化输出 / RAG / Tool Calling / 评测
  • 用 system message 固定"输出边界/安全边界"(role 概念) (Home)
  • Prompt 模板参数化,避免字符串拼接地狱(并注意 {} 分隔符冲突) (Home)
  • 结构化输出别只靠"请输出 JSON",用 .entity() 或 structured output 能力 (Home)
  • 记录 token 使用量,做成本仪表盘(Tokens = Money) (Home)
  • RAG:先 ETL/分块/入库,再检索回填;chunk 切分遵守语义边界与 token 比例 (Home)
  • Tool Calling:工具入参强校验 + 审计日志 + 权限控制 (Home)
  • 配置重试退避(429/5xx),并准备熔断/限流 (Home)
  • 关键链路可回归:用固定样本集做自动化评测(Evaluator API) (Home)
  • 所有原始输入/输出日志都要脱敏与分级存储(实践建议)

7. 参考


相关推荐
Funny_AI_LAB6 分钟前
重构 Coding Agent:当 LLM 没有 KV Cache 时,上下文工程该怎么做?
人工智能·经验分享·语言模型·重构
GISMagic9 分钟前
AI 时代的软件工程与架构能力提升路线
人工智能·架构·软件工程
中伟视界12 分钟前
化工检修作业新规明日施行,边缘AI如何守好现场?
人工智能
喵个咪12 分钟前
GoWind Admin|风行 — 开箱即用的企业级全栈中后台框架:AI 模块
后端·go·ai编程
OpenCSG14 分钟前
OpenCSG Agentic-27B 正式开源:27B Dense 模型,Agent 执行能力实现全面跃升
人工智能·开源·opencsg
Elastic 中国社区官方博客21 分钟前
使用 Lucene 搜索你的 Bean —— Elasticsearch
大数据·开发语言·人工智能·elasticsearch·搜索引擎·全文检索·lucene
雪兽软件22 分钟前
AI爆改学术研究?
人工智能·学术研究
合调于形30 分钟前
Rengong zhzzneng 《人工智能》词条汉语拼音字母标调拼写实测案例
人工智能·自然语言处理·人机交互·语音识别·学习方法
cg501732 分钟前
大模型 SFT(监督微调)该怎么做?(实践版)
人工智能·深度学习·机器学习
Qt云程序员38 分钟前
选型参考:光刃RPA 与常见 RPA / 自动化方案的对比
人工智能·自动化·rpa