云选科普

Spring AI 构建 RAG 应用:从结构化输出到工具调用

以岗位分析应用为例,拆解 Spring AI 中 ChatClient、RAG、Tool Calling 与 SSE 的组合方式,并总结内存向量库、自动配置、会话记忆和云服务器部署的取舍。

先把问题拆成三条链路

一个岗位分析类 AI 应用,通常同时面对三类需求:让模型理解用户输入、让回答引用内部资料、让模型在需要时访问外部业务数据。把三件事全部塞进系统提示词,原型可以启动,但很快会遇到知识过时、数字凭空生成和上下文难维护等问题。

更稳妥的做法是把应用拆成三条可独立替换的链路:

  • 会话链路:由 ChatClient 接收岗位描述和追问,保留有限的历史消息。
  • 知识链路:用 RAG 从岗位、技能和行业文档中检索相关片段,再交给模型生成回答。
  • 业务工具链路:用 Tool Calling 查询薪资、岗位目录或其他结构化数据,避免模型直接猜数。

Spring AI 的价值不在于替你完成业务设计,而在于为 ChatClient、Advisor、VectorStore 和工具调用提供统一的编排入口。小团队可以先用它跑通单体原型,之后再替换模型、向量库或数据源。

一套适合原型的技术组合

示例项目采用 Spring Boot、Spring AI、Spring AI Alibaba 和通义千问模型,文档使用 Apache Tika 解析,前端用原生 HTML 和 JavaScript。向量存储先使用内存实现,Embedding 使用 DashScope 的文本嵌入模型。

这套组合适合验证产品流程,但不应直接当作生产架构:内存向量库随进程重启丢失,薪资工具中的示例数据也不能替代真实、可审计的数据源。生产环境至少要把文档版本、来源、更新时间和数据权限纳入设计,并根据规模选择持久化向量库。

推荐的演进顺序是:

  1. 先让“岗位描述 → 结构化分析”单独可用。
  2. 再增加 SSE 流式输出,改善等待体验。
  3. 接入 RAG,让回答基于可追溯的内部资料。
  4. 最后接入 Tool Calling,把实时或结构化数据交给工具查询。

先做结构化输出,再做流式输出

同步分析接口可以让模型返回固定字段,例如自动化影响区间、分析摘要、可迁移技能、目标岗位和能力差距。使用 DTO 描述字段含义,再通过结构化输出转换器约束结果,比让前端解析一大段自然语言更容易测试。

示意流程如下:

岗位描述 → ChatClient → 模型 → 结构化转换 → AnalyzeResponse

追问接口则适合使用 SSE。后端以 Flux<String> 持续返回内容,浏览器通过 ReadableStream 读取并追加到对话区域。这里有一个容易忽略的工程取舍:严格的 JSON 格式指令和自然语言流式回答并不总是兼容。可以为“同步分析”和“流式追问”配置两个 ChatClient,前者强调字段约束,后者只负责可读文本,并在前端对最终结果做必要的兜底处理。

会话记忆也应设置上限。原型可以使用按 sessionId 保存的滑动窗口,例如只保留最近若干轮消息;长期运行时则需要考虑过期清理、并发访问、敏感信息脱敏和持久化方案,不能让内存无限增长。

RAG 的关键不是“接上向量库”

知识库可以按主题拆成岗位案例、行业自动化趋势、技能映射和薪资基准等文档。应用启动时完成文档读取、切分、嵌入和写入向量库;查询时由 Advisor 根据相似度取回若干片段,再把片段放进模型上下文。

一个可调试的 RAG 流程应至少保留这些信息:

  • 文档名称、版本和更新时间;
  • 分块后的唯一标识;
  • 检索使用的 topK 和相似度阈值;
  • 最终注入模型的片段;
  • 回答中显示的来源标记。

这样才能区分“知识库没有相关内容”“检索阈值太高”和“模型没有正确使用上下文”。对于薪资、政策和岗位要求等易变化内容,不要只依赖静态 Markdown;应定期更新或改为查询经过授权的业务数据库。

