SpringAI 框架完整使用
SpringAI 基础定位
通俗定位
SpringAI 是 Spring 官方推出的 AI 开发框架,专门简化 Java 对接各大公有大模型的开发流程,底层自动封装 HTTP 网络请求、请求报文组装、流式数据解析、异常处理,不用手写 OpenFeign、WebClient 那一套重复代码。
生活化类比
原生 OpenFeign/WebClient 调用大模型 = 自己买菜、洗切、调味、开火全套手动操作,每换一道菜都要重复工序;
SpringAI = 预制料理包,调料、加工步骤全部封装完毕,你只需要告知需求,直接出成品,大幅减少重复开发工作。
SpringAI 基础配置与核心对象 ChatClient
项目依赖引入
只需要引入 SpringAI starter,无需手动定义请求实体、请求头拦截器:
XML
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
</dependency>
yml 统一配置(全局管理密钥、模型地址)
所有模型信息写配置文件,不用硬编码、不用自定义拦截器携带密钥:
XML
spring:
ai:
dashscope:
api-key: sk-xxxx你的密钥
base-url: https://dashscope.aliyuncs.com
核心对象:ChatClient
框架核心入口对象,自动读取 yml 配置,内置同步、流式两种调用能力,直接注入使用:
java
@Autowired
private ChatClient chatClient;
ChatClient 是什么?
通俗定义
ChatClient 是 SpringAI 对外提供最高阶、业务层首选 的对话客户端门面,底层封装 ChatModel,把 HTTP 请求、报文组装、流式解析、异常、重试、提示词管理全部屏蔽,提供链式流畅 API,专门用来和各大 LLM 对话(通义千问、GLM、DeepSeek 等)。
生活化类比
原生 OpenFeign / WebClient = 自己买菜、洗菜、切肉、调酱汁、开火全套手动操作,每换一个菜品就要重写全套代码;
ChatClient = 全自动智能料理机:只需要告诉机器你想要什么菜(用户需求),机器自动处理食材、火候、调味,不用管底层机械运转(HTTP 通信、模型 API 差异)。
整体分层流程图

