Spring AI Alibaba 核心知识点
-
- [1. ChatClient](#1. ChatClient)
- [2. Embedding Model-嵌入模型](#2. Embedding Model-嵌入模型)
- [3. Function Calling-工具](#3. Function Calling-工具)
-
- [3.1 为什么需要工具调用](#3.1 为什么需要工具调用)
- [3.2 核心原理:六步闭环](#3.2 核心原理:六步闭环)
- [3.3 定义工具的三种方式](#3.3 定义工具的三种方式)
-
- [3.3.1 @Tool注解(声明式,最推荐)](#3.3.1 @Tool注解(声明式,最推荐))
- [3.3.2 FunctionToolCallback(函数式)](#3.3.2 FunctionToolCallback(函数式))
- [3.3.3 MethodToolCallback(编程式,最精细)](#3.3.3 MethodToolCallback(编程式,最精细))
- [3.4 工具的注册与作用域](#3.4 工具的注册与作用域)
- [3.5 三个关键机制](#3.5 三个关键机制)
-
- [3.5.1 returnDirect:结果是否直返](#3.5.1 returnDirect:结果是否直返)
- [3.5.2 ToolContext:传递上下文](#3.5.2 ToolContext:传递上下文)
- [3.5.3 结果转换与不支持的类型](#3.5.3 结果转换与不支持的类型)
- [3.6 Spring AI 2.0的重大架构升级](#3.6 Spring AI 2.0的重大架构升级)
- [3.7 官方预置工具生态](#3.7 官方预置工具生态)
- [3.8 生产环境最佳实践](#3.8 生产环境最佳实践)
- [3.9 常见问题速查](#3.9 常见问题速查)
- [4. Chat Memory-对话记忆](#4. Chat Memory-对话记忆)
-
- [4.1 核心概念](#4.1 核心概念)
-
- [4.1.1 什么是Chat Memory](#4.1.1 什么是Chat Memory)
- [4.1.2 三层架构](#4.1.2 三层架构)
- [4.2 默认行为](#4.2 默认行为)
- [4.3 MessageWindowChatMemory的窗口裁剪规则](#4.3 MessageWindowChatMemory的窗口裁剪规则)
- [4.4 快速接入](#4.4 快速接入)
-
- [4.4.1 基础用法(内存存储)](#4.4.1 基础用法(内存存储))
- [4.4.2 自定义窗口大小](#4.4.2 自定义窗口大小)
- [4.5 生产级存储选型](#4.5 生产级存储选型)
-
- [4.5.1 JDBC存储(MySQL示例)](#4.5.1 JDBC存储(MySQL示例))
- [4.5.2 其他存储后端](#4.5.2 其他存储后端)
- [4.6 生产环境最佳实践](#4.6 生产环境最佳实践)
- [4.7 总结](#4.7 总结)
- [5. Prompt-提示词](#5. Prompt-提示词)
-
- [5.1 核心概念](#5.1 核心概念)
-
- [5.1.1 Prompt的本质](#5.1.1 Prompt的本质)
- [5.1.2 四大消息角色(MessageType)](#5.1.2 四大消息角色(MessageType))
- [5.2 核心API结构](#5.2 核心API结构)
-
- [5.2.1 三层对象关系](#5.2.1 三层对象关系)
- [5.2.2 PromptTemplate三大接口](#5.2.2 PromptTemplate三大接口)
- [5.3 三种使用方式](#5.3 三种使用方式)
-
- [5.3.1 方式一:PromptTemplate(底层模板)](#5.3.1 方式一:PromptTemplate(底层模板))
- [5.3.2 方式二:SystemPromptTemplate(系统消息专用)](#5.3.2 方式二:SystemPromptTemplate(系统消息专用))
- [5.3.3 方式三:ChatClient Fluent API(推荐)](#5.3.3 方式三:ChatClient Fluent API(推荐))
- [5.4 StringTemplate占位符语法](#5.4 StringTemplate占位符语法)
- [5.5 外部化模板(生产推荐)](#5.5 外部化模板(生产推荐))
- [5.6 ChatClient中的Prompt配置](#5.6 ChatClient中的Prompt配置)
-
- [5.6.1 默认系统提示词](#5.6.1 默认系统提示词)
- [5.6.2 运行时覆盖](#5.6.2 运行时覆盖)
- [5.6.3 多模态(媒体内容)](#5.6.3 多模态(媒体内容))
- [5.7 生产环境最佳实践](#5.7 生产环境最佳实践)
-
- [5.7.1 提示词工程原则](#5.7.1 提示词工程原则)
- [5.7.2 模板管理策略](#5.7.2 模板管理策略)
- [5.7.3 常见陷阱](#5.7.3 常见陷阱)
- [5.8 总结](#5.8 总结)
- [6. Document Retriever-文档检索](#6. Document Retriever-文档检索)
-
- [6.1 核心定位](#6.1 核心定位)
- [6.2 依赖与配置](#6.2 依赖与配置)
-
- [6.2.1 引入依赖](#6.2.1 引入依赖)
- [6.2.2 配置API Key](#6.2.2 配置API Key)
- [6.3 创建检索器](#6.3 创建检索器)
-
- [6.3.1 核心代码](#6.3.1 核心代码)
- [6.3.2 可选的高级参数](#6.3.2 可选的高级参数)
- [6.4 接入ChatClient(RAG完整链路)](#6.4 接入ChatClient(RAG完整链路))
- [6.5 百炼知识库的两种创建方式](#6.5 百炼知识库的两种创建方式)
- [6.6 与VectorStore RAG的区别](#6.6 与VectorStore RAG的区别)
- [6.7 生产环境最佳实践](#6.7 生产环境最佳实践)
- [6.8 总结](#6.8 总结)
- [7. Structured Output-格式化输出](#7. Structured Output-格式化输出)
-
- [7.1 核心价值](#7.1 核心价值)
- [7.2 四种内置转换器](#7.2 四种内置转换器)
- [7.3 ChatClient高层用法(推荐)](#7.3 ChatClient高层用法(推荐))
-
- [7.3.1 基础POJO/Record映射](#7.3.1 基础POJO/Record映射)
- [7.3.2 泛型类型(List、Map)](#7.3.2 泛型类型(List、Map))
- [7.3.3 底层ChatModel用法](#7.3.3 底层ChatModel用法)
- [7.4 可靠性开关(Spring AI 2.0+)](#7.4 可靠性开关(Spring AI 2.0+))
-
- [7.4.1 validateSchema():自纠错重试](#7.4.1 validateSchema():自纠错重试)
- [7.4.2 useProviderStructuredOutput():厂商原生结构化输出](#7.4.2 useProviderStructuredOutput():厂商原生结构化输出)
- [7.5 流式输出的限制](#7.5 流式输出的限制)
- [7.6 Agent场景的结构化输出](#7.6 Agent场景的结构化输出)
- [7.7 生产环境最佳实践](#7.7 生产环境最佳实践)
- [7.8 总结](#7.8 总结)
- [8. Vector Store-向量存储](#8. Vector Store-向量存储)
-
- [8.1 核心定位](#8.1 核心定位)
- [8.2 两种使用模式](#8.2 两种使用模式)
-
- [8.2.1 模式一:百炼全托管(`DashScopeCloudStore`)](#8.2.1 模式一:百炼全托管(
DashScopeCloudStore)) - [8.2.2 模式二:本地/第三方向量数据库](#8.2.2 模式二:本地/第三方向量数据库)
- [8.2.1 模式一:百炼全托管(`DashScopeCloudStore`)](#8.2.1 模式一:百炼全托管(
- [8.3 文档入库与检索](#8.3 文档入库与检索)
-
- [8.3.1 文档入库](#8.3.1 文档入库)
- [8.3.2 相似度检索](#8.3.2 相似度检索)
- [8.4 百炼知识库的两种创建方式](#8.4 百炼知识库的两种创建方式)
- [8.5 与RAG的整合](#8.5 与RAG的整合)
- [8.6 支持的向量数据库](#8.6 支持的向量数据库)
- [8.7 生产环境最佳实践](#8.7 生产环境最佳实践)
- [8.8 总结](#8.8 总结)
1. ChatClient
1.1 ChatModel
ChatModel是Spring AI定义的统一模型适配接口,是AI交互的底层抽象,支撑通义系列模型的聊天、文生图、音频转录等多场景,同时支持同步与流式API。
其核心逻辑是接收Prompt作为输入,调用后端大模型生成响应,返回ChatResponse供应用层处理。同步调用使用call(Prompt),流式调用返回Flux<ChatResponse>。
核心接口
- ChatModel:底层原子接口,适合需要精细控制模型通信的场景。
- ChatClient:高层Fluent API,封装了Prompt组装、输出解析、Advisor编排等复杂度,是日常开发的首选。
依赖与配置
Maven依赖:
java
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
application.yaml核心配置:
java
spring:
ai:
model:
chat: dashscope # 启用DashScope Chat Model,设为none可禁用
dashscope:
chat:
api-key: ${AI_DASHSCOPE_API_KEY}
work-space-id: xxx # 可选,指定百炼工作空间ID
options:
model: qwen-plus # 可选模型:qwen-turbo/qwen-max/qwen-max-longcontext等
两层参数配置
参数支持全局默认与单次调用动态覆盖,后者优先级更高:
- 全局默认配置:在ChatClient初始化时指定,对所有请求生效。
java
DashScopeApi dashScopeApi = DashScopeApi.builder()
.apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
.build();
ChatModel chatModel = DashScopeChatModel.builder()
.dashScopeApi(dashScopeApi)
.defaultOptions(DashScopeChatOptions.builder()
.withModel(DashScopeChatModel.DEFAULT_MODEL_NAME)
.withTemperature(0.5)
.withMaxToken(1000)
.build())
.build();
也可通过spring.ai.dashscope.chat.options.*在配置文件中全局指定。
- 单次调用动态覆盖:针对特定请求调整参数,不影响全局配置。
java
ChatResponse response = chatModel.call(
new Prompt(
"Generate the names of 5 famous pirates.",
DashScopeChatOptions.builder()
.withModel("qwen-plus")
.withTemperature(0.4F)
.build()
)
);
常用参数:model(模型名)、temperature(发散度,0-1,值越高输出越随机)、topP、topK、maxToken、frequencyPenalty等。
调用方式完整图谱
- 同步调用
.call().content():返回纯字符串响应,适合后台任务、短文本场景。.call().chatResponse():返回完整ChatResponse,包含token用量、模型信息等元数据。.call().entity(Class):直接映射为Java POJO,用于结构化输出场景。
- 流式调用 :
.stream().content()返回Flux<String>,适合前端打字机效果场景。 - 提示模板:支持运行时变量替换,示例如下:
java
String answer = chatClient.prompt()
.user(u -> u.text("Tell me the names of 5 movies whose soundtrack was composed by {composer}")
.param("composer", "John Williams"))
.call()
.content();
生产环境要点
- 无状态局限:原生Chat Model调用无状态,多轮对话需配合Chat Memory与Advisor机制实现上下文传递。
- 超时与重试:需在DashScopeApi或HTTP客户端层配置合理的连接超时、读取超时,搭配失败重试策略,应对SaaS服务的网络抖动。
- 参数调优经验值 :
- 客服/问答场景:
temperature设为0.1~0.3,保证输出准确性。 - 创意写作场景:
temperature设为0.7~0.9,提升输出发散性。 - 代码生成场景:
temperature设为0.2~0.4,平衡准确性与多样性。
- 客服/问答场景:
1.2 ChatClient
ChatClient是构建在ChatModel之上的高层Fluent API,类比AI版的RestClient,封装了Prompt组装、输出解析、对话记忆、RAG、工具调用等复杂逻辑,让开发者以接近自然语言的链式调用完成AI交互。
创建方式
最常用的是注入Spring Boot自动配置的ChatClient.Builder:
java
@RestController
@RequestMapping("/ai")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
}
也可通过ChatClient.builder(chatModel)编程式创建,在Builder阶段预设defaultSystem、defaultOptions、defaultAdvisors。
核心调用链
最经典的同步字符串返回写法:
java
String response = chatClient.prompt()
.user("解释一下Spring Boot")
.call()
.content();
链式调用语义清晰,各环节职责明确:
.prompt():开启一次对话构建。.system()/.user():设置系统消息/用户消息,支持{placeholder}模板变量运行时替换。.call():同步调用,等待完整响应。.stream():流式调用,返回Flux<String>,适合长文本生成、降低用户感知延迟。.content():抽取纯文本响应。.entity(Class):直接映射为Java POJO,用于结构化输出。.chatResponse():获取包含token用量等元数据的完整响应对象。
调用选择建议 :后台任务、API间调用、短响应用call();聊天界面、长文本生成用stream()。
关键扩展点:Advisor
Advisor是ChatClient的拦截器机制,类比Spring MVC的HandlerInterceptor,在请求发往模型前、响应返回后插入横切逻辑,是ChatClient隐藏复杂度的核心设计。
- 构建时注册(推荐):在ChatClient初始化时预设Advisor,对所有请求生效。
java
ChatMemory chatMemory = ...;
VectorStore vectorStore = ...;
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(), // 对话记忆注入
QuestionAnswerAdvisor.builder(vectorStore).build() // RAG检索增强
)
.build();
- 调用时传参:针对单次请求动态调整Advisor参数,如指定会话ID。
java
String response = chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "678"))
.user(userText)
.call()
.content();
内置Advisor一览:
MessageChatMemoryAdvisor:将对话历史作为messages集合注入Prompt。PromptChatMemoryAdvisor:将记忆合并进system text。VectorStoreChatMemoryAdvisor:从向量库检索相关记忆注入。QuestionAnswerAdvisor:Naive RAG实现,从向量库检索文档增强问答。SafeGuardAdvisor:内容安全护栏。ReReadingAdvisor:RE2推理策略,增强输入理解。
Advisor链按getOrder()排序执行,值小的先执行;最后一个Advisor由框架自动添加,负责最终调用LLM。
生产环境要点
- 优先使用ChatClient:90%场景使用ChatClient即可,仅在需要极细粒度控制模型通信时回落至ChatModel。
- 多模型场景适配 :不同任务绑定不同模型(复杂推理用qwen-max,简单问答用qwen-turbo),通过
@Qualifier注入多个ChatClient Bean实现。 - 预设默认配置 :在Builder阶段通过
defaultSystem()设置全局系统提示词,通过defaultOptions()预设temperature、maxTokens等参数,避免每次调用重复配置。
java
ChatClient.builder(chatModel)
.defaultSystem("你是客服助手,回答限制在2-4句")
.defaultOptions(DashScopeChatOptions.builder()
.withTemperature(0.2)
.withMaxToken(400)
.build())
.build();
- 结构化输出优先用
**.entity()**:要求模型按固定格式返回时,直接映射到Record或POJO,省去手动JSON解析成本。
java
record Movie(String title, String director, int year) {}
Movie movie = chatClient.prompt()
.user("推荐一部经典电影")
.call()
.entity(Movie.class);
ChatClient核心定位 :Fluent API入口 + Prompt链式组装 + Advisor拦截器链 + 同步/流式/结构化三种返回形态。开发阶段注入ChatClient.Builder,用.prompt().user().call().content()跑通主干,需要扩展记忆、RAG、工具调用时往defaultAdvisors中添加对应Advisor即可。
1.3 ChatModel和ChatClient的关系
两者的协作链路清晰分层:
| 步骤 | 说明 |
|---|---|
| 调用 | Controller → ChatClient(Fluent API入口) |
| 委托 | ChatClient → ChatModel(核心调用逻辑,执行call()/stream()) |
| 增强 | ChatClient内部 → Advisor链(日志/重试/RAG/工具调用等横切逻辑) |
| HTTP请求 | ChatModel → LLM(通义千问/百炼等后端模型) |
| 返回 | LLM → ChatModel → ChatClient(封装为ChatResponse) |
简言之,ChatClient组合并委托ChatModel完成实际模型调用,所有增强逻辑通过Advisor链实现。
2. Embedding Model-嵌入模型
嵌入模型是RAG流水线的语义理解核心,负责将文本转换为高维向量(浮点数数组),支撑后续的相似度匹配。其接口设计围绕两个目标:一是可移植性,不同厂商模型切换成本极低;二是简单性,提供embed(String)、embed(Document)、call(EmbeddingRequest)等易用方法。
一次嵌入调用的数据流转:文本 → EmbeddingRequest → EmbeddingModel.call() → EmbeddingResponse → 向量数组(List<Double>)。
接入配置
引入依赖后即自动配置DashScopeEmbeddingModel Bean:
java
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
</dependency>
API Key配置(二选一):
java
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
或通过环境变量注入:export SPRING_AI_DASHSCOPE_API_KEY=<YOUR_API_KEY>
Embedding专属配置:
java
spring:
ai:
dashscope:
embedding:
options:
model: text-embedding-v3
dimensions: 1024
text-type: document # document(入库文本)/query(查询文本),用于优化嵌入质量
可用的嵌入模型包括text-embedding-v1/v2/v3/v4、qwen2.5-vl-embedding、tongyi-embedding-vision-plus等;其中text-embedding-v3支持641024多档维度,`v4`支持642048维度。
提示 :spring.ai.dashscope.embedding.*专属配置优先级高于通用spring.ai.dashscope.*,可实现Embedding与Chat使用不同账号/Endpoint。
调用方式
注入后即可直接使用:
java
@RestController
public class EmbeddingController {
private final EmbeddingModel embeddingModel;
@Autowired
public EmbeddingController(EmbeddingModel embeddingModel) {
this.embeddingModel = embeddingModel;
}
@GetMapping("/ai/embedding")
public Map<String, Object> embed(@RequestParam String message) {
EmbeddingResponse response = embeddingModel.embedForResponse(List.of(message));
return Map.of("embedding", response);
}
}
运行时动态覆盖选项:
java
EmbeddingResponse response = embeddingModel.call(
new EmbeddingRequest(
List.of("Hello World"),
DashScopeEmbeddingOptions.builder()
.withModel("text-embedding-v3")
.withDimensions(768)
.build()
)
);
所有spring.ai.dashscope.embedding.options.*前缀的配置,都可在单次EmbeddingRequest中通过DashScopeEmbeddingOptions运行时覆盖。
在RAG中的关键作用
嵌入模型承担RAG流水线的两个核心向量化环节:
- 文档入库阶段 :
VectorStore.add(documents)内部自动调用EmbeddingModel,将文档切片转为向量后存入向量库。 - 用户查询阶段:将用户问题向量化,在VectorStore中执行余弦相似度/欧氏距离检索,召回最相关的文档片段。
java
// 入库:VectorStore内部自动调用EmbeddingModel
vectorStore.add(List.of(
new Document("i study LLM"),
new Document("i love java")
));
// 查询:用户问题经EmbeddingModel向量化后检索
List<Document> results = vectorStore.similaritySearch("学习大语言模型");
生产环境要点
- 模型选型建议 :中文场景首选
text-embedding-v3,支持1024维(可调至64~1024)、50+语种、最大8192 token,中文效果最优;旧版text-embedding-v1仅1536维且仅支持中英文。维度越低,存储和计算成本越低,但语义表达能力会下降。 - text-type必须设置 :入库文档设为
document,用户查询设为query,可显著提升嵌入质量。 - 维度一致性是硬约束 :同一应用的所有向量必须维度相同,一旦选定
dimensions,后续所有入库、查询、向量字段维度都要对齐,中途修改会导致相似度计算失效。 - 批量处理更高效 :单次
EmbeddingRequest支持传入文本列表批量向量化,建议批量大小10~100条,减少API调用次数。 - 入库幂等性:同一文档多次入库会产生重复向量,需基于文档hash或业务ID做去重;文档变更时,先按元数据过滤删除旧向量,再重新入库。
- 成本与限流 :Embedding API按token消耗计费且有速率限制,建议对高频访问的嵌入结果做缓存,并通过
RetryTemplate实现指数退避重试。
3. Function Calling-工具
3.1 为什么需要工具调用
大模型存在两个核心局限:无实时信息(无法获知当前时间、天气等动态数据)、无法执行真实动作(无法下单、发邮件、操作数据库)。工具调用(Tool Calling)正是为解决这两个问题而生,其本质是:模型仅负责决策"是否调用工具、调用哪个工具、传递什么参数",真正执行工具的是Java应用,执行结果再回喂给模型生成最终回答。模型永远不会直接接触注册的API,这是核心安全边界。
工具调用主要服务两类场景:
- 信息检索类:查天气、查数据库、搜网页、读文件,扩展模型知识边界,是RAG的重要延伸。
- 执行动作类:发邮件、创单、触发工作流,将模型的"计划"转化为真实业务操作。
3.2 核心原理:六步闭环
一次完整的工具调用是应用与模型的多轮交互过程:
- 应用将"用户问题+工具定义(名称/描述/参数Schema)"发给模型。
- 模型推理:判断是否需要调用工具。
- 若需要调用:模型返回
tool_call(工具名+参数JSON);若不需要:模型直接返回文本回答,流程结束。 - 应用根据
tool_call找到对应Java方法并执行。 - 执行结果作为工具消息回喂给模型。
- 模型基于结果生成最终自然语言回答;若结果仍包含
tool_call,则继续循环。
Spring AI 2.0中,上述循环被抽象为递归Advisor------ToolCallingAdvisor,由DefaultChatClient自动注册到Advisor链中,驱动请求/响应循环直到模型不再返回工具调用请求。这使得工具调用从黑盒逻辑变为白盒可观测、可扩展的组件,支持挂载拦截器、日志观测、权限校验、自定义循环终止条件。
3.3 定义工具的三种方式
Spring AI Alibaba提供三种将Java逻辑暴露为AI工具的方案,按推荐程度排序:
3.3.1 @Tool注解(声明式,最推荐)
在任意方法上标注@Tool,Spring AI自动生成JSON Schema并注册为工具,方法可以是静态/实例、任意可见性,所在类可以是顶层或嵌套类。
java
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
}
@Tool核心属性:
| 属性 | 说明 | 建议 |
|---|---|---|
| name | 工具名,默认取方法名;同一请求内必须唯一 | 显式命名,避免重载歧义 |
| description | 工具描述,模型据此判断调用时机 | 必填,强烈建议写详细 |
| returnDirect | true=结果直返调用方,不回喂模型 | 默认false |
| resultConverter | 自定义结果→String的转换器 | 一般无需设置 |
参数用@ToolParam(description = "...")标注,@Nullable参数被视为可选。
调用方示例:
java
String response = ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
3.3.2 FunctionToolCallback(函数式)
适合已有Function实现、或偏好函数式接口的场景:
java
public class TimeFunction implements Function<TimeFunction.Request, String> {
public record Request(String zoneId) {}
public String apply(Request request) {
return ZonedDateTime.now(ZoneId.of(request.zoneId)).toString();
}
}
// 注册并调用
String response = chatClient.prompt("Obtain Beijing time")
.toolCallbacks(FunctionToolCallback
.builder("getTimeByZoneId", new TimeFunction())
.description("Get time by zone id")
.inputType(TimeFunction.Request.class)
.build())
.call()
.content();
3.3.3 MethodToolCallback(编程式,最精细)
适合需要完全手动控制方法、对象、Schema的动态场景,灵活性最高但配置成本也最高。
3.4 工具的注册与作用域
工具可注册在两个层级,作用域完全不同:
- 单次调用级 :仅对本次请求生效,通过
.tools()或.toolCallbacks()指定。
java
chatClient.prompt("...")
.tools(new DateTimeTools())
.call();
- 全局默认级 :从该ChatClient发起的所有请求共享,通过
defaultTools()或defaultToolCallbacks()预设。
java
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools())
.build();
风险提示:全局工具在所有聊天请求间共享,若使用不当可能导致非预期调用,属于P0级安全风险。若同时提供默认和运行时tools,运行时tools将完全覆盖默认tools。
3.5 三个关键机制
3.5.1 returnDirect:结果是否直返
默认情况下,工具执行结果会回喂模型,由模型加工后回复用户。部分场景(如查询类、需要原样返回的数据)可设置returnDirect = true,跳过模型加工,直接将结果返回给调用方。
java
@Tool(description = "Get time by zone id", returnDirect = true)
public String getTimeByZoneId(String zoneId) { ... }
或在FunctionToolCallback中通过ToolMetadata.builder().returnDirect(true).build()配置。
3.5.2 ToolContext:传递上下文
除模型提供的参数外,应用可向工具注入额外上下文(如当前用户ID、租户信息),在多租户、权限隔离场景下至关重要。
java
public class UserInfoTools {
@Tool(description = "get current user name")
public String getUserName(ToolContext context) {
String userId = context.getContext().get("userId").toString();
// ...业务逻辑
}
}
// 调用时注入上下文
chatClient.prompt("Get my username")
.tools(new UserInfoTools())
.toolContext(Map.of("userId", "12345"))
.call();
3.5.3 结果转换与不支持的类型
工具返回值默认通过DefaultToolCallResultConverter(基于Jackson)序列化为JSON字符串回喂模型,可通过resultConverter自定义。
以下类型不支持作为工具的输入/输出,设计工具方法时需避开:
- 基础类型(Primitive)
Optional- 集合类型(
List/Map/Array/Set) - 异步类型(
CompletableFuture/Future) - 响应式类型(
Flow/Mono/Flux)
3.6 Spring AI 2.0的重大架构升级
Spring AI 2.0对工具调用循环做了彻底重构,核心变化如下:
- 1.x时代:每个ChatModel实现各自维护私有工具执行循环,功能可用但不可观测、不可扩展。
- 2.0时代:工具循环被提升为Advisor链上的
ToolCallingAdvisor(递归Advisor),特性包括:DefaultChatClient自动注册,Advisor链中只允许存在一个ToolAdvisor。- 默认
order为HIGHEST_PRECEDENCE + 300。 - 内置
ToolExecutionEligibilityChecker,默认判断条件是response.hasToolCalls(),可重写以实现厂商特定的停止逻辑。 - 支持
conversationHistoryEnabled配置对话历史管理。
记忆与工具循环的放置关系(重点):
MessageChatMemoryAdvisor默认order为HIGHEST_PRECEDENCE + 200,处于工具循环之外,意味着:
- 循环外(默认):记忆顾问在循环开始前加载一次历史,仅持久化最终的"用户-助手"消息,工具请求/响应消息不写入存储,与1.x行为一致。
- 循环内(order > 300):记忆顾问在每轮迭代都被调用,完整记录工具请求/响应的全过程,模型可在后续对话中推理"之前试过什么、调用了哪些工具、返回了什么"。
提示 :当记忆顾问置于循环内时,必须禁用ToolCallingAdvisor的内部对话历史以避免重复写入。若使用自动注册的ToolCallingAdvisor,DefaultChatClient会自动检测并禁用内部历史,无需额外配置。
java
// 手动构建:记忆置于循环内 + 禁用ToolCallingAdvisor内部历史
var toolCallingAdvisor = ToolCallingAdvisor.builder()
.disableInternalConversationHistory()
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
.order(BaseAdvisor.HIGHEST_PRECEDENCE + 400)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(chatMemoryAdvisor, toolCallingAdvisor)
.build();
3.7 官方预置工具生态
Spring AI Alibaba社区提供40+开箱即用的工具Starter,artifactId格式为spring-ai-alibaba-starter-tool-calling-xxx,以阿里翻译为例:
java
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-tool-calling-alitranslate</artifactId>
<version>1.0.0.2</version>
</dependency>
java
spring:
ai:
alibaba:
toolcalling:
alitranslate:
enabled: true
access-key-id: ${ALITRANSLATE_ACCESS_KEY_ID}
secret-key: ${ALITRANSLATE_SECRET_KEY}
调用示例:
java
String response = chatClient.prompt("You are a translation assistant.")
.user("Please translate 'Thank you' into English and Japanese")
.toolNames("aliTranslateService")
.call()
.content();
已覆盖的典型工具包括:
- 翻译:阿里翻译
aliTranslateService、百度翻译baiduTranslate - 地图:高德天气
gaoDeGetAddressWeather、百度地图baiduMapGetAddressWeatherInformation、百度地址baiDuMapGetAddressInformation - 搜索:百度搜索
baiduSearch、Bing搜索bingSearch - 钉钉:群消息
dingTalkGroupSendMessageByCustomRobot
3.8 生产环境最佳实践
- 描述决定一切 :模型完全依赖
description判断是否调用工具,这是工具调用准确率的第一影响因素。
× 模糊描述:@Tool(description = "天气查询")
√ 清晰描述:@Tool(description = "查询指定城市的实时天气,返回温度和天气状况。当用户询问天气、气温、气候相关信息时调用")
经验法则:description写1-2句,覆盖"功能+触发场景+参数说明"。
- 参数与返回值设计 :
- 用简单类型:
String、int、double、boolean、List、Record。 - 返回值尽量扁平,模型处理扁平结构比深层嵌套更可靠。
- 可选参数用
@Nullable或@ToolParam(required = false)标注。 - 避开"不支持的类型"清单(见3.5.3节)。
- 用简单类型:
- 工具粒度控制:工具不是越多越好,数量过多会引发模型"选择困难",降低调用准确率并增加token消耗。建议单次注册工具数控制在5-15个以内,按业务场景动态注册(如客服场景仅挂订单/退款工具,运维场景仅挂监控/日志工具)。
- 异常兜底 :永远不要让工具抛出未捕获异常,需返回带
ERROR前缀的结构化信息,让模型能理解并决定下一步。
java
@Tool(description = "Query order by ID")
public String getOrder(@ToolParam(description = "Order ID") String orderId) {
try {
return orderService.findById(orderId).toString();
} catch (OrderNotFoundException e) {
return "ERROR: Order not found for id=" + orderId;
} catch (Exception e) {
return "ERROR: " + e.getMessage();
}
}
- 安全与权限 :
- 工具是"可执行入口",不要在未鉴权情况下注册删除/高危操作。
- 在工具内部从
ToolContext取用户身份,校验操作权限。 - 对外部API调用设置超时和熔断(如Resilience4j)。
- 返回值含敏感信息时做脱敏处理。
- 调试技巧 :开启DEBUG日志可观察完整的
tool_callsJSON交互过程,确认模型是否发起调用、参数是否符合Schema、工具是否被执行。
java
logging:
level:
org.springframework.ai: DEBUG
com.alibaba.cloud.ai: DEBUG
3.9 常见问题速查
| 现象 | 根因 | 解法 |
|---|---|---|
| 工具从未被调用 | description太模糊 |
优化描述,降低temperature |
| 参数类型不匹配报错 | JSON Schema与实际参数不一致 | 检查@ToolParam注解 |
| 工具被重复调用 | returnDirect=false且模型对结果不满意 |
设returnDirect=true |
| 返回结果乱码 | 字符编码问题 | 确保请求/响应均为UTF-8 |
| 启动报错No qualifying bean of type 'ChatModel' | API Key未配置或加载失败 | 检查配置文件/环境变量 |
| 翻译/地图返回404 | 配额耗尽或密钥错误 | 去对应开放平台检查 |
4. Chat Memory-对话记忆
4.1 核心概念
4.1.1 什么是Chat Memory
大模型本质是"金鱼脑"------LLM是无状态的,不会保留先前交互的信息。要让AI应用支持多轮对话,必须由应用侧主动管理上下文,Spring AI Alibaba的Chat Memory机制正是为此设计。
关键认知 :Chat Memory ≠ Chat History。Chat Memory是LLM在对话过程中为维持上下文感知而保留的信息;Chat History是用户与模型之间交换的所有消息的完整记录。Chat Memory抽象用于管理前者,完整的聊天历史应通过Spring Data等方案单独存储。
4.1.2 三层架构
Spring AI Alibaba的记忆模块遵循清晰的抽象原则:
- ChatMemory接口 :定义
add、get、clear等标准操作,决定保留哪些消息以及何时删除。 - MessageChatMemoryAdvisor:以Advisor形式介入ChatClient请求链路------请求发送前自动从Memory读取历史注入Prompt,响应返回后自动将用户提问和模型回复写入Memory。
- ChatMemoryRepository:存储层抽象,唯一职责是储存和检索消息,支持InMemory、Redis、JDBC等多种实现。
4.2 默认行为
Spring AI会自动配置一个ChatMemory Bean,开发者可直接注入使用:
- 默认存储 :
InMemoryChatMemoryRepository(基于ConcurrentHashMap在内存中存储)。 - 默认策略 :
MessageWindowChatMemory------按消息条数维护滑动窗口。 - 存储切换:若已配置不同的存储库(如Cassandra、JDBC、Neo4j),Spring AI将自动改用该存储库。
4.3 MessageWindowChatMemory的窗口裁剪规则
这是对话记忆最核心的机制,理解它才能避免"莫名其妙失忆":
- SystemMessage永驻:新增SystemMessage时,旧的SystemMessage会被移除(保证系统指令唯一);淘汰阶段SystemMessage始终保留,优先逐出其他消息。
- 轮次边界对齐 :逐出以完整"轮次(turn)"为单位------从UserMessage开始,包含后续Assistant回复、工具调用、工具响应,直到下一个UserMessage。因此
maxMessages是上限值,实际保留数可能略低。 - 极端情况 :若单个轮次消息数就超过
maxMessages,所有非系统消息都会被清空,直到新的UserMessage加入。
提示:此处的"窗口"是消息条数窗口,不是模型的token上下文窗口,二者是不同层的概念------前者管"保留几条",后者管"模型能消化多少token"。
4.4 快速接入
4.4.1 基础用法(内存存储)
java
// 初始化基于内存的对话记忆
ChatMemory chatMemory = new InMemoryChatMemory();
DashScopeChatModel chatModel = ...;
ChatClient chatClient = ChatClient.builder(dashscopeChatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
// 对话记忆的唯一标识
String conversationId = UUID.randomUUID().toString();
ChatResponse response = chatClient.prompt()
.user("我想去新疆")
.advisors(spec -> spec
.param(CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call()
.chatResponse();
两个关键参数:
CHAT_MEMORY_CONVERSATION_ID_KEY:会话唯一标识,不同用户的对话靠它隔离。CHAT_MEMORY_RETRIEVE_SIZE_KEY:本次调用从记忆中召回多少条消息。
4.4.2 自定义窗口大小
java
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(new InMemoryChatMemoryRepository())
.maxMessages(20) // 关键参数:保留最近20条消息
.build();
4.5 生产级存储选型
默认的InMemoryChatMemoryRepository存在三个致命缺陷:服务重启即丢失、内存无限堆积、多实例不同步,生产环境必须更换存储层。
4.5.1 JDBC存储(MySQL示例)
Spring AI Alibaba提供MysqlChatMemoryRepository,框架初始化时自动检查表是否存在,不存在则自动建表,无需手动编写DDL。
java
// 实例化ChatMemoryRepository和ChatMemory
ChatMemoryRepository chatMemoryRepository = MysqlChatMemoryRepository.mysqlBuilder()
.jdbcTemplate(jdbcTemplate)
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.build();
// 构建ChatClient时注册Advisor
this.dashScopeChatClient = chatClientBuilder
.defaultSystem(DEFAULT_PROMPT)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
JdbcChatMemoryRepository开箱即用支持PostgreSQL、MySQL/MariaDB、SQL Server、HSQLDB、Oracle等多种数据库,可通过JDBC URL自动探测方言。
4.5.2 其他存储后端
| 存储类型 | 适用场景 | 依赖配置 |
|---|---|---|
| JDBC(关系型) | 企业级应用、需持久化 | spring-ai-starter-model-chat-memory-repository-jdbc |
| Cassandra | 高并发、分布式、需TTL | spring-ai-starter-model-chat-memory-repository-cassandra |
| Neo4j | 复杂关系型对话场景 | spring-ai-starter-model-chat-memory-repository-neo4j |
| InMemory | 开发测试、轻量级应用 | 默认启用 |
4.6 生产环境最佳实践
- ChatMemory必须是单例Bean :最高频踩坑点 :若将
new InMemoryChatMemory()直接写在.defaultAdvisors()里,每次请求都会创建新的Memory实例,对话根本无法留存。正确写法是将ChatMemory定义为类成员变量或Spring Bean:
java
private final ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(50)
.build();
- conversationId必须隔离 :需从JWT/Session/业务订单号提取唯一标识作为
conversationId,遗漏或共用同一ID会导致用户间"串话",属于P0级安全事故。 - maxMessages按需调优 :
- 简单问答:10-20条
- 客服场景:20-30条
- 复杂编程助手:30-50条
不设上限虽不会内存溢出(落盘存储场景),但单会话消息无限增长会撑爆token配额,增加模型"迷失"风险。
- 工具调用中间消息不入库:当前实现限制:执行工具调用时与LLM交换的中间message不会存储在记忆中,该问题将在未来版本解决。如需存储这些message,可参考用户控制的工具执行方案。
- Agent场景的短期记忆 :构建Agent(如
ReactAgent)时,短期记忆作为Agent状态的一部分,通过checkpointer管理:
java
// 开发环境
ReactAgent agent = ReactAgent.builder()
.model(chatModel)
.saver(new MemorySaver())
.build();
// 生产环境用Redis
RedisSaver redisSaver = new RedisSaver(redissonClient);
ReactAgent agent = ReactAgent.builder()
.model(chatModel)
.saver(redisSaver)
.build();
通过RunnableConfig.builder().threadId("1")指定会话ID维护对话上下文。
4.7 总结
Spring AI Alibaba的对话记忆本质是四层协作:
- Advisor负责每次prompt前后自动读写记忆。
- ChatMemory决定保留哪些消息(滑动窗口策略,按消息条数裁剪)。
- ChatMemoryRepository决定消息存储位置(内存/JDBC/Cassandra/Neo4j)。
落地姿势 :开发阶段用默认InMemory跑通流程,生产环境将Repository换成JDBC或Redis,按业务场景调优maxMessages并做好conversationId隔离。
短时记忆vs长时记忆:本文所述的Chat Memory是短时记忆,受限于模型的Context Window,随会话结束而消失;跨会话的知识检索需要使用Vector Store实现RAG,属于长时记忆范畴,二者互补构成完整的AI应用记忆体系。
5. Prompt-提示词
Prompt是引导AI模型生成特定输出的输入格式,其设计与措辞直接影响模型的响应质量。Spring AI Alibaba在Spring AI的Prompt抽象之上,提供了从底层PromptTemplate到高层ChatClient Fluent API的完整工具链。
5.1 核心概念
5.1.1 Prompt的本质
Prompt从最初的简单字符串,逐步演进为包含特定占位符(如USER:、SYSTEM:)的结构化输入,最终发展为多角色消息结构------每条消息被分配一个明确角色,AI模型据此理解上下文和目的。
类比理解:
Prompt ≈ View(Spring MVC):包含占位符的模板,运行时被动态内容替换。ChatModel ≈ JDBC核心库:底层调用接口。ChatClient ≈ JdbcClient:构建在ChatModel之上的高层Fluent API,通过Advisor串联记忆、RAG、Agent等能力。
5.1.2 四大消息角色(MessageType)
Spring AI用枚举MessageType定义四种核心角色,消息顺序会影响模型响应逻辑,建议按SYSTEM → USER → ASSISTANT → TOOL排列:
| 角色 | 说明 | 典型用途 |
|---|---|---|
| SYSTEM | 定义AI的行为准则、响应风格、知识边界 | 设定身份(如"你是Java架构师")、输出格式约束 |
| USER | 用户的输入------问题、指令或陈述 | 用户的实际查询 |
| ASSISTANT | AI的历史响应,维持对话连贯性 | 多轮对话上下文;可包含工具调用请求 |
| TOOL | 工具/函数的执行结果,回喂给模型 | 外部API调用后的返回值 |
提示:SYSTEM消息用户不可见,但深刻影响AI的所有响应,是整个对话的"操作手册"。
5.2 核心API结构
5.2.1 三层对象关系
java
Prompt(容器)
├── List<Message>(有序消息列表)
│ ├── SystemMessage → SYSTEM
│ ├── UserMessage → USER(唯一支持多模态媒体)
│ ├── AssistantMessage → ASSISTANT
│ └── ToolResponseMessage → TOOL
└── ChatOptions(模型配置:temperature、maxTokens、model名称等)
关键源码结构:
java
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages; // 有序,顺序影响AI响应
private ChatOptions chatOptions; // 可选,覆盖模型默认配置
}
5.2.2 PromptTemplate三大接口
PromptTemplate类内部使用StringTemplate 4.x引擎渲染模板,提供三类核心接口:
| 接口 | 核心方法 | 用途 |
|---|---|---|
| PromptTemplateStringActions | render()/render(Map model) |
渲染为字符串 |
| PromptTemplateMessageActions | createMessage()/createMessage(Map model) |
生成Message对象 |
| PromptTemplateActions | create()/create(Map model, ChatOptions opts) |
生成完整Prompt对象 |
5.3 三种使用方式
5.3.1 方式一:PromptTemplate(底层模板)
适合需要精细控制Prompt构建的场景:
java
// 1. 创建带占位符的模板
PromptTemplate promptTemplate = new PromptTemplate("Tell me a {adjective} joke about {topic}");
// 2. 运行时填充变量
Prompt prompt = promptTemplate.create(Map.of("adjective", "witty", "topic", "programming"));
// 3. 调用模型
return chatModel.call(prompt).getResult();
5.3.2 方式二:SystemPromptTemplate(系统消息专用)
专门用于构建SYSTEM角色消息,常与UserMessage组合:
java
// 用户消息
String userText = """
Tell me about three famous pirates from the Golden Age of Piracy.
Write at least a sentence for each pirate.
""";
Message userMessage = new UserMessage(userText);
// 系统消息模板
String systemText = """
You are a helpful AI assistant that helps people find information.
Your name is {name}
You should reply to the user's request with your name and also
in the style of a {voice}.
""";
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);
Message systemMessage = systemPromptTemplate.createMessage(
Map.of("name", "Alexa", "voice", "pirate")
);
// 组合为完整Prompt
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
List<Generation> response = chatModel.call(prompt).getResults();
5.3.3 方式三:ChatClient Fluent API(推荐)
日常开发首选,链式调用简洁直观,内部自动使用PromptTemplate + StTemplateRenderer完成变量替换,无需手动创建模板对象:
java
String answer = chatClient.prompt()
.user(u -> u.text("Tell me the names of 5 movies whose soundtrack was composed by {composer}")
.param("composer", "John Williams"))
.call()
.content();
5.4 StringTemplate占位符语法
Spring AI默认使用StringTemplate 4.x引擎,支持多种占位符写法:
| 语法 | 示例 | 说明 |
|---|---|---|
| 简单占位符 | {name} |
运行时必须提供值,否则抛异常 |
| 带默认值 | {name:未知用户} |
未提供时使用默认值 |
| 条件选择 | `{gender:男 | 女}` |
| 匿名占位符 | {...} |
保留未使用的参数 |
自定义分隔符(避免与JSON冲突) :当Prompt中包含JSON内容时,{}会与JSON语法冲突,可切换分隔符:
java
String answer = ChatClient.create(chatModel).prompt()
.user(u -> u.text("""
Tell me the names of 5 movies whose soundtrack was composed by <composer>
""")
.param("composer", "John Williams"))
.templateRenderer(StTemplateRenderer.builder()
.startDelimiterToken('<')
.endDelimiterToken('>')
.build())
.call()
.content();
5.5 外部化模板(生产推荐)
将Prompt模板放到外部文件,让非开发人员(PM、领域专家)也能直接编辑,无需修改代码,是生产项目的成熟做法:
java
src/main/resources/prompts/
├── translator.st # 翻译器
├── summarizer.st # 摘要生成器
├── code-reviewer.st # 代码审查
└── customer-service.st # 客服回复
模板文件示例(translator.st):
java
你是一个专业翻译助手。
请将以下内容翻译为{target_language},保持原文语气和风格。
原文:
{source_text}
Java代码注入:
java
@Value("classpath:/prompts/translator.st")
private Resource translatorTemplate;
public String translate(String text, String targetLang) {
SystemPromptTemplate template = new SystemPromptTemplate(translatorTemplate);
Message message = template.createMessage(Map.of(
"target_language", targetLang,
"source_text", text
));
return chatModel.call(new Prompt(List.of(message)))
.getResult().getOutput().getContent();
}
提示 :结合Spring的@Configuration+@Bean管理模板,可享受条件装配、AOP拦截等全套Spring能力,实现"内联→模板→外部资源→Bean统一管理"的成熟路线。
5.6 ChatClient中的Prompt配置
5.6.1 默认系统提示词
在Builder阶段预设,所有请求自动带上:
java
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个严谨的Java编程助手,回答简洁明了")
.defaultUser("解释建造者模式的使用场景") // 可选:默认用户消息
.defaultOptions(ChatOptions.builder()
.temperature(0.3)
.maxTokens(500)
.build())
.build();
5.6.2 运行时覆盖
单次调用中动态指定系统提示和用户提示:
java
String result = chatClient.prompt()
.system(s -> s.text("你是一个{role},请用{style}风格回答")
.param("role", "客服专员")
.param("style", "热情恭敬"))
.user("我的订单怎么还没到?")
.options(ChatOptions.builder().temperature(0.7).build())
.call()
.content();
5.6.3 多模态(媒体内容)
UserMessage是唯一支持多模态的消息类型,可携带图片、音频等媒体:
java
Message userMessage = new UserMessage(
"请描述这张图片的内容",
List.of(new Media(MimeTypeUtils.IMAGE_JPEG, imageResource))
);
5.7 生产环境最佳实践
5.7.1 提示词工程原则
- 具体化:模糊的Prompt产出模糊的结果。例如将"解释Spring"优化为"用200字以内解释Spring IoC容器的核心原理,面向有Java基础的开发者"。
- 给示例(Few-Shot):在SYSTEM消息中提供1-3个输入-输出样例,可显著提升输出质量。
- 明确输出格式:要求JSON时明确指定字段名和类型,配合结构化输出使用效果更佳。
- 设边界:在SYSTEM中声明"不做的事",如"不提供医疗诊断建议",减少模型幻觉。
5.7.2 模板管理策略
| 阶段 | 做法 | 适用场景 |
|---|---|---|
| 内联字符串 | 直接在代码里写Prompt | 快速原型 |
| PromptTemplate | 提取占位符,Java中填充 | 中小项目 |
| 外部.st文件 | Resource加载+Bean管理 | 生产项目,多人协作 |
5.7.3 常见陷阱
- 占位符未闭合:
{name缺少}会导致渲染异常。 - JSON与占位符冲突:模板中含JSON时务必切换分隔符(如
<>)。 - 变量未提供:
{name}在Map中无对应key会抛IllegalArgumentException。 - SYSTEM消息过长:过于冗长的系统提示会挤占上下文窗口,建议控制在模型上下文的10%-20%以内。
- 角色顺序混乱:消息顺序影响模型理解,严格按
SYSTEM → USER → ASSISTANT → TOOL排列。
5.8 总结
Prompt是AI应用的"灵魂接口",Spring AI Alibaba提供了从底层PromptTemplate+StringTemplate引擎到高层ChatClient Fluent API的完整工具链。开发阶段用Fluent API快速迭代,生产阶段将模板外部化为.st文件交由非开发人员维护,并在SYSTEM消息中写好"你是谁、做什么、不做什么",即可保障Prompt质量稳定。
进阶路线:内联字符串→PromptTemplate占位符→外部.st文件+Resource注入→SYSTEM/USER角色分离+Few-Shot示例→配合Structured Output做格式化输出→结合RAG注入检索增强上下文。
6. Document Retriever-文档检索
6.1 核心定位
文档检索是检索增强生成(RAG)流水线的"检"环节------将用户问题送到知识库里找出最相关的文档片段,再交给大模型生成回答。Spring AI Alibaba提供的DashScopeDocumentRetriever专门用于对接阿里云百炼(Model Studio)知识库,文档解析、切片、向量化与索引优化全部由阿里云托管。
其在Spring AI RAG架构中的职责是:"将用户问题转为向量→在知识库中执行相似度搜索→返回匹配度最高的文档"。
DashScopeDocumentRetriever的核心特点:
- 百炼全托管:免去自建向量数据库、调优索引的运维负担,按调用量付费,具备企业级安全能力。
- 检索结果直供大模型 :检索到最相关文本切片后,将上下文与原始问题一并提交给大模型(默认
qwen-max)生成回答。
6.2 依赖与配置
6.2.1 引入依赖
java
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${version}</version>
</dependency>
Spring Boot自动配置会为DashScopeDocumentRetriever生效。
6.2.2 配置API Key
官方支持两种方式,二选一:
java
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
# workspace-id: ${AI_DASHSCOPE_WORKSPACE_ID} # 可选,检索默认业务空间的知识库时无需配置
或设置环境变量:export SPRING_AI_DASHSCOPE_API_KEY=<YOUR_API_KEY>
提示 :所有以spring.ai.dashscope开头的属性,都可以在构造DashScopeDocumentRetriever时通过Runtime Options传入覆盖。
6.3 创建检索器
6.3.1 核心代码
java
// 1. 创建DashScope API客户端
var dashScopeApi = new DashScopeApi(System.getenv("DASHSCOPE_API_KEY"));
// 2. 配置检索选项,指定要检索的百炼知识库
DocumentRetriever retriever = new DashScopeDocumentRetriever(
dashScopeApi,
DashScopeDocumentRetrieverOptions.builder()
.withIndexName("spring-ai知识库") // 百炼控制台中创建的知识库名称
.build()
);
withIndexName是关键参数,其值必须是百炼平台提前创建好的知识库名称,作为唯一索引用于后续RAG知识检索。
6.3.2 可选的高级参数
DashScopeDocumentRetrieverOptions支持一系列检索增强参数,是提升RAG精度的核心配置:
| 参数 | 说明 |
|---|---|
withDenseSimilarityTopK(int) |
密集相似度(向量)检索返回前K个结果 |
withSparseSimilarityTopK(int) |
稀疏相似度(关键词)检索返回前K个结果 |
withEnableRewrite(boolean) |
启用查询重写,优化检索效果 |
withRewriteModelName(String) |
重写模型名称,如conv-rewrite-qwen-1.8b |
withEnableReranking(boolean) |
启用结果重排序 |
withRerankModelName(String) |
重排序模型名称,如gte-rerank-hybrid |
withRerankMinScore(float) |
重排序最小分数阈值 |
withRerankTopN(int) |
重排序后返回的前N个最佳结果 |
提示:查询重写+重排序是提升RAG检索精度的"黄金组合"------前者优化用户query,后者对候选结果做精排。
6.4 接入ChatClient(RAG完整链路)
Spring AI Alibaba推荐通过DocumentRetrievalAdvisor把检索器挂载到ChatClient上,实现"检索→注入→调用模型→返回"的自动化:
java
@Service
public class CloudRagService {
private static final String INDEX_NAME = "TestKnowledgeBase";
// 检索增强的系统提示词模板
private static final String retrievalSystemTemplate = """
Here is some context information.
---------------------
{question_answer_context}
---------------------
Use only the context provided, not your prior knowledge, to answer
the user's question. If the context does not contain the answer,
state that you cannot answer the question.
""";
private final ChatClient chatClient;
public CloudRagService(ChatClient.Builder builder, DashScopeApi dashscopeApi) {
// 创建百炼知识库检索器
DocumentRetriever retriever = new DashScopeDocumentRetriever(
dashscopeApi,
DashScopeDocumentRetrieverOptions.builder()
.withIndexName(INDEX_NAME)
.build()
);
// 构建RAG增强的ChatClient
this.chatClient = builder
.defaultAdvisors(new DocumentRetrievalAdvisor(retriever, retrievalSystemTemplate))
// 默认模型为qwen-max,可切换为qwen-plus等
// .defaultOptions(DashScopeChatOptions.builder().withModel("qwen-plus").build())
.build();
}
public Flux<ChatResponse> retrieve(String message) {
return chatClient.prompt()
.user(message)
.stream()
.chatResponse();
}
}
完整RAG链路:用户提问→DocumentRetrievalAdvisor拦截→DashScopeDocumentRetriever检索百炼知识库最相关片段→片段注入到Prompt的{question_answer_context}占位符→提交大模型生成回答。
6.5 百炼知识库的两种创建方式
DashScopeDocumentRetriever检索的知识库必须提前在百炼平台创建好,有两种方式:
方式一:百炼控制台可视化创建(推荐)
- 登录百炼控制台,左侧"数据管理"菜单→"导入数据"上传私域文档。
- 左侧"知识索引"菜单→"创建知识库"完成文档向量化。
- 记住填写的知识库名称,作为
withIndexName()的唯一索引。
方式二:API代码创建
通过DashScopeDocumentCloudReader(实现DocumentReader)和DashScopeCloudStore(实现VectorStore)将本地数据上传到百炼:
java
public void importDocuments() {
String path = "absolute-path-to-your-file";
// 1. 读取并切片文档
DocumentReader reader = new DashScopeDocumentCloudReader(path, dashscopeApi, null);
List<Document> documentList = reader.get();
// 2. 添加到百炼云向量库(indexName即为知识库名称)
VectorStore vectorStore = new DashScopeCloudStore(
dashscopeApi,
new DashScopeStoreOptions(indexName)
);
vectorStore.add(documentList);
}
6.6 与VectorStore RAG的区别
Spring AI生态中存在两套RAG检索方案,切勿混淆:
| 方案 | 检索器 | 知识库位置 | 适用场景 |
|---|---|---|---|
| 百炼RAG(本文) | DashScopeDocumentRetriever |
阿里云百炼平台托管 | 不想自运维向量库,追求开箱即用 |
| VectorStore RAG | QuestionAnswerAdvisor + 本地VectorStore |
自建向量数据库 | 需要完全掌控数据,或已有向量库 |
VectorStore RAG的典型写法:
java
ChatClient chatClient = ChatClient.builder(chatModel)
.build();
ChatResponse response = chatClient.prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore)) // 本地向量库检索
.user(userText)
.call()
.chatResponse();
两者可以并存------同一个ChatClient既可以挂DocumentRetrievalAdvisor检索百炼知识库,也可以挂QuestionAnswerAdvisor检索本地向量库。
6.7 生产环境最佳实践
- 知识库名称集中管理 :
INDEX_NAME作为检索的唯一索引,建议通过配置中心或环境变量注入,避免硬编码:
java
@Value("${bailian.knowledge.index-name}")
private String indexName;
- 启用查询重写与重排序:生产环境强烈建议开启这两个特性,可显著提升检索精度:
java
DashScopeDocumentRetrieverOptions.builder()
.withIndexName(indexName)
.withEnableRewrite(true)
.withRewriteModelName("conv-rewrite-qwen-1.8b")
.withEnableReranking(true)
.withRerankModelName("gte-rerank-hybrid")
.withRerankMinScore(0.01f)
.withRerankTopN(5)
.build()
- 系统提示词约束模型行为 :在
DocumentRetrievalAdvisor的模板中明确要求"仅基于上下文回答,无相关信息则如实说明",避免模型幻觉:
java
请基于提供的企业知识库内容回答问题,无相关信息则明确说明无法回答
- 模型选型 :默认
qwen-max效果最强但成本较高;如果对延迟和成本敏感,可切换到qwen-plus。 - 多轮对话配合记忆 :RAG服务可与
MessageChatMemoryAdvisor配合使用,实现"检索增强+多轮对话记忆"的双重能力。 - 权限与隔离 :
- API Key通过环境变量注入,禁止硬编码。
- 多租户场景下,不同租户使用不同的
INDEX_NAME实现知识隔离。 - 通过
workspace-id支持多业务空间。
6.8 总结
DashScopeDocumentRetriever是百炼知识库的"检索代理"------文档解析、切片、向量化、索引优化全部由阿里云托管,应用侧只需指定withIndexName()指向已创建的知识库,再通过DocumentRetrievalAdvisor挂载到ChatClient,即可实现"用户提问→百炼检索→上下文注入→大模型回答"的完整RAG链路。
选型建议:
- 追求开箱即用、不想运维向量库→百炼RAG(
DashScopeDocumentRetriever)。 - 数据敏感需私有部署、已有向量库→VectorStore RAG(
QuestionAnswerAdvisor)。 - 复杂场景→两者共存,按数据性质路由到不同检索器。
进阶路线 :百炼控制台创建知识库→DashScopeDocumentRetriever检索→DocumentRetrievalAdvisor接入ChatClient→启用重写+重排序提升精度→配合ChatMemory实现多轮RAG对话→百炼RAG+VectorStore RAG混合检索。
7. Structured Output-格式化输出
7.1 核心价值
大模型本质是text-in/text-out的系统,一旦下游代码需要根据字段路由、持久化值或基于结果分支,就必须将非结构化的文本转换为类型化对象。结构化输出正是填补这道鸿沟的桥梁------引导模型产出符合Schema的文本内容,再由应用解析回Java对象,让其余代码像处理普通领域对象一样使用它。
Spring AI Alibaba的结构化输出能力构建在Spring AI的抽象之上,提供两层API:
- 高层Fluent API :
ChatClient的.entity(...)方法------声明目标类型,框架自动生成JSON Schema、注入提示词、反序列化结果。 - 底层转换器API :
StructuredOutputConverter及其内置实现,提供更精细的控制。
本质:框架自动在Prompt中加入数据格式指令,辅助模型理解要求的结果数据格式,同时在拿到模型数据后完成到JavaBean的转换。
7.2 四种内置转换器
Spring AI当前提供的Converter实现:
| 转换器 | 输入导向 | 输出类型 | 典型场景 |
|---|---|---|---|
BeanOutputConverter |
指示模型生成符合DRAFT_2020_12的JSON,Schema从指定Java类派生 | 任意POJO/Record | 最常用,类型安全 |
MapOutputConverter |
指示模型生成符合RFC8259的JSON | java.util.Map |
动态键值对 |
ListOutputConverter |
指示模型生成逗号分隔的格式化输出 | java.util.List |
简单列表 |
AbstractMessageOutputConverter/AbstractConversionServiceOutputConverter |
抽象基类 | 自定义 | 扩展自定义转换器 |
其中BeanOutputConverter是生产首选------它从定义的Java类自动派生JSON Schema,再用ObjectMapper把模型输出的JSON反序列化为目标对象。
7.3 ChatClient高层用法(推荐)
7.3.1 基础POJO/Record映射
定义接收实体(Java Record简洁高效,适合数据载体):
java
record ActorsFilms(String actor, List<String> movies) {}
调用时将.content()换成.entity(目标类型.class):
java
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
.user(u -> u.text("Generate the filmography of 5 movies for {actor}.")
.param("actor", "Tom Hanks"))
.call()
.entity(ActorsFilms.class);
返回的actorsFilms是类型化对象,可直接调用actorsFilms.actor()和actorsFilms.movies()。
7.3.2 泛型类型(List、Map)
.entity(Class)仅适用于具体类,泛型类型必须用ParameterizedTypeReference:
java
List<ActorsFilms> films = chatClient.prompt()
.user("Generate filmographies for three random actors.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {});
Map同理:
java
Map<String, Object> result = ChatClient.create(chatModel).prompt()
.user(u -> u.text("为我提供 {subject}")
.param("subject", "一个从1到9的数字数组,键名为'numbers'"))
.call()
.entity(new ParameterizedTypeReference<Map<String, Object>>() {});
7.3.3 底层ChatModel用法
需要精细控制时,可直接使用BeanOutputConverter:
java
BeanOutputConverter<List<ActorsFilms>> outputConverter =
new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});
String format = outputConverter.getFormat(); // 框架自动生成的格式指令
String template = "为Tom Hanks和Bill Murray生成5部电影的作品。\n{format}";
Prompt prompt = new PromptTemplate(template, Map.of("format", format)).create();
Generation generation = chatModel.call(prompt).getResult();
List<ActorsFilms> actorsFilms = outputConverter.convert(generation.getOutput().getText());
7.4 可靠性开关(Spring AI 2.0+)
默认的.entity(...)没有硬性保证------框架只是"要求"模型按Schema生成JSON,而非强制。大多数情况下模型会遵守,但偶尔会返回多余字段、遗漏必填项,或用自然语言包裹JSON,导致解析器抛出异常。
Spring AI 2.0提供了两个独立且可组合的开关:
7.4.1 validateSchema():自纠错重试
开启后,Spring AI会用实体的Schema校验响应,校验失败时把具体错误追加到Prompt并重发调用,默认最多重试3次:
java
ActorsFilms films = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorsFilms.class, spec -> spec.validateSchema());
7.4.2 useProviderStructuredOutput():厂商原生结构化输出
把Schema作为API级别的约束发送给模型厂商,由厂商运行时强制保证合规性,而非依赖提示词指令:
java
ActorsFilms films = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorsFilms.class, spec -> spec.useProviderStructuredOutput());
风险提示:这需要底层模型支持原生结构化输出能力,部分模型(如早期OpenAI版本)不原生支持对象数组。
7.5 流式输出的限制
.entity(...)仅适用于.call()路径。类型化解析需要完整响应,而.stream()返回的是文本块(text chunks)而非类型化对象。因此所有.entity(...)变体------无论是Class、ParameterizedTypeReference、自定义转换器,还是带可靠性开关的版本------都不能在流式路径上使用。
如果业务既要流式又要结构化,折中方案是:流式输出原始JSON文本,客户端收集完整后再反序列化。
7.6 Agent场景的结构化输出
在Spring AI Alibaba的ReactAgent中,通过outputSchema和outputType处理结构化输出:
java
ReactAgent agent = ReactAgent.builder()
.name("agent")
.model(chatModel)
.outputSchema(schemaString) // 方式一:直接提供JSON Schema字符串
// OR
.outputType(MyClass.class) // 方式二:提供Java类,自动转换为Schema(推荐)
.build();
推荐使用outputType(Class)------既保证类型安全,又实现自动Schema生成,代码更易维护。结构化响应在Agent的AssistantMessage中作为JSON文本返回,可解析为需要的格式。
7.7 生产环境最佳实践
- **优先用Record + **
**.entity()**:Java Record不可变、简洁、自动生成equals/hashCode/toString,是承载AI返回结构的理想载体。 - 复杂场景开启可靠性开关 :生产环境强烈建议至少开启
validateSchema(),低成本显著提升成功率;若模型支持原生结构化输出,叠加useProviderStructuredOutput()更佳。 - Schema设计原则 :
- 字段命名清晰、类型明确。
- 对必填字段使用
jakarta.validation.constraints.NotNull等注解(Spring AI会纳入Schema生成)。 - 枚举类型优于裸字符串。
- 避免过深的嵌套结构。
- 异常处理 :即使开启了
validateSchema(),也要在业务代码中对解析失败做兜底:
java
try {
ActorsFilms films = chatClient.prompt()
.user(...)
.call()
.entity(ActorsFilms.class, spec -> spec.validateSchema());
return films;
} catch (Exception e) {
log.error("结构化输出解析失败", e);
// 降级:返回默认值/走非结构化文本解析/抛业务异常
}
- 原生结构化输出参数:可通过Advisor参数全局或按调用开启原生结构化输出:
java
ActorFilms actorFilms = chatClient.prompt()
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
7.8 总结
结构化输出=JSON Schema自动生成+提示词注入+类型化反序列化。开发时用ChatClient的.entity(Record.class)一行搞定POJO映射;泛型用ParameterizedTypeReference;生产环境加上validateSchema()做自纠错,模型支持的话再叠加useProviderStructuredOutput()下沉到厂商级约束。
关键边界:
.entity(...)仅适用于.call(),流式路径不可用。- 默认行为只是"要求"模型遵守Schema,并非强制------可靠性开关是生产必备。
- Agent场景用
outputType(Class)获得类型安全的自动Schema生成。
进阶路线 :.entity(POJO.class)快速映射→ParameterizedTypeReference处理泛型→validateSchema()自纠错重试→useProviderStructuredOutput()厂商级约束→ReactAgent.outputType()做Agent结构化输出→自定义StructuredOutputConverter处理非JSON格式。
8. Vector Store-向量存储
8.1 核心定位
向量存储是检索增强生成(RAG)的"记忆体"------将文本转为高维向量,通过相似度搜索(而非精确匹配)找出与用户问题语义最接近的内容,再交由大模型生成回答。Spring AI Alibaba在Spring AI的VectorStore抽象之上,既支持对接阿里云百炼全托管知识库(DashScopeCloudStore),也兼容Spring AI生态的Redis、Cassandra、Milvus、Elasticsearch、Pinecone等主流向量数据库。
向量数据库与传统关系型数据库的根本区别在于:它们执行的是相似性搜索,而非精确匹配。当给定一个查询向量时,VectorStore会返回与该查询向量"相似"的向量。
Spring AI通过VectorStore接口提供了与向量数据库交互的抽象API,所有向量数据库实现都必须支持以下核心操作:
add(List<Document>):批量添加文档(内部自动调用EmbeddingModel把文本转为向量并存储)。similaritySearch(SearchRequest):高级相似度搜索,支持topK、相似度阈值、元数据过滤。similaritySearch(String):简单相似度搜索。delete(List<String>):根据文档ID批量删除。delete(Filter.Expression):根据元数据过滤条件删除。
Document是Spring AI中表示文本数据的核心类,包含id(唯一标识)、content(文本内容)、metadata(键值对元数据)、embedding(向量表示,通常由EmbeddingModel自动生成)四个主要属性。
8.2 两种使用模式
8.2.1 模式一:百炼全托管(DashScopeCloudStore)
文档解析、切片、向量化、索引优化全部由阿里云百炼平台完成,应用侧只需调用API。
依赖与配置:
java
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${version}</version>
</dependency>
java
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
# workspace-id: ${AI_DASHSCOPE_WORKSPACE_ID} # 可选
创建云端向量存储:
java
var dashscopeApi = new DashScopeApi(System.getenv("DASHSCOPE_API_KEY"));
DashScopeCloudStore cloudStore = new DashScopeCloudStore(
dashscopeApi,
new DashScopeStoreOptions("spring-ai知识库") // 百炼平台创建的知识库名称
);
DashScopeStoreOptions通过其构造函数创建选项,在构造DashScopeCloudStore时传入以完成配置。所有以spring.ai.dashscope开头的属性都可以在构造DashScopeCloudStore时通过Runtime Options覆盖。
8.2.2 模式二:本地/第三方向量数据库
Spring AI Alibaba兼容Spring AI官方支持的所有向量数据库实现,包括Redis、Cassandra、Milvus、Elasticsearch、MariaDB、Pinecone等。以Redis为例:
java
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>
8.3 文档入库与检索
8.3.1 文档入库
无论哪种模式,入库流程都是统一的------vectorStore.add(documents)内部会自动调用EmbeddingModel完成向量化:
java
// 准备要存储的文档
List<Document> documents = List.of(
new Document("i study LLM"), // 自动向量化
new Document("i love java")
);
// 调用VectorStore.add(),内部自动把文本转为向量并存储
vectorStore.add(documents);
Document是Spring AI定义的文档对象,包含content(内容)、id、metadata(额外信息)。调用add()时,VectorStore内部会自动用EmbeddingModel把文本转为向量,将向量和原始文本一起存入底层向量库。
8.3.2 相似度检索
java
// 构建搜索请求
SearchRequest searchRequest = SearchRequest.builder()
.query("LLM") // 搜索词(会自动转为向量)
.topK(2) // 返回最相似的2个结果
.build();
// 执行检索
List<Document> results = vectorStore.similaritySearch(searchRequest);
SearchRequest是Spring AI的搜索请求构建器,支持query(查询文本)、topK(返回数量)等参数。
8.4 百炼知识库的两种创建方式
DashScopeCloudStore检索的知识库必须在百炼平台提前创建好,有两种方式:
方式一:百炼控制台可视化创建(推荐)
- 登录百炼控制台,左侧"数据管理"菜单→"导入数据"上传私域文档。
- 左侧"知识索引"菜单→"创建知识库"完成文档向量化。
- 记住填写的知识库名称,作为
withIndexName()的唯一索引。
方式二:API代码创建
通过DashScopeDocumentCloudReader(实现DocumentReader)和DashScopeCloudStore(实现VectorStore)将本地数据上传到百炼:
java
public void importDocuments() {
String path = "absolute-path-to-your-file";
// 1. 读取并切片文档
DocumentReader reader = new DashScopeDocumentCloudReader(path, dashscopeApi, null);
List<Document> documentList = reader.get();
// 2. 添加到百炼云向量库(indexName即为知识库名称)
VectorStore vectorStore = new DashScopeCloudStore(
dashscopeApi,
new DashScopeStoreOptions(indexName)
);
vectorStore.add(documentList);
}
提示 :代码中的indexName值将作为唯一索引用于后续RAG知识检索。
8.5 与RAG的整合
向量存储通常不单独使用,而是通过QuestionAnswerAdvisor挂载到ChatClient上,形成完整的RAG链路:
java
// 构建RAG增强的ChatClient
ChatClient chatClient = ChatClient.builder(chatModel)
.build();
ChatResponse response = chatClient.prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore)) // 本地向量库检索
.user(userText)
.call()
.chatResponse();
完整RAG链路:用户提问→QuestionAnswerAdvisor拦截→VectorStore相似度检索→相关文档片段注入Prompt→提交大模型生成回答。
如果是百炼全托管模式,则用DashScopeDocumentRetriever+DocumentRetrievalAdvisor替代本地VectorStore。
8.6 支持的向量数据库
Spring AI Alibaba不仅提供对阿里云原生向量服务的支持,还兼容Spring AI官方支持的所有向量数据库实现:
| 向量数据库 | 适用场景 | 关键配置 |
|---|---|---|
| Redis | 高性能、TTL天然适配会话过期 | spring-ai-starter-vector-store-redis |
| Cassandra | 高并发、分布式 | spring.ai.vectorstore.cassandra.* |
| Milvus | 大规模向量检索,AI原生 | spring.ai.vectorstore.milvus.* |
| Elasticsearch | 已有ES基础设施 | spring.ai.vectorstore.elasticsearch.* |
| MariaDB | 关系型+向量混合 | spring.ai.vectorstore.mariadb.* |
| Pinecone | 云端全托管向量服务 | spring.ai.vectorstore.pinecone.* |
| DashScopeCloudStore | 阿里云百炼全托管 | DashScopeStoreOptions指定知识库名 |
以MariaDB为例,可通过Spring Boot配置:
java
spring:
datasource:
url: jdbc:mariadb://localhost/db
username: myUser
password: myPassword
ai:
vectorstore:
mariadb:
initialize-schema: true
distance-type: COSINE
dimensions: 1536
8.7 生产环境最佳实践
- 维度一致性是硬约束 :同一应用的所有向量必须维度相同。一旦选定
dimensions,后续所有入库、查询、向量字段维度都要对齐,中途修改会导致相似度计算失效。维度越低,存储和计算越快,但语义表达能力会下降。 - topK与相似度阈值调优 :
topK:控制返回结果数量,一般3-10。过小漏掉相关内容,过大引入噪声。similarityThreshold:相似度阈值(0~1,值越高越相似),建议0.6-0.75起步,根据召回质量逐步调优。
java
SearchRequest request = SearchRequest.builder()
.query(query)
.topK(5)
.similarityThreshold(0.7f)
.build();
- 元数据过滤提升精度 :元数据过滤允许在相似度搜索的同时,基于文档的元数据进行精确过滤,从而提高检索的准确性和效率。Spring AI提供两种过滤方式:
- SQL-like字符串表达式:简单直观,但缺乏类型安全。
FilterExpressionBuilder:类型安全的构建器API,推荐生产环境使用。
java
// 构建过滤条件:type == "tutorial" AND version > "1.0"
FilterExpressionBuilder b = new FilterExpressionBuilder();
vectorStore.similaritySearch(SearchRequest.builder()
.query(query)
.topK(5)
.similarityThreshold(0.7f)
.withFilterExpression(b.and(
b.eq("type", "tutorial"),
b.gt("version", "1.0")
)).build());
- 入库幂等性:同一文档多次入库会产生重复向量,需要基于文档hash或业务ID做去重;文档变更时,先按元数据过滤删除旧向量,再重新入库。
- 企业级文档入库管道:生产环境推荐实现完整的文档入库管道:解析→分片→向量化→存储:
java
public IngestionResult ingest(String filePath, DocumentMeta meta) {
// 1. 根据文件类型选择解析器
DocumentParser parser = parserFactory.getParser(getFileType(filePath));
List<Document> rawDocuments = parser.parse(filePath);
// 2. 语义分片
TextSplitter splitter = createSplitter(meta.getDocType());
List<Document> chunks = splitter.split(rawDocuments);
// 3. 为每个分片注入元数据(用于后续权限过滤)
chunks.forEach(chunk -> {
chunk.getMetadata().put("doc_id", meta.getDocId());
chunk.getMetadata().put("department", meta.getDepartment());
chunk.getMetadata().put("access_level", meta.getAccessLevel());
});
// 4. 向量化并存储
vectorStore.add(chunks);
}
- 选型建议 :
- 追求开箱即用、不想运维向量库→百炼全托管
DashScopeCloudStore。 - 数据敏感需私有部署、已有向量库→本地
VectorStore(Redis/Milvus/ES等)。 - 复杂场景可百炼RAG与
VectorStoreRAG混合使用,按数据性质路由到不同检索器。
- 追求开箱即用、不想运维向量库→百炼全托管
8.8 总结
VectorStore=文本的"语义搜索引擎"------Document入库时自动向量化,similaritySearch()按相似度召回。Spring AI Alibaba既支持DashScopeCloudStore对接百炼全托管知识库,也兼容Redis、Milvus、Cassandra等主流向量数据库;通过QuestionAnswerAdvisor挂载到ChatClient即可实现完整RAG链路。
核心要点:
add()内部自动调用EmbeddingModel完成向量化。SearchRequest的topK和similarityThreshold是检索精度的两个关键旋钮。- 维度一致性是硬约束------一旦选定不可中途更改。
- 元数据过滤是生产环境权限隔离和精准检索的必备能力。
进阶路线 :DashScopeCloudStore快速接入百炼→本地VectorStore(Redis/Milvus)私有部署→SearchRequest调优topK+相似度阈值→元数据过滤实现权限隔离→QuestionAnswerAdvisor接入ChatClient完成RAG→百炼RAG+VectorStore RAG混合检索。