Tool Calling 负责查数据,不负责拍脑袋

薪资查询可以抽象成一个工具,输入职业名称和城市层级,返回数据源中的薪资区间。模型的职责是判断用户是否需要调用工具、填充参数并组织语言;工具的职责是校验参数、查询数据并返回结果。

@Tool(description = "查询指定职业和城市层级的薪资范围")
public String getSalaryRange(String roleName, String cityTier) {
    // 校验参数后查询受控数据源
    return querySalaryDatabase(roleName, cityTier);
}

不要把未核实的示例数字写进生产工具,也不要让工具直接接受任意 SQL。实际系统应增加参数白名单、超时、审计日志、权限控制和错误返回;对于没有数据的组合,明确返回“暂无数据”,让模型如实说明,而不是补一个看似合理的数字。

常见故障与排查顺序

自动配置和依赖冲突

预览版依赖可能注册尚未随当前发行包提供的自动配置,导致应用启动失败。遇到这类问题,先确认 Spring Boot、Spring AI 和适配器版本是否处于兼容组合,再查看启动日志中的具体配置类。临时排除未使用的多模态或音频自动配置可以帮助原型启动,但应记录版本和排除原因,升级后重新验证。

向量库依赖还可能间接引入 JDBC 自动配置。若只想运行内存原型,不要为了“以后会用到”提前加入数据库 starter;需要持久化时,再补齐驱动、连接配置和迁移脚本。

资源目录与前端渲染

资源扫描不能假设每个可选目录都存在。加载 PDF、Markdown 等目录时,应对不存在目录做容错并记录警告,而不是让整个应用退出。

流式回复如果直接进行 HTML 转义,Markdown 标题和列表会以原始符号显示。前端应明确选择安全的 Markdown 渲染策略,对模型输出做必要的 HTML 清理,同时把来源标记单独处理,避免把文档内容当成可执行 HTML。

部署到云服务器时怎么选

这类应用的资源瓶颈通常来自模型请求延迟、Embedding 批处理、向量检索和并发会话,而不是简单的网页静态资源。个人开发或演示阶段,一台普通云服务器即可承载 Spring Boot、前端和内存向量库;需要稳定运行时,再把数据库、向量库和对象存储拆分出来。

可以按下面的路径规划:

  • 个人验证:单台云服务器 + 外部模型 API + 内存向量库,重点验证交互和提示词。
  • 小团队内测:云服务器运行应用,使用持久化数据库或向量库,文档放对象存储,增加 HTTPS、日志和备份。
  • 生产服务:按并发拆分应用实例,使用托管数据库或高可用向量服务,对模型调用设置限流、重试和超时,并将知识库更新做成可审计任务。

如果只是调用云端模型,通常不必一开始购买 GPU 服务器;只有在自部署模型、需要本地推理或有明确数据隔离要求时,才把 GPU 成本纳入方案比较。

上线前的检查清单

  1. 结构化输出失败时,接口是否能返回可诊断错误。
  2. RAG 回答是否能展示来源、版本和更新时间。
  3. 工具调用是否有参数校验、权限、超时和审计记录。
  4. 会话历史是否限制长度,并处理过期数据。
  5. 模型、Embedding 和向量库版本是否固定并经过联调。
  6. 知识库中的薪资、政策等事实是否有负责人与更新周期。
  7. 云服务器、数据库和对象存储是否配置备份、监控与访问控制。

Spring AI 应用的落地重点,不是把更多能力一次性接入,而是让模型、检索和业务数据各自承担清晰职责。先用最小链路验证价值,再逐步替换内存组件、补齐治理能力,通常比一开始堆叠完整基础设施更容易控制成本和风险。

继续浏览

还想继续看,可以再看这些文章

适合想继续在同一主题下横向阅读、对比不同切入角度的用户。