ChatClient 和底层 ChatModel 区别对比表
| 对象 | 层级 | 使用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| ChatModel | 底层 API | 深度自定义、自研复杂中间件 | 底层完全可控 | 需要手动拼接 Prompt、处理分段、异常,代码量大 |
| ChatClient | 上层门面(推荐) | 90% 业务开发(短信生成、问答) | 链式简洁、内置提示词 / 流式 / 重试 / Advisor 拦截 | 底层网络改造灵活性略低 |
ChatClient 两种创建方式(项目标准写法)
SpringAI 自动装配 ChatClient.Builder,优先用 Builder 构建,不用手动 new。
方式 1:Controller/Service 构造注入(最简单测试)
Spring 自动注入 Builder,调用.build()生成实例
java
@RestController
public class AiSmsController {
// 自动注入Builder原型Bean
private final ChatClient chatClient;
// 构造器注入
public AiSmsController(ChatClient.Builder chatClientBuilder) {
// 构建客户端
this.chatClient = chatClient.build();
}
}
方式 2:配置类全局统一创建(生产推荐,全局系统提示词)
统一设置项目固定角色(合规短信专员),所有调用自动携带系统指令,不用每次写 system ()
java
@Configuration
public class AiChatConfig {
@Bean
public ChatClient smsChatClient(ChatClient.Builder builder) {
return builder
// 全局固定系统提示词:只写一次,全局生效
.defaultSystem("你是运营商合规短信文案专员,输出40-60字,禁用极限词")
// 全局默认模型参数:降低随机性
.defaultOptions(OpenAiChatOptions.builder().temperature(0.2).build())
.build();
}
}
创建核心要点
ChatClient.Builder是原型 Bean(每次注入都是新 Builder),可生成多个不同配置的 ChatClient;defaultSystem()全局系统提示词,所有对话自动带上,减少重复编码;defaultOptions()统一全局 temperature、model 名称。
ChatClient 两大核心调用模式(call 同步 /stream 流式)
同步调用 .call()(后台批量、管理后台)
一次性接收完整文本,阻塞执行,返回字符串。
完整短信生成示例
java
@GetMapping("/sms/sync")
public String syncGenSms(String activity) {
return chatClient.prompt()
// 用户单次需求
.user("写两条"+activity+"家电营销短信")
// 同步发送,等待完整结果
.call()
// 提取AI返回纯文本
.content();
}
流式调用 .stream()(前端实时打字预览 SSE)
返回 Flux<String>,分段逐字推送,前端实现打字效果,非阻塞。
SSE 流式接口标准代码
java
// 必须声明媒体类型text/event-stream
@GetMapping(value = "/sms/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamSms(String activity) {
return chatClient.prompt()
.user("写两条"+activity+"家电营销短信")
// 开启分段流式输出
.stream()
.content();
}
两种调用模式对比表格
| 调用方法 | 返回值 | 底层特性 | 适用短信项目场景 | 用户体验 |
|---|---|---|---|---|
| call() | String | 同步阻塞,一次性全量返回 | 定时批量生成、后台管理页 | 页面长时间空白加载 |
| stream() | Flux<String> | 异步非阻塞,分段推送 | 前端 AI 实时预览页面 | 逐字弹出,无空白等待 |
关键 API 流程文字框架
java
chatClient.prompt() // 开启一次对话构造
.system(临时角色) // 单次覆盖全局system(可选)
.user(用户需求) // 业务提问
.options(单次参数) // 临时覆盖全局temperature
.call() / .stream() // 选择同步/流式
.content() // 提取纯文本
ChatClient 五大核心内置能力
1. 统一管理提示词(全局 + 单次)
- 全局
defaultSystem():项目通用合规规则,所有对话自带; - 单次
.system():仅当前请求临时修改角色,互不干扰; - 支持 PromptTemplate 模板,可带占位符动态填充活动名称。
2. 原生流式 SSE 封装,不用手写 WebClient
底层自动封装 WebClient、Flux 分段解析,无需处理 SSE chunk 分割逻辑,几行代码实现实时预览。
3. 内置全局超时、重试机制
yml 统一配置超时、重试次数,Builder 可自定义重试 Advisor,不用手动整合 Spring Retry。
4. Advisor 拦截器体系(扩展能力)
内置多种拦截器,开箱即用:
- ChatMemoryAdvisor:自动维护多轮对话上下文;
- PromptAdvisor:统一处理提示词模板;
- LoggingAdvisor:打印输入输出日志;
- 自定义 Advisor:做输出校验、Prompt 注入过滤。
5. 多模型无缝切换
仅修改 yml 的api-key、base-url,业务ChatClient代码一行不用改动,适配通义、GLM、DeepSeek 等任意厂商模型。
ChatClient 处理返回结构(返回对象说明)
call()返回ChatResponseSpeccontent():直接拿纯文本(日常短信生成最常用);chatResponse():完整响应对象,可读取 token 消耗、finish_reason、错误码;
stream()返回StreamResponseSpeccontent():分段字符串 Flux,逐字返回;
ChatClient 生产环境优势总结
- 屏蔽各家大模型 API 差异,一套代码切换多款模型;
- 链式 API 极简,省去手动封装请求头、JSON 实体、WebClient 流式代码;
- 全局统一配置系统提示词、temperature、超时、重试;
- 内置 Advisor 拦截体系,轻松实现多轮记忆、日志、内容校验、安全过滤;
- 同步 / 流式两套 API 统一入口,项目同步批量、前端预览两套场景全覆盖。
ChatClient 核心常用方法 / 字段功能对照表
一、ChatClient.Builder 构建器核心方法(全局配置,创建客户端时使用)
| 方法名 | 作用说明 | 项目使用场景(短信 AI 平台) |
|---|---|---|
| defaultSystem(String text) | 设置全局固定系统提示词,所有对话自动携带,全局永久生效 | 统一规定:合规短信专员、禁用极限词、字数限制,不用每次调用重复写 |
| defaultOptions(ChatOptions) | 全局统一模型参数(temperature、topP、模型名称) | 全局设置 temperature=0.2,保证文案风格稳定统一 |
| defaultAdvisors(Advisor...) | 全局注册拦截器(日志、记忆、安全过滤) | 全局开启日志打印、Prompt 注入过滤、多轮对话记忆 |
| build() | 根据前面配置,生成最终可用 ChatClient 实例 | 配置完成后执行,交给 Spring 注入业务使用 |
二、chatClient.prompt () 对话构建链方法(单次请求内链式调用)
| 链式方法 | 作用说明 | 使用示例 |
|---|---|---|
| prompt() | 开启一次全新对话,返回 PromptSpec 对象,所有单次配置从这里开始 | chatClient.prompt() |
| system(String text) | 单次临时系统提示词,仅覆盖当前这一次请求,不影响全局 | .system ("只生成家电活动短信,50 字左右") |
| user(String text) | 传入用户业务提问、活动需求,必写方法 | .user ("写两条 618 冰箱促销短信") |
| options(ChatOptions) | 单次请求临时覆盖模型参数,优先级高于全局 defaultOptions | .options(OpenAiChatOptions.builder().temperature(0.5).build()) |
三、发起请求核心执行方法(二选一:同步 / 流式)
| 执行方法 | 返回对象 | 功能作用 | 适用场景 |
|---|---|---|---|
| call() | ChatResponseSpec | 同步阻塞调用,一次性接收完整 AI 返回内容 | 后台批量生成短信、管理后台同步接口 |
| stream() | StreamResponseSpec | 异步分段流式调用,返回 Flux 分段数据流 | 前端实时打字预览 SSE 页面 |
四、响应解析通用方法(call/stream 后提取数据)
| 方法 | 归属对象 | 作用 |
|---|---|---|
| content() | ChatResponseSpec / StreamResponseSpec | 直接提取 AI 生成的纯文本(日常开发最常用) |
| chatResponse() | ChatResponseSpec | 获取完整原始响应对象,可读取 token 消耗、结束标识、错误信息 |
五、配套常用配置字段(yml 全局配置字段)
| yml 配置字段 | 含义 | 业务价值 |
|---|---|---|
| spring.ai.dashscope.api-key | 大模型鉴权密钥 | 统一管理密钥,不硬编码在代码中 |
| spring.ai.dashscope.base-url | 模型 API 基础地址 | 切换测试 / 生产环境只改配置,无需改代码 |
| spring.ai.retry.max-attempts | 最大重试次数 | 5xx 服务故障自动重试,减少人工报错 |
| spring.ai.retry.timeout | 请求全局超时时间 | 防止大模型响应过慢阻塞服务线程 |
六、补充关键字段 / 对象说明
| 对象 / 字段名称 | 类型 | 核心作用 |
|---|---|---|
| ChatClient.Builder | 构建器类 | 模板化统一创建带全局配置的 ChatClient,单项目可创建多个不同配置客户端 |
| ChatOptions | 参数实体 | 存放 temperature、model 名称、top_p 等模型生成控制参数 |
| Advisor | 拦截器接口 | 切面扩展,可在请求前过滤恶意 Prompt、响应后校验文案合规性 |
| Flux<String> | 流式返回类型 | WebFlux 数据流,用于 SSE 逐字推送给前端 |
简易执行流程图
引入依赖 → yml 配置模型密钥地址 → 自动装配 ChatClient → 调用同步 / 流式接口生成文案
SpringAI 两种调用模式:同步问答 + SSE 流式问答
| 调用模式 | 底层特性 | 适用业务场景(你的短信项目) | 用户体验 |
|---|---|---|---|
| 同步问答 | 一次性接收完整返回数据,同步阻塞 | 后台批量生成短信、运营管理后台单次生成文案 | 页面等待全部生成完成才展示,会有空白加载 |
| SSE 流式问答 | 分段推送数据,支持逐字输出 | 前端 AI 文案实时预览页面 | 文字逐行弹出打字效果,无长时间空白 |
SpringAI 五大核心功能
五大核心功能:
- 单次普通对话同步调用
- SSE 流式问答接口开发
- 可复用提示词模板封装
- 全局超时、重试、统一异常捕获
- 接口降级兜底逻辑开发
整体类比:原生 OpenFeign/WebClient 是手工做饭;SpringAI 五大功能等于一套全自动厨房五件套,覆盖业务所有 AI 生成场景。
普通单次同步对话调用
通俗定义
基于 ChatClient.call () 实现一次性完整获取 AI 返回文本,同步阻塞执行,无需处理分段数据流,适合后台批量、管理后台同步生成文案。
生活化类比
点外卖一次性送餐,商家全部做好打包统一送达,你全程等待完整商品。
核心作用
- 一行链式代码完成对话,不用手动组装 JSON 请求体、请求头;
- 自动拼接 system 系统提示词 + user 用户需求,底层封装 messages 数组;
- 直接提取纯文本 content,省去手动解析返回 JSON。
短信项目代码示例
java
@RestController
@RequestMapping("/ai/sms")
public class AiSmsController {
private final ChatClient chatClient;
// 构造注入全局配置好的ChatClient
public AiSmsController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是合规短信文案专员,40-60字,禁用极限词")
.build();
}
// 同步单次对话接口
@GetMapping("/sync")
public String syncGen(String activityName) {
return chatClient.prompt()
.user("生成2条"+activityName+"营销短信")
.call() // 同步调用
.content(); // 直接拿文案文本
}
}
适用场景
- 定时任务批量生成短信模板;
- 运营后台同步生成文案;
- 不需要实时打字预览的页面。
优缺点
优点:代码极简、调试简单、无响应式学习成本;
缺点:同步阻塞,大批量并发会占用 Tomcat 线程。
SSE 流式问答接口开发
通俗定义
基于 ChatClient.stream () 返回 Flux<String>分段数据流,实现 SSE 服务端单向推送,前端逐字打字预览效果,SpringAI 底层自动封装 WebClient、分段解析逻辑。
生活化类比
奶茶分次出杯,做好一小杯立刻递给顾客,不用等全部制作完成。
核心作用
- 内置流式协议解析,不用手写 WebClient 响应式 Flux 代码;
- 非阻塞,不长期占用 Tomcat 工作线程,高并发预览不卡顿;
- 天然适配 text/event-stream SSE 协议,前端开箱即用。
短信项目代码示例
java
// produces指定SSE媒体类型
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamSms(String activityName) {
return chatClient.prompt()
.user("生成2条夏季空调促销短信")
.stream() // 开启流式分段输出
.content();
}
适用场景
前端 AI 文案实时预览页面,消除页面长时间空白加载。
关键优势对比原生 WebClient
原生 WebClient 需要手动配置连接池、超时、分段分割逻辑;SpringAI 一行 stream () 封装全部底层细节。
自定义可复用提示词模板封装
通俗定义
SpringAI 提供 PromptTemplate 统一抽取固定系统角色、合规约束、通用句式,支持占位符动态填充业务参数,全局多处业务复用,减少重复 Prompt 硬编码。
生活化类比
打印预制填空模板,活动名称、优惠金额动态填入,不用每次重新写完整文案要求。
核心作用
- 统一管理合规规则(禁用极限词、字数限制),修改只改一处;
- 支持 ${变量} 占位符,运行时动态填充活动、商品类型;
- 区分全局默认 system 模板、单次临时业务模板。
代码示例(模板复用)
java
// 1. 定义公共模板字符串
String smsPromptTemplate = """
你是合规短信专员,输出2条40-60字短信,禁用极限词。
活动:${activity},优惠:${discount}
""";
// 2. 填充占位符
PromptTemplate template = new PromptTemplate(smsPromptTemplate);
Map<String,Object> params = Map.of("activity","618家电","discount","满2000减300");
Prompt prompt = template.create(params);
// 3. 传入ChatClient调用
String result = chatClient.prompt(prompt).call().content();
项目价值
- 避免在多个 Controller/Service 重复写大段系统提示词;
- 统一管控短信合规规则,后期运营商规则变更只修改模板;
- 大幅减少 Token 浪费,提升开发效率。
全局超时、重试、异常统一捕获
通俗定义
SpringAI 原生支持 yml 全局配置超时时间、最大重试次数,内置统一异常拦截,自动区分 4xx 客户端错误、5xx 服务端故障,无需手动整合 Spring Retry、手动写超时逻辑。
生活化类比
外卖系统内置规则,商家超时自动重试 2 次,地址填错直接提示不重复派送。
1)yml 全局配置(开箱即用)
XML
spring:
ai:
dashscope:
api-key: sk-xxx
retry:
max-attempts: 3 # 最大重试3次
timeout: 30s # 全局读取超时30秒
2)自动异常分类处理
| 错误类型 | 框架处理逻辑 | 业务规则 |
|---|---|---|
| 4xx(密钥错误、参数非法、额度耗尽) | 不重试,直接抛出业务异常 | 客户端问题,重试无意义 |
| 5xx(模型服务宕机、过载) | 自动间隔重试,达到 max-attempts 停止 | 服务临时故障,有限重试提升成功率 |
核心价值
- 全局统一管控,所有 ChatClient 调用共享一套超时、重试规则;
- 底层自动捕获网络异常、模型返回错误,统一封装异常信息;
- 不用手动给 OpenFeign/WebClient 写重试、超时配置,减少大量模板代码。
接口降级兜底逻辑编写
通俗定义
当大模型服务超时、宕机、调用失败时,自动切换返回本地预设标准短信模板,保障营销业务不中断,属于生产环境高可用必备能力。
生活化类比
奶茶店设备故障,立刻提供提前备好的瓶装饮料兜底,不让顾客空手离开。
两种降级实现方式
方式 1:Builder 全局统一兜底(推荐)
可自定义兜底返回文案,当大模型服务宕机、超时无响应时,自动返回预设标准短信模板,保障营销业务不中断。
java
@Bean
public ChatClient smsChatClient(ChatClient.Builder builder){
return builder
.defaultSystem("合规短信专员")
// 自定义Advisor,捕获异常返回兜底文案
.defaultAdvisors((request, next) -> {
try {
return next.stream();
} catch (Exception e) {
// 兜底预设短信
return Flux.just("【家电特惠】门店家电限时满减,欢迎到店选购");
}
})
.build();
}
方式 2:业务层手动 try-catch 降级
java
public String genSms(String activity){
try {
return chatClient.prompt().user(activity).call().content();
}catch (Exception e){
// 模型服务不可用,返回兜底模板
return "门店优惠活动火热进行中,进店享专属折扣";
}
}
项目核心价值
- 避免大模型第三方服务故障导致营销功能完全不可用;
- 提升系统稳定性,商户不会因为 AI 接口挂掉无法下发短信;
- 分层兜底:全局统一兜底 + 业务自定义兜底双重保障。
五大功能汇总对比速查表
| 功能序号 | 功能名称 | 核心 API | 解决什么痛点 |
|---|---|---|---|
| 1 | 单次同步对话 | call() | 批量后台生成文案,简化同步调用代码 |
| 2 | SSE 流式输出 | stream() | 前端实时打字预览,替代手写 WebClient |
| 3 | 提示词模板封装 | PromptTemplate | 复用 Prompt、统一合规规则,减少重复代码 |
| 4 | 全局超时重试异常 | yml retry 配置 | 不用手动整合重试、超时、异常区分逻辑 |
| 5 | 服务降级兜底 | Advisor/try-catch | 第三方模型故障时业务不中断,高可用保障 |
表格对比:SpringAI VS 原生 HTTP 调用
OpenFeign/WebClient/RestTemplate对比
| 对比维度 | 原生 HTTP 调用(OpenFeign/WebClient) | SpringAI 框架 |
|---|---|---|
| 代码冗余度 | 高,需手动写请求实体、请求头、JSON 解析、分段处理流式数据 | 极低,底层全部封装,仅传入业务提示词即可 |
| 多模型切换 | 切换通义千问 / ChatGLM 需要修改接口、实体、请求地址,改动量大 | 仅修改 yml 配置,一行配置切换模型,无业务代码改动 |
| 流式开发难度 | WebClient 需要掌握 Flux 响应式语法,学习成本高 | 内置流式封装,少量代码实现 SSE,无需理解底层数据流 |
| 异常 & 重试 | 手动区分 4xx/5xx 错误,自行整合重试组件 | 全局统一捕获异常,配置文件直接开启重试 |
| 底层自定义能力 | 极高,HTTP 请求全流程可自由改造 | 封装程度高,底层网络逻辑自定义较麻烦 |
LangChain4j 基础入门
LangChain4j 定位
通俗定位
LangChain4j 是 Java 生态主流 AI 编排框架,核心不只是简单调用大模型,而是串联整套 AI 业务流程:多轮对话记忆、提示词模板、工具调用、文档知识库、RAG 链路编排,适合复杂长流程 AI 业务。
SpringAI 侧重「快速简单调用大模型」;LangChain4j 侧重「复杂 AI 业务流水线组装」。
生活化类比
SpringAI = 家用微波炉,一键热饭,简单需求够用;
LangChain4j = 完整中央厨房流水线,具备食材存储、配方模板、辅助工具、批量加工全套能力,适合复杂、多步骤业务。
两者适用场景简易区分表
| 框架 | 核心擅长场景 | 短信项目使用场景 |
|---|---|---|
| SpringAI | 单次、简单文案生成,同步 / 流式快速对接模型 | 普通活动短信单次生成、前端实时预览 |
| LangChain4j | 多轮连续对话、AI 工具调用、知识库 RAG 完整流程 | 运营连续多次修改短信需求、AI 自动校验商户额度 |
三大核心组件:ChatMemory、PromptTemplate、Tools 工具调用
组件 1:ChatMemory 对话记忆
通俗解释
自动存储多轮对话历史,每次请求自动拼接过往问答,实现连续上下文对话,不用开发者手动拼接 messages。
生活化类比
微信聊天记录,每次发消息自动带上之前全部对话,AI 能记住你上一轮提的约束(比如 "短信不能用极限词、控制 50 字")。
常见实现类
- InMemoryChatMemory:内存存储,服务重启丢失,测试、临时对话使用;
- PersistentChatMemory:持久化(Redis / 数据库),线上生产存储多轮会话。
使用方式
配置类注册成 Bean,AiService 绑定,前端传 sessionId 即可自动隔离记忆,全程不需要手动存聊天记录。
项目痛点解决
运营连续调整需求:先要求家电短信、再要求加优惠券、再删减字数。
若无 ChatMemory,每次调用都要重复粘贴全部历史需求;ChatMemory 自动携带上下文。
组件 2:PromptTemplate 提示词模板
通俗解释
统一封装固定角色、合规约束,支持占位符动态填充业务参数,全局多处复用提示词。
和 SpringAI 模板共性与差异
相同:都支持占位符、统一管理系统规则;
差异:LangChain4j 的模板深度集成记忆、工具、RAG 链路,可一键嵌入完整流程。
实际可用工具
框架提供PromptTemplate实体类,内置方法:
- 加载模板文本
- 填充占位符参数
- 生成完整 Prompt 对象传给大模型
使用方式
定义全局静态模板常量,组装 AiService 时全局绑定,所有对话自动携带统一合规规则;也可单次请求单独创建临时模板。
示例模板
你是合规短信文案专员,严格遵守规则:${rule}
请根据活动生成短信:${activityName}
组件 3:Tools 工具调用基础认知
通俗解释
允许大模型主动调用你后端写的 Java 业务接口,AI 判断需要外部数据时自动发起调用,拿到结果再生成文案。
生活化类比
导购不清楚门店剩余优惠券库存,主动去后台库存系统查询,再给顾客推荐。
实际可用工具
框架提供全套注解、工具注册逻辑:
@Tool注解标记业务方法- ToolProvider 统一管理所有工具
- AiService 自动识别并交给大模型调用
使用方式
给后端业务方法加@Tool注解,组装 AiService 时注册工具集,AI 会自主判断是否调用该接口(比如查询商户短信额度、活动有效期)。
短信项目落地场景
AI 生成短信前,主动调用工具接口查询:商户剩余 Token 额度、是否开启短信通道、活动有效期;
若额度不足,AI 直接输出提示,不生成文案。
核心价值
打破大模型 "无外部业务数据" 的局限,让 AI 联动现有业务系统。
汇总对比表格
| 组件名称 | 是否单纯概念 | 是否可编码使用 | 本质 |
|---|---|---|---|
| ChatMemory | 否,不是纯概念 | 是,可直接实例化、注入 | 会话存储工具类 |
| PromptTemplate | 否,不是纯概念 | 是,提供实体类与操作方法 | 提示词封装工具 |
| Tools 工具体系 | 否,不是纯概念 | 是,整套注解 + 管理组件 | AI 调用外部接口工具框架 |
LangChain4j 基础实操流程
场景需求:运营连续多次调整短信文案规则,框架自动记忆历史对话,不用每次手动拼接全部上下
1.前置初始化
导入依赖 + yml 配置大模型密钥,框架自动加载通义千问 ChatLanguageModel 大模型对象
引入 LangChain4j 依赖
XML
<!-- LangChain4j 核心依赖 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>0.34.0</version>
</dependency>
<!-- 通义千问模型对接依赖 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-dashscope-spring-boot-starter</artifactId>
<version>0.34.0</version>
</dependency>
解释
- 核心 starter 提供 ChatMemory、PromptTemplate、AiService 全套组件;
- dashscope 专用依赖,用来对接阿里通义千问大模型;
- SpringBoot 项目自动装配模型客户端,无需手动创建 HTTP 请求。
yml 配置模型密钥(全局统一配置)
XML
langchain4j:
dashscope:
api-key: sk-xxxxxxxxxxxx你的密钥
model-name: qwen-turbo
解释
将鉴权密钥、模型名称配置在配置文件,硬编码,框架自动读取配置构建模型实例。
2.创建 ChatMemory 存储会话上下文
ChatMemory = 对话存储器,开辟独立会话存储空间,按照 sessionId 隔离每个运营的聊天记录,限制最大消息数防止上下文过长
作用:
- 根据会话 ID(sessionId)区分不同运营人员,每人拥有独立聊天记录;
- 自动保存每一轮问答内容;
- 每次请求自动拼接历史上下文,无需手动拼接字符串;
- 设置消息上限,防止对话过多超出大模型上下文窗口,引发报错。
- 测试环境使用内存存储,服务重启记录清空;生产环境替换为 Redis 持久化存储。
该组件写在 Spring 配置类中,提供会话内存生成规则:
java
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LangChain4jConfig {
/**
* 会话内存提供者
* 根据传入的sessionId,分配独立的对话存储空间
*/
@Bean
public ChatMemoryProvider chatMemoryProvider() {
// maxMessages = 10:最多保存10轮一问一答,超出会自动丢弃最早的记录
return sessionId -> MessageWindowChatMemory.withMaxMessages(10);
}
}
解释
ChatMemoryProvider:框架规定的会话内存生成接口;
sessionId -> MessageWindowChatMemory.withMaxMessages(10)
- 入参 sessionId:会话唯一标识,用来隔离不同用户对话;
MessageWindowChatMemory:窗口式内存存储,最常用;withMaxMessages(10):限定最大存储消息条数,控制上下文长度,避免 token 溢出;
底层默认存储在 JVM 内存,重启服务所有对话记录全部消失。
LangChain4j ChatMemory 持久化存储到 Redis 完整教程
默认 MessageWindowChatMemory 使用 InMemoryChatMemoryStore,数据存在 JVM 内存,服务重启、多实例部署会话全部丢失,只适合本地测试。
LangChain4j 设计了顶层抽象接口 ChatMemoryStore,所有存储实现都要实现它,包含 3 个核心方法:
getMessages():根据 sessionId 读取历史对话updateMessages():更新 / 写入最新一轮对话deleteMessages():清空会话记录
持久化改造思路:自定义 RedisChatMemoryStore 实现 ChatMemoryStore,用 Redis 存储序列化后的对话 JSON;再替换掉默认内存存储,让 ChatMemory 走 Redis 读写。
整体实现步骤
步骤 1:引入全部 Maven 依赖
1)LangChain4j 核心、通义千问模型 2)Spring Redis 客户端
XML
<!-- LangChain4j核心 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>0.34.0</version>
</dependency>
<!-- 通义千问模型 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-dashscope-spring-boot-starter</artifactId>
<version>0.34.0</version>
</dependency>
<!-- Spring Redis 操作模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
步骤 2:application.yml 配置 Redis 连接 + 大模型密钥
XML
# Redis连接配置
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password: ""
database: 0
# 通义千问密钥
langchain4j:
dashscope:
api-key: sk-xxxxxxxxxxxx
model-name: qwen-turbo
步骤 3:自定义 RedisChatMemoryStore(实现 ChatMemoryStore)
核心作用:完成 ChatMessage 与 JSON 序列化、Redis 读写、会话过期自动清理
java
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.List;
@Repository
public class RedisChatMemoryStore implements ChatMemoryStore {
// Redis key前缀,区分业务数据
private static final String KEY_PREFIX = "sms:chat_memory:";
// 会话自动过期:3天无访问自动删除
private static final Duration SESSION_TTL = Duration.ofDays(3);
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 根据sessionId读取该会话全部历史对话
*/
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String redisKey = KEY_PREFIX + memoryId;
String json = stringRedisTemplate.opsForValue().get(redisKey);
// 不存在会话返回空集合
if (json == null || json.isBlank()) {
return List.of();
}
// JSON反序列化为对话消息列表
return ChatMessageDeserializer.messagesFromJson(json);
}
/**
* 新增/更新一轮对话,写入Redis并刷新过期时间
*/
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String redisKey = KEY_PREFIX + memoryId;
// 对话列表序列化为JSON字符串
String json = ChatMessageSerializer.messagesToJson(messages);
// 写入Redis,同时设置过期时间
stringRedisTemplate.opsForValue().set(redisKey, json, SESSION_TTL);
}
/**
* 手动清空某个会话全部历史
*/
@Override
public void deleteMessages(Object memoryId) {
String redisKey = KEY_PREFIX + memoryId;
stringRedisTemplate.delete(redisKey);
}
}
代码逐段解释
ChatMessageSerializer/ChatMessageDeserializer:LangChain4j 内置工具,专门负责对话消息序列化,不用自己写 Jackson 转换逻辑;StringRedisTemplate:使用字符串模板,直接存 JSON 文本,避免复杂对象序列化报错;SESSION_TTL:会话自动过期,避免 Redis 堆积大量无效长期未使用会话;- KEY_PREFIX:给所有会话 key 加业务前缀,和系统其他 Redis 数据隔离。
步骤 4:修改 LangChain4j 配置,替换内存存储为 Redis 存储
修改之前的ChatMemoryProvider,将自定义 Redis 存储注入,全局所有会话走 Redis 持久化:
java
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Configuration
public class LangChain4jConfig {
// 注入Redis持久化存储实现
@Resource
private RedisChatMemoryStore redisChatMemoryStore;
// 步骤2:构建会话内存提供者(替换原内存存储)
@Bean
public ChatMemoryProvider chatMemoryProvider() {
// memoryId:前端传入的会话ID
return memoryId -> MessageWindowChatMemory.builder()
// 指定使用Redis存储,不再使用默认InMemory
.chatMemoryStore(redisChatMemoryStore)
// 限制单会话最多保存10轮问答,防止上下文超长溢出token
.maxMessages(10)
.build();
}
// 步骤3:全局短信提示词模板(不变)
public static final PromptTemplate SMS_RULE_TEMPLATE = PromptTemplate.from("""
你是运营商合规短信文案专员,请严格遵守以下规范:
1. 单条短信字数控制在40~60字之间;
2. 严禁使用极限词、夸大宣传词汇;
3. 只输出短信正文,不要多余话术解释;
用户本次需求:{{userContent}}
""");
// 步骤4:组装顶层AiService(无需改动,自动复用Redis记忆)
@Bean
public SmsAiService smsAiService(ChatLanguageModel chatModel, ChatMemoryProvider memoryProvider) {
return AiServices.builder(SmsAiService.class)
.chatLanguageModel(chatModel)
.chatMemoryProvider(memoryProvider)
.systemPromptTemplate(SMS_RULE_TEMPLATE)
.build();
}
}
关键改动说明
MessageWindowChatMemory.builder().chatMemoryStore(redisChatMemoryStore)
- 不指定
chatMemoryStore:默认使用内存存储InMemoryChatMemoryStore; - 传入自定义
RedisChatMemoryStore:所有读写对话全部转发 Redis,实现持久化。
步骤 5:原有 Controller、AiService 接口完全不用修改,直接复用
java
// 业务接口不变
public interface SmsAiService {
String createSmsText(@MemoryId String sessionId, @UserMessage String userDemand);
}
// Controller 完全不变
@RestController
public class SmsChatController {
private final SmsAiService smsAiService;
public SmsChatController(SmsAiService smsAiService) {
this.smsAiService = smsAiService;
}
@GetMapping("/langchain/sms/chat")
public String smsChat(@RequestParam String sessionId, @RequestParam String demand) {
return smsAiService.createSmsText(sessionId, demand);
}
}
运行验证效果
- 第一次调用接口
sessionId=op001,对话存入 Redissms:chat_memory:op001; - 重启 SpringBoot 服务,再次使用相同 sessionId 请求,历史对话完整保留(内存存储会丢失,Redis 持久化不会);
- 多台服务集群部署,共享同一 Redis,不同实例可读取同一会话上下文;
- 超过 3 天无访问,Redis 自动清除该会话 key,释放存储空间。
内存存储 VS Redis 持久化对比表
| 对比项 | 默认 InMemory 内存存储 | Redis 持久化存储 |
|---|---|---|
| 数据存放 | JVM 堆内存 | Redis 独立缓存服务 |
| 服务重启 | 会话全部丢失 | 会话永久保存,过期自动清理 |
| 分布式集群 | 多实例会话隔离,无法共享 | 全实例共享同一会话记录 |
| 内存占用 | 会话越多堆内存越大,易 OOM | 不占用应用服务内存 |
| 适用场景 | 本地测试、单机临时演示 | 线上生产环境、分布式项目 |
3.定义 PromptTemplate 封装短信合规规则
PromptTemplate = 可复用提示词模板,统一固化短信合规要求:字数、禁用极限词、角色定位,全局复用
- 将短信固定规则(角色、字数、禁用极限词)统一封装;
- 支持固定文本 + 动态用户需求拼接;
- 一处修改全局生效,不用在每一次调用时重复抄写合规文案,统一管控运营商短信规范。
代码追加至上方配置类
java
import dev.langchain4j.prompt.PromptTemplate;
@Configuration
public class LangChain4jConfig {
// 步骤2 会话内存Bean 此处省略,保留原有代码
/**
* 全局短信合规提示词模板
*/
public static final PromptTemplate SMS_RULE_TEMPLATE = PromptTemplate.from("""
你是运营商合规短信文案专员,请严格遵守以下规范:
1. 单条短信字数控制在40~60字之间;
2. 严禁使用极限词、夸大宣传词汇;
3. 只输出短信正文,不要多余话术解释;
用户本次需求:{{userContent}}
""");
}
解释
PromptTemplate.from("文本"):创建固定提示词模板;- 三重引号:Java 文本块,方便多行文本编写,无需手动换行转义;
{``{userContent}}:模板占位符,后续会填充运营的活动需求;- 定义为常量,全局任意位置都可以复用这套短信规则。
构建 AiService(框架顶层封装对象,整合记忆、模板、模型、工具);
AiService 是 LangChain4j 最高层门面对象,将「大模型 + 对话记忆 + 提示词模板」三者绑定在一起,生成可直接调用的 AI 代理服务
一次性整合四大核心内容: 大模型客户端 + ChatMemory 对话记忆 + PromptTemplate 提示词模板 + Tools 工具调用
开发者无需手动拼接消息、组装请求,直接调用自定义接口方法即可发起对话。
完整代码(分为两部分:业务接口 + 配置类组装 Bean)
① 先定义 AI 业务接口
java
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
/**
* 短信AI生成服务接口
*/
public interface SmsAiService {
/**
* 生成营销短信
* @param sessionId 会话ID:绑定专属对话记忆
* @param userDemand 用户本次活动需求
* @return AI生成的短信文案
*/
String createSmsText(
@MemoryId String sessionId,
@UserMessage String userDemand
);
}
② 在配置类中组装生成 AiService 实例
java
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.AiServices;
@Configuration
public class LangChain4jConfig {
// 步骤2、步骤3代码保留不变
/**
* 组装顶层AiService,整合模型、记忆、提示词模板
*/
@Bean
public SmsAiService smsAiService(
ChatLanguageModel chatModel, // yml自动装配的通义千问大模型
ChatMemoryProvider memoryProvider // 步骤2创建的会话内存
) {
return AiServices.builder(SmsAiService.class)
// 绑定对接的大模型
.chatLanguageModel(chatModel)
// 绑定会话记忆管理器
.chatMemoryProvider(memoryProvider)
// 绑定全局短信系统提示词模板
.systemPromptTemplate(SMS_RULE_TEMPLATE)
.build();
}
}
代码注解详解
@MemoryId:标识该参数为会话 ID,框架根据此 ID 调取对应的历史聊天记录;@UserMessage:标识该参数为用户输入的业务需求;ChatLanguageModel:项目读取 yml 配置自动创建的通义千问客户端,负责底层 HTTP 调用大模型接口;AiServices.builder():框架提供的构建器,一站式整合所有组件;.build():生成接口代理实现类,交由 Spring 容器管理,可直接注入使用。
多次连续调用方法,自动携带历史对话,实现多轮对话。
编写 Controller 接收前端请求,使用同一个 sessionId 反复调用接口;
框架自动完成:读取历史对话 → 拼接系统模板 + 历史消息 + 新需求 → 请求大模型 → 保存本轮问答记录,实现连贯对话。
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SmsChatController {
// 注入组装完成的AI服务
private final SmsAiService smsAiService;
public SmsChatController(SmsAiService smsAiService) {
this.smsAiService = smsAiService;
}
/**
* 多轮短信生成接口
* @param sessionId 会话标识
* @param demand 用户需求
* @return 短信文案
*/
@GetMapping("/langchain/sms/chat")
public String smsChat(
@RequestParam String sessionId,
@RequestParam String demand
) {
return smsAiService.createSmsText(sessionId, demand);
}
}
多轮调用测试演示
第一轮请求
地址:/langchain/sms/chat?sessionId=op001&demand=写2条618家电促销短信
返回:两条合规家电短信,系统自动保存本次问答记录。
第二轮请求(同一个 sessionId)
地址:/langchain/sms/chat?sessionId=op001&demand=加上优惠券福利,每条控制50字以内
框架自动读取上一轮对话历史,AI 记住 618 家电活动,叠加新需求,不会丢失前置约束。
更换 sessionId=op002
全新空白会话,无任何历史记录,对话从头开始。
内部执行流转图

