Spring AI Alibaba 核心知识点

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.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等
两层参数配置

参数支持全局默认与单次调用动态覆盖,后者优先级更高:

  1. 全局默认配置:在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.*在配置文件中全局指定。

  1. 单次调用动态覆盖:针对特定请求调整参数,不影响全局配置。
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,值越高输出越随机)、topPtopKmaxTokenfrequencyPenalty等。

调用方式完整图谱
  • 同步调用
    • .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();
生产环境要点
  1. 无状态局限:原生Chat Model调用无状态,多轮对话需配合Chat Memory与Advisor机制实现上下文传递。
  2. 超时与重试:需在DashScopeApi或HTTP客户端层配置合理的连接超时、读取超时,搭配失败重试策略,应对SaaS服务的网络抖动。
  3. 参数调优经验值
    • 客服/问答场景: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阶段预设defaultSystemdefaultOptionsdefaultAdvisors

核心调用链

最经典的同步字符串返回写法:

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。

生产环境要点
  1. 优先使用ChatClient:90%场景使用ChatClient即可,仅在需要极细粒度控制模型通信时回落至ChatModel。
  2. 多模型场景适配 :不同任务绑定不同模型(复杂推理用qwen-max,简单问答用qwen-turbo),通过@Qualifier注入多个ChatClient Bean实现。
  3. 预设默认配置 :在Builder阶段通过defaultSystem()设置全局系统提示词,通过defaultOptions()预设temperaturemaxTokens等参数,避免每次调用重复配置。
java 复制代码
ChatClient.builder(chatModel)
    .defaultSystem("你是客服助手,回答限制在2-4句")
    .defaultOptions(DashScopeChatOptions.builder()
        .withTemperature(0.2)
        .withMaxToken(400)
        .build())
    .build();
  1. 结构化输出优先用 **.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/v4qwen2.5-vl-embeddingtongyi-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流水线的两个核心向量化环节:

  1. 文档入库阶段VectorStore.add(documents)内部自动调用EmbeddingModel,将文档切片转为向量后存入向量库。
  2. 用户查询阶段:将用户问题向量化,在VectorStore中执行余弦相似度/欧氏距离检索,召回最相关的文档片段。
java 复制代码
// 入库:VectorStore内部自动调用EmbeddingModel
vectorStore.add(List.of(
    new Document("i study LLM"),
    new Document("i love java")
));

// 查询:用户问题经EmbeddingModel向量化后检索
List<Document> results = vectorStore.similaritySearch("学习大语言模型");
生产环境要点
  1. 模型选型建议 :中文场景首选text-embedding-v3,支持1024维(可调至64~1024)、50+语种、最大8192 token,中文效果最优;旧版text-embedding-v1仅1536维且仅支持中英文。维度越低,存储和计算成本越低,但语义表达能力会下降。
  2. text-type必须设置 :入库文档设为document,用户查询设为query,可显著提升嵌入质量。
  3. 维度一致性是硬约束 :同一应用的所有向量必须维度相同,一旦选定dimensions,后续所有入库、查询、向量字段维度都要对齐,中途修改会导致相似度计算失效。
  4. 批量处理更高效 :单次EmbeddingRequest支持传入文本列表批量向量化,建议批量大小10~100条,减少API调用次数。
  5. 入库幂等性:同一文档多次入库会产生重复向量,需基于文档hash或业务ID做去重;文档变更时,先按元数据过滤删除旧向量,再重新入库。
  6. 成本与限流 :Embedding API按token消耗计费且有速率限制,建议对高频访问的嵌入结果做缓存,并通过RetryTemplate实现指数退避重试。

3. Function Calling-工具

3.1 为什么需要工具调用

大模型存在两个核心局限:无实时信息(无法获知当前时间、天气等动态数据)、无法执行真实动作(无法下单、发邮件、操作数据库)。工具调用(Tool Calling)正是为解决这两个问题而生,其本质是:模型仅负责决策"是否调用工具、调用哪个工具、传递什么参数",真正执行工具的是Java应用,执行结果再回喂给模型生成最终回答。模型永远不会直接接触注册的API,这是核心安全边界。

