1. 开场:第四篇了,聊聊Spring AI Alibaba真正的落地场景
先说一个我自己的感受。前面三篇我们把Spring AI Alibaba的模型接入、Prompt模板、输出解析这些基础能力过了一遍,说实话,光靠这些能力你只能做一个"能聊天"的Demo。真正让这个框架有价值的,是当你把大模型跟企业的数据、业务逻辑、系统操作打通之后。RAG、NL2SQL、Agent这三件事,恰恰是Spring AI Alibaba在底层帮你做了大量封装的地方。
这篇我打算直接从实战角度出发,把三个核心场景完整拆开讲:怎么把企业内部文档喂给大模型做检索增强(RAG),怎么让大模型直接对数据库写查询语句(NL2SQL),以及怎么把一个有工具调用能力的Agent在Spring Boot项目里跑起来。
这篇文章适合谁?你已经掌握Spring AI Alibaba的基本用法,能调通一个聊天接口,但想更进一步,把它接到真实业务系统里的开发者。我会把每一步的配置、代码、常见坑都写清楚。另外,文中的方案都基于Spring AI Alibaba 1.0.0-M3.1版本,如果你用的是更新版本,留意一下API的兼容性。
2. 先理清Spring AI Alibaba的组件架构
2.1 它不是Spring Cloud Alibaba,别搞混
刚接触Spring AI Alibaba的同事经常问一个问题:它跟Spring Cloud Alibaba是一回事吗?这个问题很关键,因为它决定了你后续排错的方向。
Spring Cloud Alibaba负责的是微服务治理,包括服务注册发现、配置中心、限流熔断这套东西。而Spring AI Alibaba是阿里云通义千问团队主导的一个"AI应用开发框架",它的目标是让Java开发者能快速把大模型能力集成到业务系统里,同时提供一套统一抽象,避免被某个模型厂商绑定死。
换句话说,Spring Cloud Alibaba解决的是"微服务怎么互联"的问题,Spring AI Alibaba解决的是"应用怎么用上大模型"的问题。两者可以共存,但职责完全分开。我见过有人以为引入Spring AI Alibaba就必须同时引入Nacos,其实没有这回事,除非你自己项目里有微服务需求。
2.2 核心模块一览
Spring AI Alibaba的项目结构可以从GitHub的仓库里看到,它包含几个关键模块,我按实际使用频率排个序:
- spring-ai-alibaba-core:核心抽象层。定义了大模型的统一接口,包括ChatModel、StreamingChatModel、EmbeddingModel等。
- spring-ai-alibaba-dashscope:通义千问的适配器。把DashScope的模型(qwen-plus、qwen-max等)封装成标准接口。
- spring-ai-alibaba-tool-calling:工具调用能力。让模型可以在对话过程中调用你注册好的自定义函数。
- spring-ai-alibaba-graph:图编排能力。用于构建复杂的Agent工作流。
- spring-ai-alibaba-examples:官方示例工程。
如果你只是简单聊天,只需要引入core和dashscope。但要做RAG,你还需要一个向量数据库的适配器,框架支持Redis、Milvus、PGVector等。做NL2SQL的时候,更多依赖的是Prompt工程和Schema描述,不一定需要额外模块。做Agent的话,tool-calling和graph就派上用场了。
2.3 为什么建议直接用它而不是自己封装Http调用
我在早期项目里试过直接用HTTP SDK调通义千问的接口,做一两个功能没问题,但做到后面你会发现自己在重复造轮子。模型调用管理、上下文对话轮次、Prompt模板化、输出Json格式校验、工具调用的参数解析,每个环节都要自己写一遍。
Spring AI Alibaba把这些都抽象好了。它参考了LangChain的设计思路,但完全基于Spring Boot原生风格,依赖注入、自动装配、配置绑定都跟Spring生态无缝衔接。写起来的感觉是:你在写一个普通的Spring Service,只不过里面多了一个ChatClient的组件。
3. RAG落地:把企业知识库变成大模型的记忆
3.1 为什么单纯微调解决不了知识库问题
在讲RAG之前,先聊一下为什么很多人第一反应是微调模型。你的业务场景可能是:员工手册、技术文档、产品FAQ,这些内容不该让大模型凭空编。微调的做法是用这些数据去训练一个私有模型,但问题很明显。
第一,微调成本高。数据清洗、标注、训练环境、GPU资源,一套流程下来周期很长。第二,你的文档是动态变化的,今天新增一份,明天修改一段,每次都要重新训练,完全不现实。第三,微调解决的是"输出风格和知识记忆"问题,但模型仍然会编造不在训练数据里的细节。
RAG的思路则是"检索加生成"。你把文档切片、向量化之后存到向量数据库,当用户提问时,先把问题也向量化,去库里找到最相似的几个片段,再把片段作为上下文拼进Prompt里,最后让模型基于这些材料生成回答。
这个类比很简单:微调是让模型背熟一本书,RAG是允许模型考试时翻书。对大多数企业内部场景来说,RAG是更灵活的方案。
3.2 数据准备是RAG成败的第一个关口
很多RAG项目效果差的第一个原因,不在检索和生成环节,而在数据切片环节。
切片之前先做清洗。原始文档里常见的噪音包括:页眉页脚、目录、重复段落、表格提取后变成的乱序文本。这些噪音会把向量化结果带偏。我建议的处理流程是:先去页眉页脚,再统一换行格式,表格转成Markdown格式或文字描述,图片OCR提取说明文字。
切片长度怎么定?这是RAG调参的第一个关键点。我踩过几个地方的坑,最终形成了一套经验值:
- 普通技术文档,按500到800字切一个片段,保留段落边界。
- 如果文档有明确的章节结构,优先按章节切,一个章节内再分片段。
- 相邻片段之间保留50到100字的重叠,避免检索时把完整信息切碎。
为什么重叠这么重要?举个真实例子。我之前处理一份接口文档,一个接口的说明正好被切在两个片段边界处,上片段说了接口地址和请求方法,下片段说了参数和返回值。用户问"这个接口的请求参数是什么",检索返回了上片段,模型只能看到地址和方法,回答必然不完整。有了重叠后,两个片段都包含衔接部分,检索命中的概率就高了。
嵌入式模型的选择,也值得说一句。文本向量化的效果直接决定检索质量。Spring AI Alibaba支持DashScope的text-embedding-v2,效果对于中文文档来说足够好。如果你处理的是英文为主的技术文档,也可以用OpenAI的text-embedding-3-small,效果挺稳。向量维度大一点,检索精度相对会好一些,但存储和计算成本也会上升,按实际数据量来取舍。
3.3 用Spring AI Alibaba搭建RAG的完整步骤
这里直接给可落地的代码,不需要绕弯子。
第一步,引入依赖。在pom.xml里加上DashScope和向量数据库的适配。我用的是Redis作为向量存储,一来公司里Redis基本都有现成实例,二来Spring AI对Redis的支持比较成熟。
第二步,配置连接信息。在application.yml里设置DashScope的API Key、模型名和Redis连接。
第三步,封装文档解析器。从PDF、Word、Markdown里提取文本,要做格式清洗和切片。Spring AI的ParagraphPdfReader、PagePdfReader可以直接用,但输出是纯文本列表,切片逻辑要自己写。
第四步,编写EmbeddingService和VectorStore。启动时初始化VectorStore,然后把切片文本逐条embedding入库。
第五步,定义问答接口。Controller接收用户问题,先用VectorStore去检索相关性最高的TopK片段,拼装Prompt,再调用ChatModel生成回答。
整个流程里,我最想强调的是第五步的Prompt组装。
有一个常见的反面教材:把检索到的所有片段都一股脑塞进Prompt,不管跟问题有没有关系。这样做有两个后果,一是Prompt过长浪费Token,二是无关片段会干扰模型判断,甚至让模型基于错误材料回答。
我建议的做法是,检索返回的TopK片段,先做一个重排序或者相关性过滤,只保留相似度超过阈值的片段。如果所有片段相似度都低,那就不应该直接返回答案,而是告诉用户"知识库里没有相关内容"。
这一步做得好的话,你的RAG质量会明显不一样。
另外,回答里最好让模型标注信息出处。你可以在Prompt里要求:"当回答基于提供的文档内容时,请在末尾标注参考的来源编号"。这样能够降低模型编造的倾向,也方便用户自行核对。
3.4 RAG调优的几个关键点
第一个是检索方式。深入的检索加上对应的重排策略,效果是会好过一个简单向量搜索的。Spring AI Alibaba里可以用Redis执行混合搜索(向量相似度加文本相关性),也有专门的Rerank配置,配合通义千问的text-rerank模型。实测下来,混合搜索加Rerank之后,问答准确率能提升明显,但延迟会多出几百毫秒到一秒,需要按业务收益来权衡。
第二个是日期时效。模型不知道当前日期,如果文档里包含"截至2025年3月"这种时间信息,那用户问"最新的政策是什么",模型可能分不清新旧文档。我的处理办法是,在文档元数据里存发布日期,检索排序时除了向量相似度,还按时间倒序加权。Spring AI Alibaba的向量存储接口支持MetaData,加上这一层功能就很方便了。
第三个是对话历史和追问。如果用户在上文提到了"这个功能怎么配置",下一句问"那权限呢",你在组装Prompt时如果只考虑当前问题,模型会因为没有上下文而答错。这里需要把最近几轮对话的历史摘要注入到Prompt里。
4. NL2SQL:让大模型帮你写SQL的落地细节
4.1 NL2SQL的本质是"规范化"
NL2SQL要解决的问题很直接:业务人员不会写SQL,但他们会用自然语言问问题。比如"上个月华东区的销售额top10的产品是什么"。你希望系统能自动转成数据库查询语句去执行,再返回结果。
这个场景的本质不是让模型学SQL语法,模型本来就会SQL。难点在于:第一,让模型理解你的库表和字段含义;第二,生成的SQL必须安全、准确;第三,面对复杂的业务问题,模型需要推理出正确的表关联关系和过滤条件。
所以,NL2SQL的核心工作是"Schema描述"的编写,以及"SQL生成后的校验"。
4.2 Spring AI Alibaba里怎么实现
如果你使用DashScope的通义千问模型,它本身就具备不错的SQL生成能力。你的工作是把数据库结构、示例数据、业务规则整理好,用Prompt把它传进去。
我提供一个比较可靠的Prompt模板结构:
系统角色定义:明确告诉模型它是一名数据库专家,只能根据提供的表结构和规则生成SQL,禁止猜测不存在的字段。
数据库结构定义:使用建表语句(CREATE TABLE)来描述每个表的字段、类型、注释和索引。
业务规则说明:比如"订单金额以实际支付金额为准""退款订单不计入销售额"这类逻辑约束。
示例问题对:给两到三个典型的问答示例,让模型模仿你的输出格式。
输出格式约束:要求只输出SQL语句本身,不要解释,不要加Markdown代码块标记。
这个模板看起来简单,但细节决定成败。建表语句的英文注释、字段命名是否清晰、是否能表达字段的业务含义,都会直接影响模型的表现。字段名如果都是pty1、c002这种无意义缩写,模型根本无从理解字段含义。
4.3 Schema上下文的动态管理
有一个很现实的问题:一个数据库可能有几十张表,但你不可能把全部建表语句都塞进Prompt里,Token数量会爆炸,模型也会被无关的表结构干扰。
我建议的方案是两步走:
先做"表召回"。用户提问后,先根据问题里的关键词,从表名、字段名、字段注释里去匹配相关的表。召回方式可以简单用ES或数据库模糊匹配,也可以先向量化表描述之后走向量检索。
把召回的相关表的建表语句拼装成Prompt,再传给模型生成SQL。如果表关联关系复杂,模型可能需要看到所有相关表的schema才能正确地JOIN。
这个"先召回后生成"的架构,其实就是把NL2SQL问题做了降维处理。一次只给模型看相关的表定义,生成成功率会高很多,模型也不容易被无关信息干扰。
4.4 安全底线与防注入
NL2SQL的安全问题比普通聊天还要重要。模型生成的SQL如果直接扔给数据库执行,轻则查询结果不对,重则删表、脱库,这个后果谁都不想承担。
我建议至少做下面几层防护:
权限控制:使用独立的低权限数据库账号,只授予SELECT权限,禁止更新和删除操作。这是底线中的底线。
只读检测:在SQL执行前做关键字检查,检测到insert、update、delete、drop、alter、truncate等破坏性语句直接拒绝。别指望模型一定不会生成这些语句,加上一层保险。
超时控制:设置查询超时,避免生成的SQL是笛卡尔积死循环式的慢查询,把连接池拖垮。
结果行数限制:在SQL外层包一层LIMIT,自动限制最大返回行数,比如100或者200行。查询结果几十万行的场景,即使没坏处,也对系统性能有冲击。
行级权限:如果是多租户系统,一定要在SQL里强制拼接租户ID的过滤条件。这个逻辑不能让模型自己决定拼不拼,需要系统层面加进去。
这几层防护,我建议你都做。只做其中一两层是不够的,因为每一层都可能被绕过。宁可多几个检查步骤,也不要省事换来事故。
4.5 实测中常见的质量问题
NL2SQL生成的结果,在我实际测试中有几类高频问题:
表名列名幻觉。模型可能生成了库里根本不存在的字段,因为上下文里有了表结构,幻觉率好很多,但遇到表字段命名不规范的情况还是会出错。解决办法是把字段清单做成约束,Prompt里明确写"禁止使用未在上述表结构中出现的字段"。
过滤条件遗漏。比如"近30天的订单",模型可能只看日期范围,忘了订单状态过滤。解决办法是在业务规则里把默认过滤条件写清楚。
聚合计算错误。比如"平均客单价",模型可能用SUM(amount)/COUNT(*)而不是SUM(amount)/COUNT(DISTINCT user_id)。这类问题要靠示例对来引导。
我的经验是,每次上线之前准备一批测试问题集,跑完写总结,记录哪些Query准确、哪些失败、失败原因是什么。这个回归测试很重要,因为你改了Prompt或组织规则以后,跑一遍测试集才能知道是不是变好还是变坏。
5. Agent开发:从工具调用到自主决策
5.1 理解Agent和Function Calling的关系
在Spring AI Alibaba里开发Agent,核心机制是Function Calling(工具调用)。简单说,模型在回答用户问题时,遇到自己无法直接回答的内容(比如需要查库存、需要调外部接口),它不会硬编答案,而是输出一个"我想调用某个函数"的请求,框架把这个请求解析出来,执行你的Java方法,再把执行结果返回给模型,模型基于返回结果组织最终回答。
这个过程里,你有两个角色:
一个是模型,负责"决策"。它根据用户的意图,决定要不要调用工具,调用哪个工具,参数是什么。
一个是你的Java代码,负责"执行"。每个工具就是一个被@Tool注解修饰的方法,执行后把结果返回给模型。
5.2 编写一个简单Agent:天气预报+库存查询
我举个具体例子。假设你要做一个客服助手Agent,它需要能查询实时库存和预计发货时间。这两个信息模型自己是不知道的,必须通过调用后端接口取得。
在Spring AI Alibaba里,写法很简洁:
先定义一个工具类,方法上加@Tool注解,同时给出清晰的中文描述。
然后在调用ChatModel时,把工具类注册进去。
核心技巧在于@Tool注解的description。这个描述写得清不清楚,直接决定模型在什么时候调用这个工具,以及参数传得对不对。
一个合格的描述应该说明"这个工具是干什么的"、"什么时候用它"、"参数分别代表什么"。描述模糊的话,模型会在不该调用的时候乱调,然后在传入参数时也会反复问用户或乱猜。
5.3 多工具编排:让Agent学会"先A后B"
单工具调用其实不难,真正的挑战是"多步推理"和"多工具编排"。比如用户问"这个商品有库存吗?如果有的话,帮我创建一个预售订单"。这个过程至少涉及两个工具:查库存,再创建订单。而且创建订单依赖查询到的商品状态。
怎么做?不需要你写if-else逻辑,模型本身能够根据对话历史来决策。你只需要在系统Prompt里写清楚"请先检查库存再创建订单"。
Spring AI Alibaba的ChatClient支持自动维护对话历史和工具调用状态,你只需要把工具列表传进去,模型可以多轮调用工具,直到得出最终答案或者无法继续。
这里有一个成本问题需要注意:多轮工具调用会消耗更多的Token,每次调用都会把历史上下文再传给模型一次。我建议在Agent场景里,对历史记录做一个轻量摘要,避免上下文无限膨胀。
5.4 使用Graph模块编排复杂的Agent流程
如果你的Agent流程已经不满足于"单串多轮工具调用",而是需要分支、循环、条件判断,比如:"先判断用户身份,如果是VIP走快速退款通道,否则走人工审核",那就应该用Graph模块。
Spring AI Alibaba的Graph模块允许你定义一系列节点,每个节点是一个独立的处理单元,节点之间有状态转移关系。它的实现思路类似LangGraph,好处是把控制流显式地画出来,可控性比较强。
不过我也说句实话,Graph模块的学习曲线比单纯用Function Calling高不少。如果只是三五个工具线性调用,不建议上Graph,先把Function Calling和Prompt写好就够用了。Graph适合的是"决策路径分叉场景多"的复杂Agent。
5.5 Agent开发的一个坑:模型误解工具参数
这类问题特别常见。工具方法定义了一个枚举类型的参数,模型却传了不支持的字符串,框架在反序列化时就报错了。解决办法有两个:一种是在Prompt里明确说清楚参数的枚举值有哪些,让模型按规范传参;另一种是工具方法内部做容错处理,不认识的参数值映射到默认值。
另一个高频问题是"工具调用结果为空"。模型调用了工具,但工具返回null或者空列表,模型可能会强行编造一个有意义的回答。我建议工具返回结果时就带状态信息,比如"查无数据"和"系统异常"分开,模型看过之后至少不会在回答里硬凑。
6. 常见问题与排查技巧实录
6.1 模型重复调用同一个工具,陷入死循环
三个字:上下文。模型每一步都会把历史记录带进去,如果上一步的工具返回结果没有正确写入消息历史,模型就不知道工具已经调用过了,会再来一遍。
Spring AI Alibaba处理这个做得好的地方是,工具调用信息会自动追加到Message列表。但如果你自己组装消息历史,就一定要确保工具调用记录和工具返回结果都完整存在于历史中,两头缺失任何一处,都可能引发重复调用。
6.2 Token数超限
特别是RAG场景,把检索片段全塞进Prompt,很快就把上下文窗口打满了。我的建议是,把检索片段做摘要压缩后再进Prompt,模型先读摘要,再按需展开。这招比简单截断效果好。
6.3 检索结果不相关
排查步骤:先看你查询的向量化"表现"怎么样。把问题和文档单独拿出来比对Embedding结果,看在向量相似度上是不是真的有区分度。然后看召回TopK是否合理。你可以临时把K调大一些,人工看一眼返回的片段是不是你想要的内容。最后才考虑做全体重排。
6.4 响应延迟大
RAG链路本身就比纯对话多几步。尽量把向量检索和模型调用做成并行,不要让用户等太久。对实时性要求高的场景,模型选择考虑一下用qwen-turbo,效果和速度折中来看比较合适。
6.5 多版本兼容问题
Spring AI Alibaba目前还处在快速迭代期,API变动比较频繁。我的建议是,锁定一个小版本测试通过后不要随便升级。升级前重点看ReleaseNote里关于ChatClient、ToolCalling、VectorStore相关部分的变更说明。
7. 最后说点实在话
这个系列写到第四篇,我自己最大的体会是:Spring AI Alibaba已经把"接入大模型"的门槛降得很低了,但真正考验开发者的不再是怎么调API,而是怎么设计Prompt、怎么组织数据、怎么控制边界、怎么做安全兜底。
RAG、NL2SQL、Agent这三个方向,任何一个单独拿出来都可以做成一个颇具规模的系统,但它们的共通点也相当一致:在模型能力之上,拼的还是怎么把业务知识整理好,怎么把系统约束控制住,怎么设计清晰的工具逻辑。
我也建议大家在学习这个框架的过程中,多留意官方示例仓库里的代码。更重要的是自己动手把Demo跑起来,然后把文档换成自己工作里的资料,模型换成自己熟悉的业务数据,踩一些真实的坑,你会对这些组件的工作方式有更深的理解。
希望这篇能帮到你,也欢迎交流你在做这一类系统时踩过的坑。