LangChain4j 多轮对话完整业务流程图

区分 SpringAI 与 LangChain4j 适用场景对比表
一、综合对比总表
| 对比维度 | SpringAI | LangChain4j |
|---|---|---|
| 核心定位 | Spring 生态轻量级大模型调用框架,主打快速对接 LLM | Java 端 AI 全链路编排框架,主打复杂 AI 流程组装 |
| 底层适配 | 深度整合 SpringBoot、WebFlux,无缝融入现有 Spring 微服务 | 无强 Spring 绑定,普通 Java 项目、Spring 项目均可使用 |
| 上手难度 | 极低,链式 API 简洁,开箱即用 | 中等,组件多、流程复杂,需要理解整套编排思想 |
| 核心优势 | 同步 / 流式一行代码、内置重试 / 超时 / 降级、多模型一键切换 | 原生支持对话记忆、工具调用、完整 RAG 流水线、多步骤任务 |
| 多轮对话支持 | 需手动拼接历史消息,无内置记忆组件 | 内置 ChatMemory,自动维护上下文,开箱即用 |
| 工具调用 | 无原生工具链,自定义实现复杂 | 原生 Tools 体系,简单注解即可让 AI 调用后端业务接口 |
| RAG 能力 | 仅基础文本问答,无完整分片 / 向量库链路封装 | 全套 RAG 组件:文档加载、文本切分、向量化、向量检索一体化 |
| 流式 SSE | 原生封装 stream (),适配前端实时预览 | 流式支持较弱,需要自行适配 WebFlux 做 SSE |
| 项目改动成本 | 极小,现有 Spring 项目快速接入 | 中等,复杂流程需要改造业务分层 |
二、短信业务场景细分对照表
| 业务场景 | 推荐框架 | 选择理由 |
|---|---|---|
| 单次同步生成营销短信(后台管理) | SpringAI | 代码极简,内置超时重试,不需要复杂上下文 |
| 前端实时打字预览 SSE 页面 | SpringAI | 原生封装流式输出,不用手动处理 Flux 分段逻辑 |
| 运营连续多轮调整文案(多次修改需求、叠加约束) | LangChain4j | ChatMemory 自动保存历史对话,不用手动拼接所有历史 Prompt |
| AI 自动校验商户额度、活动有效期后再生成文案(AI 调用业务接口) | LangChain4j | 原生 Tools 工具调用能力,AI 自主查询外部业务数据 |
| 接入企业合规知识库,检索规则后生成短信(完整 RAG 流程) | LangChain4j | 内置全套文档切片、向量入库、相似度检索组件 |
| 项目仅简单对接大模型,无复杂 AI 流水线 | SpringAI | 轻量无冗余组件,学习、维护成本低 |
| 后期需要拓展复杂 AI 多步骤任务(总结、检索、工具联动) | LangChain4j | 流程编排能力强,扩展性更好 |
选型背诵总结
- 追求快速开发、流式预览、简单单次问答、原生 Spring 适配 → 选 SpringAI;
- 需要多轮记忆对话、AI 工具调用、完整 RAG 知识库、复杂多步骤 AI 流程 → 选 LangChain4j。
RAG 检索增强生成
RAG 是什么、解决什么痛点
通俗定义
RAG 全称Retrieval-Augmented Generation 检索增强生成。
简单说:先检索企业私有文档资料,把真实业务规则塞给大模型,让 AI 基于内部真实数据回答,根治模型幻觉。
生活化类比
普通大模型 = 实习员工,只懂网上公开信息,公司内部短信合规规则完全不知道,容易乱编规定,导致商户短信违规封号;
RAG = 员工上岗前先翻阅公司《短信运营合规手册》,回答问题只允许参考手册内容,不能自己瞎编规则。
两大核心痛点(不用 RAG 会出现的问题)
- 模型幻觉:AI 凭空编造不存在的运营商合规要求、活动规则;
- 无法读取企业私有数据:公有大模型训练数据不包含你公司内部文档、专属业务规范。
RAG 核心价值
- 大幅减少 AI 虚假编造内容;
- 支持企业内部私有知识库(合规文档、活动模板、行业规则);
- 减少超长 Prompt,降低 Token 消耗,节约调用成本。
RAG 完整七步业务流程
文档加载 → 文本分片切片 → 文本向量化 → 向量存入向量库 → 用户提问向量化 → 相似度召回文档片段 → 拼接检索文档 + 提示词传入大模型 → AI 生成合规回答