工具调用主要服务两类场景:

  • 信息检索类:查天气、查数据库、搜网页、读文件,扩展模型知识边界,是RAG的重要延伸。
  • 执行动作类:发邮件、创单、触发工作流,将模型的"计划"转化为真实业务操作。

3.2 核心原理:六步闭环

一次完整的工具调用是应用与模型的多轮交互过程:

  1. 应用将"用户问题+工具定义(名称/描述/参数Schema)"发给模型。
  2. 模型推理:判断是否需要调用工具。
  3. 若需要调用:模型返回tool_call(工具名+参数JSON);若不需要:模型直接返回文本回答,流程结束。
  4. 应用根据tool_call找到对应Java方法并执行。
  5. 执行结果作为工具消息回喂给模型。
  6. 模型基于结果生成最终自然语言回答;若结果仍包含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。
    • 默认orderHIGHEST_PRECEDENCE + 300
    • 内置ToolExecutionEligibilityChecker,默认判断条件是response.hasToolCalls(),可重写以实现厂商特定的停止逻辑。
    • 支持conversationHistoryEnabled配置对话历史管理。

记忆与工具循环的放置关系(重点)

MessageChatMemoryAdvisor默认orderHIGHEST_PRECEDENCE + 200,处于工具循环之外,意味着:

  • 循环外(默认):记忆顾问在循环开始前加载一次历史,仅持久化最终的"用户-助手"消息,工具请求/响应消息不写入存储,与1.x行为一致。
  • 循环内(order > 300):记忆顾问在每轮迭代都被调用,完整记录工具请求/响应的全过程,模型可在后续对话中推理"之前试过什么、调用了哪些工具、返回了什么"。

提示 :当记忆顾问置于循环内时,必须禁用ToolCallingAdvisor的内部对话历史以避免重复写入。若使用自动注册的ToolCallingAdvisorDefaultChatClient会自动检测并禁用内部历史,无需额外配置。

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 生产环境最佳实践

  1. 描述决定一切 :模型完全依赖description判断是否调用工具,这是工具调用准确率的第一影响因素。

× 模糊描述:@Tool(description = "天气查询")

√ 清晰描述:@Tool(description = "查询指定城市的实时天气,返回温度和天气状况。当用户询问天气、气温、气候相关信息时调用")

经验法则:description写1-2句,覆盖"功能+触发场景+参数说明"。

  1. 参数与返回值设计
    • 用简单类型:StringintdoublebooleanList、Record。
    • 返回值尽量扁平,模型处理扁平结构比深层嵌套更可靠。
    • 可选参数用@Nullable@ToolParam(required = false)标注。
    • 避开"不支持的类型"清单(见3.5.3节)。
  2. 工具粒度控制:工具不是越多越好,数量过多会引发模型"选择困难",降低调用准确率并增加token消耗。建议单次注册工具数控制在5-15个以内,按业务场景动态注册(如客服场景仅挂订单/退款工具,运维场景仅挂监控/日志工具)。
  3. 异常兜底 :永远不要让工具抛出未捕获异常,需返回带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();
    }
}
  1. 安全与权限
    • 工具是"可执行入口",不要在未鉴权情况下注册删除/高危操作。
    • 在工具内部从ToolContext取用户身份,校验操作权限。
    • 对外部API调用设置超时和熔断(如Resilience4j)。
    • 返回值含敏感信息时做脱敏处理。
  2. 调试技巧 :开启DEBUG日志可观察完整的tool_calls JSON交互过程,确认模型是否发起调用、参数是否符合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接口 :定义addgetclear等标准操作,决定保留哪些消息以及何时删除。
  • 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 生产环境最佳实践

  1. ChatMemory必须是单例Bean最高频踩坑点 :若将new InMemoryChatMemory()直接写在.defaultAdvisors()里,每次请求都会创建新的Memory实例,对话根本无法留存。正确写法是将ChatMemory定义为类成员变量或Spring Bean:
java 复制代码
private final ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .maxMessages(50)
    .build();
  1. conversationId必须隔离 :需从JWT/Session/业务订单号提取唯一标识作为conversationId,遗漏或共用同一ID会导致用户间"串话",属于P0级安全事故。
  2. maxMessages按需调优
    • 简单问答:10-20条
    • 客服场景:20-30条
    • 复杂编程助手:30-50条

不设上限虽不会内存溢出(落盘存储场景),但单会话消息无限增长会撑爆token配额,增加模型"迷失"风险。

  1. 工具调用中间消息不入库:当前实现限制:执行工具调用时与LLM交换的中间message不会存储在记忆中,该问题将在未来版本解决。如需存储这些message,可参考用户控制的工具执行方案。
  2. 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检索的知识库必须提前在百炼平台创建好,有两种方式:

方式一:百炼控制台可视化创建(推荐)
  1. 登录百炼控制台,左侧"数据管理"菜单→"导入数据"上传私域文档。
  2. 左侧"知识索引"菜单→"创建知识库"完成文档向量化。
  3. 记住填写的知识库名称,作为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 生产环境最佳实践

  1. 知识库名称集中管理INDEX_NAME作为检索的唯一索引,建议通过配置中心或环境变量注入,避免硬编码:
java 复制代码
@Value("${bailian.knowledge.index-name}")
private String indexName;
  1. 启用查询重写与重排序:生产环境强烈建议开启这两个特性,可显著提升检索精度:
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()
  1. 系统提示词约束模型行为 :在DocumentRetrievalAdvisor的模板中明确要求"仅基于上下文回答,无相关信息则如实说明",避免模型幻觉:
java 复制代码
请基于提供的企业知识库内容回答问题,无相关信息则明确说明无法回答
  1. 模型选型 :默认qwen-max效果最强但成本较高;如果对延迟和成本敏感,可切换到qwen-plus
  2. 多轮对话配合记忆 :RAG服务可与MessageChatMemoryAdvisor配合使用,实现"检索增强+多轮对话记忆"的双重能力。
  3. 权限与隔离
    • 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 APIChatClient.entity(...)方法------声明目标类型,框架自动生成JSON Schema、注入提示词、反序列化结果。
  • 底层转换器APIStructuredOutputConverter及其内置实现,提供更精细的控制。

本质:框架自动在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中,通过outputSchemaoutputType处理结构化输出:

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 生产环境最佳实践

  1. **优先用Record + ****.entity()**:Java Record不可变、简洁、自动生成equals/hashCode/toString,是承载AI返回结构的理想载体。
  2. 复杂场景开启可靠性开关 :生产环境强烈建议至少开启validateSchema(),低成本显著提升成功率;若模型支持原生结构化输出,叠加useProviderStructuredOutput()更佳。
  3. Schema设计原则
    • 字段命名清晰、类型明确。
    • 对必填字段使用jakarta.validation.constraints.NotNull等注解(Spring AI会纳入Schema生成)。
    • 枚举类型优于裸字符串。
    • 避免过深的嵌套结构。
  4. 异常处理 :即使开启了validateSchema(),也要在业务代码中对解析失败做兜底:
java 复制代码
try {
    ActorsFilms films = chatClient.prompt()
        .user(...)
        .call()
        .entity(ActorsFilms.class, spec -> spec.validateSchema());
    return films;
} catch (Exception e) {
    log.error("结构化输出解析失败", e);
    // 降级:返回默认值/走非结构化文本解析/抛业务异常
}
  1. 原生结构化输出参数:可通过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(内容)、idmetadata(额外信息)。调用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检索的知识库必须在百炼平台提前创建好,有两种方式:

方式一:百炼控制台可视化创建(推荐)
  1. 登录百炼控制台,左侧"数据管理"菜单→"导入数据"上传私域文档。
  2. 左侧"知识索引"菜单→"创建知识库"完成文档向量化。
  3. 记住填写的知识库名称,作为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 生产环境最佳实践

  1. 维度一致性是硬约束 :同一应用的所有向量必须维度相同。一旦选定dimensions,后续所有入库、查询、向量字段维度都要对齐,中途修改会导致相似度计算失效。维度越低,存储和计算越快,但语义表达能力会下降。
  2. topK与相似度阈值调优
    • topK:控制返回结果数量,一般3-10。过小漏掉相关内容,过大引入噪声。
    • similarityThreshold:相似度阈值(0~1,值越高越相似),建议0.6-0.75起步,根据召回质量逐步调优。
java 复制代码
SearchRequest request = SearchRequest.builder()
    .query(query)
    .topK(5)
    .similarityThreshold(0.7f)
    .build();
  1. 元数据过滤提升精度 :元数据过滤允许在相似度搜索的同时,基于文档的元数据进行精确过滤,从而提高检索的准确性和效率。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());
  1. 入库幂等性:同一文档多次入库会产生重复向量,需要基于文档hash或业务ID做去重;文档变更时,先按元数据过滤删除旧向量,再重新入库。
  2. 企业级文档入库管道:生产环境推荐实现完整的文档入库管道:解析→分片→向量化→存储:
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);
}
  1. 选型建议
    • 追求开箱即用、不想运维向量库→百炼全托管DashScopeCloudStore
    • 数据敏感需私有部署、已有向量库→本地VectorStore(Redis/Milvus/ES等)。
    • 复杂场景可百炼RAG与VectorStore RAG混合使用,按数据性质路由到不同检索器。

8.8 总结

VectorStore=文本的"语义搜索引擎"------Document入库时自动向量化,similaritySearch()按相似度召回。Spring AI Alibaba既支持DashScopeCloudStore对接百炼全托管知识库,也兼容Redis、Milvus、Cassandra等主流向量数据库;通过QuestionAnswerAdvisor挂载到ChatClient即可实现完整RAG链路。

核心要点

  • add()内部自动调用EmbeddingModel完成向量化。
  • SearchRequesttopKsimilarityThreshold是检索精度的两个关键旋钮。
  • 维度一致性是硬约束------一旦选定不可中途更改。
  • 元数据过滤是生产环境权限隔离和精准检索的必备能力。

进阶路线DashScopeCloudStore快速接入百炼→本地VectorStore(Redis/Milvus)私有部署→SearchRequest调优topK+相似度阈值→元数据过滤实现权限隔离→QuestionAnswerAdvisor接入ChatClient完成RAG→百炼RAG+VectorStore RAG混合检索。

相关推荐
乐观的Terry1 小时前
10、发布系统-路由管理与灰度发布
java
唐青枫1 小时前
Java WebLogic 实战指南:从 Domain、数据源到 WAR 部署和集群管理
java
Biomamba生信基地1 小时前
《NG》作者采访视频:我们要学会“用”AI,而不是“学”AI
ai·生物信息学·可变剪切
2501_937860942 小时前
Java 集合底层深度剖析:Map 与 Set、二叉搜索树、哈希表全解
java·数据结构·散列表
cyforkk2 小时前
并发控制与状态安全:Single-Flight 与幂等性的本质区别
java·安全·spring
豆角焖肉3 小时前
MyBatis延迟加载、缓存机制与注解开发
java·spring·mybatis
所愿ღ3 小时前
SSM框架-Spring3
java·开发语言·笔记·spring
java1234_小锋3 小时前
【免费】基于Spark实时交通流量分析与拥堵预测系统(Java版本+可视化大屏+Kafka+SpringBoot+Vue3) 锋哥原创出品,必属精品
java·大数据·spark·kafka·实时交通流量分析与拥堵预测
组合缺一3 小时前
Solon 的 10 种 HTTP 服务器:改一行依赖,换一个引擎
java·服务器·网络协议·http·solon