RAG 全环节分步
RAG 分为两大阶段:
- 知识库构建阶段(离线一次性执行:步骤 1~4):提前把企业文档处理成向量存入数据库,后台定时 / 项目启动执行
- 用户问答检索阶段(线上实时执行:步骤 5~7):用户提问时实时执行,每次 AI 生成文案都要走这套流程
步骤 1:文档加载
举例比喻
公司有一堆合规手册(PDF、txt、md),就像一摞纸质规章制度;这一步相当于把所有文件全部拆开、扫描,把里面所有文字提取出来,丢掉图片、空白、乱码,只保留能用的业务规则。
核心作用
读取企业私有本地文档,提取纯文本,作为知识库原始素材;
支持文件:txt、markdown、PDF(短信合规规范、商户协议、活动规则)
执行时机
项目首次启动全量加载;凌晨定时任务增量更新新增文档。
流程图
本地文档文件(PDF/txt/md) → 文档解析器 → 清洗过滤空白/乱码 → 完整纯文本Document对象
完整代码 + 逐行注释
java
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import java.nio.file.Path;
public class Step1_DocumentLoad {
public static void main(String[] args) {
// 1.指定合规PDF文件路径
Path pdfPath = Path.of("doc/短信运营合规手册.pdf");
// 2.创建PDF专用解析器
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
// 3.加载文件并自动提取全部文字
Document document = FileSystemDocumentLoader.loadDocument(pdfPath, pdfParser);
// 4.获取清洗后的完整文本
String fullText = document.text();
System.out.println("提取后的文档原文:\n" + fullText);
}
}
关键点总结
只负责读取文字,不做拆分;文本过长会导致后续向量化、大模型调用 Token 超标,必须交给步骤 2 分片。
步骤 2:文本分片切片
举例比喻
一本几百页的规则书不能整本书拿去计算;把书裁剪成一张张纸条,相邻两张纸条保留一小段重复文字。
防止一条完整规则被一刀切断,一半在第一张、一半在第二张,检索时只能匹配半句话,丢失完整规则。
两种分片策略对比表
| 策略 | 配置方式 | 作用 | 不做的后果 |
|---|---|---|---|
| 固定长度分割 | 设置单段最大字符(例 800 字) | 限制单段文本长度,避免 Token 溢出 | 单段文字过长,向量精度下降、调用成本高 |
| 重叠分片 | 设置重叠字符(例 200 字) | 保证跨分片的完整语义不被截断 | 规则句子被拆分,检索不到完整合规条款 |
环节流程图
完整长文本 → 递归分片器分割 → 相邻片段预留重叠文字 → 生成多个独立 TextSegment 文本块
完整代码 + 注释
java
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import java.util.List;
public class Step2_SplitText {
public static void main(String[] args) {
// 步骤1加载出来的完整文档
Document document = Document.from("超长短信合规规则原文......");
// 分片器:单段最大800字,两段重叠200字
var splitter = DocumentSplitters.recursive(800, 200);
// 对长文本切片,得到多个小块
List<TextSegment> segmentList = splitter.split(document);
// 遍历打印所有分片
for (int i = 0; i < segmentList.size(); i++) {
System.out.println("分片" + (i+1) + ":" + segmentList.get(i).text());
}
}
}
常见踩坑
分片太小:单块只有零散词语,语义破碎,检索无意义; 分片太大:单块 Token 过多,向量相似度计算不准,浪费费用。
步骤 3:文本向量化Embedding
举例比喻
机器看不懂中文文字,只能识别数字;
每一张分片纸条,送入专门翻译器(Embedding 向量模型),翻译成一串多维数字编码。
语义相近的文字,数字编码在坐标系里距离很近;语义无关,距离很远。
例:「短信禁止最低价」和「营销不能用极限词」向量距离很近。
核心硬性规则
离线文档、用户提问必须使用同一个 Embedding 模型,向量维度完全一致,否则相似度计算完全失效。
流程图
分片文本 TextSegment → Embedding 向量化模型 → 多维浮点数组 Embedding 向量
完整代码(通义千问 Embedding)
java
import dev.langchain4j.model.dashscope.DashScopeEmbeddingModel;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
public class Step3_Embedding {
public static void main(String[] args) {
// 初始化通义千问向量化模型
DashScopeEmbeddingModel embeddingModel = DashScopeEmbeddingModel.builder()
.apiKey("sk-你的大模型密钥")
.modelName("text-embedding-v2")
.build();
// 步骤2拆分好的文本片段
TextSegment seg = TextSegment.from("短信禁止使用最高、第一、最低价等极限宣传词汇");
// 文本转向量
Embedding embedding = embeddingModel.embed(seg.text()).content();
// 打印数字向量数组
System.out.println("文本对应向量数组:" + embedding.vector());
}
}
步骤 4:向量存入向量数据库
举例比喻
把「纸条原文 + 对应的数字编码」成对存入专用档案柜(向量数据库),永久保存。后续用户提问时,拿着提问的数字编码,快速在档案柜匹配相近纸条。
PgVector vs Milvus 精简选型表
| 向量库 | 适用场景 | 运维成本 |
|---|---|---|
| PgVector | 中小型项目、百万向量内,已有 Postgres | 极低,复用现有数据库 |
| Milvus | 海量知识库、百万 / 亿级向量、高并发集群 | 高,独立部署维护集群 |
存储每条数据结构
唯一 ID + 原始文本分片 TextSegment + 多维向量 Embedding
流程图
分片文本 + 对应向量 → 批量写入向量库表 → 持久化存储,建立向量索引加速检索
Maven 依赖
java
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-store-pgvector</artifactId>
<version>0.34.0</version>
</dependency>
入库完整代码
java
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.model.dashscope.DashScopeEmbeddingModel;
public class Step4_SaveVector {
public static void main(String[] args) {
// 1.初始化PgVector向量存储
PgVectorEmbeddingStore store = PgVectorEmbeddingStore.builder()
.host("127.0.0.1")
.port(5432)
.database("sms_knowledge_db")
.user("postgres")
.password("123456")
.table("sms_rule_knowledge") // 存储合规规则的表
.dimension(1536) // 和Embedding模型向量维度保持一致
.build();
// 2.构造分片文本+向量(来自步骤2、3)
TextSegment seg = TextSegment.from("短信禁止极限宣传词");
DashScopeEmbeddingModel embeddingModel = DashScopeEmbeddingModel.builder().apiKey("sk-xxx").build();
Embedding embedding = embeddingModel.embed(seg.text()).content();
// 3.向量+原文存入数据库
store.add(embedding, seg);
System.out.println("向量入库完成");
}
}
以上 4 步 = 离线知识库构建,一次性执行完成
步骤 5:用户提问向量化
举例比喻
运营提问:"短信可以写最低价吗?"
把用户这句话,用和文档完全相同的翻译器,生成一串数字编码,用来和库里档案对比。
流程图
用户原始问句 → 同一个 Embedding 模型 → 提问向量
极简代码
java
// 用户输入问题
String userQuestion = "营销短信能不能使用最低价宣传?";
// 复用步骤3同一个向量化模型
DashScopeEmbeddingModel embeddingModel = DashScopeEmbeddingModel.builder().apiKey("sk-xxx").build();
Embedding questionEmbedding = embeddingModel.embed(userQuestion).content();
步骤 6:相似度召回匹配文档片段
举例比喻
拿着用户提问的数字编码,遍历档案柜里所有编码,计算距离;
设置门槛(相似度阈值 0.7),只保留匹配度 70% 以上的纸条,无关内容直接过滤丢弃。
阈值参数说明
- minScore=0.7(通用生产推荐)
- 阈值过高 0.9:召回内容太少,缺少参考规则,回答不完整
- 阈值过低 0.5:大量无关文档混入,干扰 AI、浪费 Token 费用
流程图
提问向量 → 向量库索引快速相似度计算 → 过滤低于阈值数据 → 返回匹配的业务文档片段
检索完整代码
java
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
public class Step6_SearchVector {
public static void main(String[] args) {
// 初始化向量库
PgVectorEmbeddingStore store = PgVectorEmbeddingStore.builder()
.host("127.0.0.1")
.port(5432)
.database("sms_knowledge_db")
.user("postgres")
.password("123456")
.table("sms_rule_knowledge")
.dimension(1536)
.build();
// 步骤5生成的提问向量
Embedding questionEmbedding = ...;
// 构建检索条件
EmbeddingSearchRequest searchReq = EmbeddingSearchRequest.builder()
.queryEmbedding(questionEmbedding)
.maxResults(3) // 最多返回3条匹配文档
.minScore(0.7) // 相似度门槛0.7
.build();
// 执行检索
EmbeddingSearchResult result = store.search(searchReq);
// 遍历打印匹配到的合规规则
result.matches().forEach(match -> {
System.out.println("匹配相似度分数:" + match.score());
System.out.println("合规原文:" + match.embedded().text());
});
}
}
步骤 7:拼接上下文交给大模型生成答案
举例比喻
把检索到的几条合规纸条、用户问题、系统角色约束打包交给 AI;
强制 AI 只能参考纸条上真实企业规则作答,不允许凭空编造运营商规范,从根源解决模型幻觉。
流程图
检索到的知识库片段 + 系统角色约束 + 用户提问 → 拼接完整 Prompt → 送入 LLM 大模型 → 返回精准合规答案
标准 Prompt 模板
你是短信合规审核专员,只能依据下方【企业内部合规资料】回答,严禁编造不存在的规则。
【企业内部合规资料】
{检索出来的多条知识库文本}
用户问题:{用户原始提问}
请简洁准确给出合规答复。
业务整合代码(结合 ChatClient)
java
import org.springframework.ai.chat.client.ChatClient;
public class Step7_GenerateAnswer {
public static void main(String[] args) {
// 1.拼接所有检索到的知识库内容
StringBuilder knowledgeText = new StringBuilder();
EmbeddingSearchResult result = ...; // 步骤6检索结果
result.matches().forEach(match -> knowledgeText.append(match.embedded().text()).append("\n"));
String userQuestion = "营销短信能不能使用最低价宣传?";
// 2.组装完整提示词
String prompt = """
你是短信合规审核专员,只能依据下方【企业内部合规资料】回答,严禁编造不存在的规则。
【企业内部合规资料】
%s
用户问题:%s
""".formatted(knowledgeText, userQuestion);
// 3.调用SpringAI ChatClient生成回答
ChatClient chatClient = ChatClient.builder().build();
String answer = chatClient.prompt()
.user(prompt)
.call()
.content();
System.out.println("AI最终合规回答:" + answer);
}
}
7 步完整汇总总表
| 阶段 | 步骤 | 执行时机 | 核心产出 | 解决什么问题 |
|---|---|---|---|---|
| 离线知识库构建 | 1 文档加载 | 项目启动 / 定时任务 | 完整文档纯文本 | 读取企业私有内部资料 |
| 离线知识库构建 | 2 文本分片切片 | 离线一次性 | 多个短文本片段 | 控制文本长度,防止语义被切断 |
| 离线知识库构建 | 3 文本向量化 | 离线一次性 | 文本对应数字向量 | 实现语义相似度计算,让机器看懂文字含义 |
| 离线知识库构建 | 4 向量入库 | 离线一次性 | 向量 + 文本持久化存储 | 建立可快速检索的私有知识库 |
| 在线实时问答 | 5 提问向量化 | 用户每次提问实时执行 | 用户问句向量 | 和库内文档向量做相似度匹配 |
| 在线实时问答 | 6 相似度召回 | 用户每次提问实时执行 | 高匹配度业务文档片段 | 筛选和用户问题相关的内部规则 |
| 在线实时问答 | 7 拼接 Prompt 生成答案 | 用户每次提问实时执行 | 无幻觉、贴合内部资料的回答 | 限制 AI 只能基于真实企业文档输出,根治模型幻觉 |
向量数据库对比表:PgVector VS Milvus
| 对比维度 | PgVector | Milvus |
|---|---|---|
| 底层本质 | PostgreSQL 数据库的扩展插件,依附 Postgres 运行 | 独立、原生分布式专用向量数据库,计算存储分离架构 |
| 适配向量数据量级 | 中小规模,500 万条以内向量最优,超过千万性能明显下滑 | 海量场景,千万~百亿级向量,支持持续扩容Zilliz |
| 部署与运维成本 | 极低,无需新增中间件;复用现有 Postgres 备份、主从、监控、权限体系 | 高,需要单独部署集群、维护多组件(Proxy/QueryNode/IndexNode),学习运维成本高 |
| 查询性能 | 百万级延迟 50~200ms;不支持 GPU 加速;高并发 QPS 上限低 | 延迟稳定<50ms,支持 GPU 加速、多核并行,高并发吞吐是 PgVector 数倍 |
| 混合查询能力(向量 + 业务字段) | 原生支持 SQL,向量检索可 JOIN 业务表、事务 ACID、多条件过滤,非常适合业务系统 RAG | 只专注纯向量检索,结构化业务过滤能力弱,向量和业务数据需分库存储,多库联调复杂 |
| 技术栈学习成本 | 极低,会 SQL 就能直接使用向量检索,不用学习新 API | 高,有独立 SDK、集合管理、分区索引专属语法,需要单独学习 |
| 内存占用 | HNSW 索引占用内存高,高维向量容易内存溢出 | 内置向量压缩算法,内存管控更精细,海量数据内存压力更小 |
| 扩容扩展能力 | 横向分片扩展困难,依赖 Postgres 原生主从,向量检索无法水平分摊压力 | 原生分布式,支持弹性扩容、冷热数据分离、集群负载均衡 |
| 短信平台适配场景 | 中小型短信系统,合规文档、规则库总量几万~百万条,已有 Postgres 业务库 | 大型政企短信平台,百万级以上合规文件、海量商户知识库,高并发检索 |
| 生活化类比 | 家用厨房加装空气炸锅,现有厨具复用,不用额外空间 | 专业商用中央后厨,专门处理大批量订单,需要独立场地维护 |
优缺点拆分表
PgVector
✅ 优点
- 不用新增中间件,复用项目现有 PostgreSQL,架构轻量化;
- 向量数据和商户、活动、合规业务数据同库,支持事务、联表查询;
- 上手简单,标准 SQL 操作向量,开发速度快;
- 中小项目运维零额外成本,备份、监控复用现有方案。
❌ 缺点
- 向量超过 500 万后查询延迟暴涨,并发能力不足;
- 不支持 GPU 加速、冷热分离等高级向量特性;
- 分布式分片复杂,无法支撑超大规模知识库。
Milvus
✅ 优点
- 底层专为向量检索优化,海量数据下低延迟、高并发;
- 分布式弹性扩容,支持 GPU 加速、多种向量索引;
- 支持百亿级向量存储检索,适配超大知识库 RAG 场景。
❌ 缺点
- 新增一套独立中间件,运维、部署、监控成本大幅上升;
- 无法和业务库做 SQL 联查,业务数据与向量数据分离,开发复杂度提升;
- 团队需要额外学习 Milvus 专属 API、集群调优知识。
项目选型决策(短信 AI RAG 场景)
-
中小型短信营销平台(推荐 PgVector) 知识库文档几十万条以内,已有 PostgreSQL 业务库,追求少组件、易维护、快速开发,选 PgVector。
-
大型政企 / 多租户短信平台(推荐 Milvus) 合规文档、商户知识库超百万条,并发检索量大,需要极低查询延迟、支持水平扩容,选 Milvus。
-
通用演进方案:初期使用 PgVector 快速落地 RAG,后续业务数据量上涨、性能不足时,平滑迁移至 Milvus。
RAG 落地优化方案
- 调整分片大小:分片过小语义断裂,分片过大 Token 消耗高,测试找到平衡值;
- 合理设置相似度阈值:阈值过高召回太少,过低带入无关文档干扰 AI;
- 增加重排序:召回多条文档后,二次筛选相关性最高片段;
- 后端二次校验:AI 输出文案后,敏感词、极限词拦截双重兜底,进一步规避违规。