Spring AI Application Example

  基于 Spring AI 框架如何构建 AI 应用程序、如何使用 ChatClient 与大模型交互、如何处理大模型的响应、如何使用 ChatModel 与大模型交互、如何使用 ChatMemory 实现会话记忆、如何使用 VectorStore 实现知识的向量存储、如何使用 Advisor 增强器、如何使用 ETL 管道处理器处理知识文档、如何实现 RAG 检索增强生成、如何实现工具调用、如何实现 MCP 服务端、如何实现 MCP 客户端等等。


—— 2025 年 7 月 7 日 甲辰年六月十三 小暑

版本

  • jdk:17
  • spring:6.2.6
  • spring boot:3.4.5
  • spring ai:1.0.0
  • spring ai alibaba:1.0.0.2

注:本文中有关 AI 相关概念与技术概述请查看 AI 相关概念与技术概述。有关 Spring AI 框架原理分析请查看 史上最烂 Spring AI 原理分析。有关 Spring AI 其它详细信息或最新信息请查看 Spring AI 官网。有关 Spring AI Alibaba 详细信息请查 Spring AI Alibaba 官网

注:史上最烂 Spring AI 原理分析 文章中 Spring AI 版本为 1.0.0-M6,本文中 Spring AI 版本为 1.0.0(正式版本),可通过参考 史上最烂 Spring AI 原理分析 来学习正式版本。

注:因 Spring AI 版本迭代较快,故文章内容可能与实际官方文档或源码不符,请以实际文档或源码为准,文章内容仅作参考。

目录

1 概述

1.1 Spring AI

  Spring AI 是由 Spring 团队开源并维护的 AI 应用开发框架。其致力于企业数据与 API 同 AI 模型的无缝集成,简化生成式 AI 应用的开发。其宗旨是 在整合 AI 大模型时,让应用程序开发变得更简单高效,同时避免不必要的复杂性。

Spring AI 从 LangChain 和 LlamaIndex 等知名 Python 项目中汲取了灵感,但 Spring AI 并非这些项目的移植版本。其始终秉持一个信念:下一代生成式 AI 不应仅局限于 Python 开发者。而应普及到多种编程语言中。

Spring AI 提供了一系列抽象接口,为开发 AI 应用程序提供了基础。并为这些抽象接口提供了多种实现,只需极少的代码改动便可替换相关组件。

  简言之,Spring AI 侧重于 AI 应用构建的底层原子能力抽象以及与 Spring Boot 生态的无缝集成,其提供了会话客户端(ChatClient、ChatModel)、会话记忆(ChatMemory)、文档抽取-转换-加载处理(ETL)、检索增强生成(RAG)、工具调用(Tool Calling)、模型上下文协议(MCP)等功能支持,便于帮助 Java 开发者快速构建 AI 应用。

1.2 Spring AI Alibaba

  Spring AI Alibaba 是由阿里巴巴团队以 Spring AI 为基础,深度集成阿里百炼平台、支持国内主流大模型(如通义千问、DeepSeek 等)、增加智能体工作流框架、开源并维护的 AI 应用开发框架。

2 相关依赖与配置

2.1 Spring AI 相关

  Spring Boot 版本为 3.4.5,Spring AI 相关依赖版本为 1.0.0。该示例中只引入了部分依赖,其余功能依赖,可根据其官方文档说明按需引入。

<!-- 会话客户端核心依赖(基础依赖) -->
<dependency>
  	<groupId>org.springframework.ai</groupId>
  	<artifactId>spring-ai-client-chat</artifactId>
  	<version>${spring-ai.version}</version>
</dependency>

<!-- 文档读取器相关(如 markdown、HTML、PDF、Tika 等格式)-->
<!-- Spring AI 和 Spring AI Alibaba 皆提供了多种实现 可在其官网、maven 仓库或 GitHub 仓库按需引入 -->
<dependency>
  	<groupId>org.springframework.ai</groupId>
  	<artifactId>spring-ai-markdown-document-reader</artifactId>
  	<version>${spring-ai.version}</version>
</dependency>

<dependency>
  	<groupId>org.springframework.ai</groupId>
  	<artifactId>spring-ai-jsoup-document-reader</artifactId>
  	<version>${spring-ai.version}</version>
</dependency>

<dependency>
  	<groupId>org.springframework.ai</groupId>
  	<artifactId>spring-ai-pdf-document-reader</artifactId>
  	<version>${spring-ai.version}</version>
</dependency>

<dependency>
  	<groupId>org.springframework.ai</groupId>
  	<artifactId>spring-ai-tika-document-reader</artifactId>
  	<version>${spring-ai.version}</version>
</dependency>

<!-- 向量存储 PGVector 向量数据库(若需其它类型向量数据库 则替换为其对应依赖) -->
<dependency>
  	<groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
  	<version>${spring-ai.version}</version>
</dependency>

<!-- Advisor API 对应依赖 -->
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-advisors-vector-store</artifactId>
  <version>${spring-ai.version}</version>
</dependency>

<!-- MCP 服务端(基于 webmvc 的实现) 若需 webflux 则引入其对应依赖即可 -->
<!-- 注:只在 MCP 服务端引入该依赖即可 -->
<dependency>
  	<groupId>org.springframework.ai</groupId>
  	<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

<!-- MCP 客户端(默认为基于 HttpClient 的 SSE 实现 若需使用基于 weblux 的 SSE 实现 则需引入 webflux 相关依赖) -->
<!-- 注:只在 MCP 客户端引入该依赖即可(即 AI 应用程序端) -->
<dependency>
  	<groupId>org.springframework.ai</groupId>
  	<artifactId>spring-ai-starter-mcp-client</artifactId>
  	<version>${spring-ai.version}</version>
</dependency>

2.2 Spring AI Alibaba 相关

  Spring AI Alibaba 版本为 1.0.0.2,其 1.0.0.X 系列版本对应 Spring AI 1.0.0 版本。该示例中只引入了部分依赖,其余功能依赖,可根据其官方文档说明按需引入。

<!-- 基础依赖 -->
<dependency>
  	<groupId>com.alibaba.cloud.ai</groupId>
  	<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    <version>${spring-ai-alibaba.version}</version>
</dependency>

<!-- 多智能体工作流依赖 -->
<dependency>
  	<groupId>com.alibaba.cloud.ai</groupId>
  	<artifactId>spring-ai-alibaba-graph-core</artifactId>
  	<version>${spring-ai-alibaba.version}</version>
</dependency>

2.3 相关配置

  以下配置请根据具体需求指定,关于更多配置,请查看 Spring AI 官网或大模型供应商官网等。

################### spring 配置 ###################
spring:
  application:
    name: spring-ai
  profiles:
    active: dev
  main:
    allow-bean-definition-overriding: true
    allow-circular-references: true
  datasource:   # 向量数据库连接配置
    url: jdbc:postgresql://xxx
    username: xxx
    password: xxx
  ai:   # spring ai 相关配置
    vectorstore:   # 向量数据库相关配置
      pgvector:
        index-type: hnsw
        dimensions: 1536
        distance-type: cosine_distance
        max-document-batch-size: 10000
        initialize-schema: true
    dash-scope:   # 阿里巴巴 dashscope 大模型平台相关配置
      api-key: xxx
      chat:
        options:   # 大模型交互参数
          model: qwen-plus   # 大模型名称

################### tomcat 配置 ###################
server:
  port: 9635
  servlet:
    context-path: /spring-ai
  tomcat:
    uri-encoding: UTF-8
  netty:
    connection-timeout: 5000

3 ChatClient 会话客户端

3.1 ChatClient 概述

  ChatClient 提供了应用程序与 AI 大模型通信的方法链式 的API,支持同步和响应式(Reactive)编程模型。其通过屏蔽会话模型(ChatModel)、提示词模版(Prompt)、会话消息(Message)、会话记忆(ChatMemory)、检索增强生成(RAG)、会话结果解析与结构化等组件,隐藏了应用程序与大模型的交互细节,从而简化了编码实现,提高了开发效率。当然也支持直接使用各组件,优点是具有更高的灵活性,缺点则是需要更多的编码实现。

  ChatClient 类似于大模型为应用程序提供的接口服务,应用程序可通过 ChatClient 完成与大模型的交互。应用程序开发者可通过 ChatClient 的方法链式(Fluent)API 快速组装一整套与 AI 大模型交互的实现。

  ChatClient 提供了以下功能:

  • 处理大模型的输入,包括系统提示词语用户提示词,即 PromptPromptTemplate
  • 调整大模型交互参数,即 ChatOptions
  • 格式化/结构化解析大模型的输出,即 Structured Output。
  • 会话记忆,即 ChatMemory
  • 工具调用,即 Tool Calling.
  • 检索增强生成,即 RAG。
  • 提供 Advisor 基础组件,可用来实现对与大模型交互的请求和响应的干预和增强。

3.2 创建 ChatClient

  可以使用 ChatClient.Builder 构建器来创建 ChatClient 实例,Spring Boot 提供的自动配置类 ChatClientAutoConfiguration 自动创建了构建器 bean,直接将构建器 bean 注入即可。同时,也可以通过编程式自行创建一个 ChatClient.Builder 实例来创建 ChatClient 实例。

3.2.1 使用构建器创建 ChatClient
  • 创建一个无状态(全局通用)ChatClient 实例

    // 创建了一个 ChatClient 实例 bean 并设置大模型系统提示词
    @Bean
    public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
      return chatClientBuilder.defaultSystem("你是大聪明,请回答用户问题。")
        .build();
    }
    
  • 创建一个有状态 ChatClient 实例

    @Resource
    private ChatClient.Builder chatClientBuilder;
    
    public String test(String prompt) {
      ChatClient chatClient = this.chatClientBuilder.build();
      return this.dashScopeChatClient.prompt()
        .system("你是编程界的大聪明,请回答用户问题。")   // 通过另一种方式设置大模型系统提示词
        .user(prompt)   // 设置用户提示词
        .call()   // 请求大模型
        .content();   // 获取模型响应
    }
    

  注:创建 ChatClient 实例时需要指定一个大模型对象(即 ChatModel)。默认情况下,ChatClient.Builder 构建器在创建 ChatClient 实例时使用的是 Spring Boot 通过自动配置提供的 ChatModel,如本示例中使用的是阿里巴巴提供的 DashScopeChatAutoConfiguration 自动配置类提供的 DashScopeChatModel 实例。如果你的应用中需要同时使用多个大模型,则需要通过编程式创建 ChatClient。

3.2.2 使用编程式创建 ChatClient

  当需要在应用中同时使用多个大模型时,可通过编程式创建 ChatClient 实例。通过设置属性 spring.ai.chat.client.enabled=false 来关闭 ChatClient.Builder 的自动配置。

  • 通过创建自定义构建器来创建

    // 按需引入对应大模型的 starter 依赖 其会自动配置 ChatModel bean 此处使用阿里百炼平台提供的实现
    @Resource
    private ChatModel dashscopeChatModel;
    
    public void test() {
      ChatClient.Builder builder = ChatClient.builder(this.dashscopeChatModel);
      ChatClient chatClient = builder.defaultSystem("你是大聪明,请回答用户问题。").build();
    }
    
  • 通过默认设置的构建器来创建

    // 按需引入对应大模型的 starter 依赖 其会自动配置 ChatModel bean 此处使用阿里百炼平台提供的实现
    @Resource
    private ChatModel dashscopeChatModel;
    
    public void test() {
      ChatClient chatClient = ChatClient.create(this.dashscopeChatModel);
    }
    
3.2.3 配置 ChatClient

  可通过全局配置和临时配置来配置 ChatClient 实例。全局配置是当前 ChatClient 实例的默认配置;临时配置是对某次请求的特殊设置,即配置某次请求要使用的特殊增强器、调用的特殊工具等。临时配置只对本次请求有效,即在请求结束后,该 ChatClient 实例会恢复到初始配置(全部配置)。

  • 全局配置

      可在构建器构建 ChatClient 实例时设置该实例的全局(默认)配置,如大模型交互参数(ChatOptions)、默认系统提示词、默认用户提示词、默认增强器(Advisor)、默认工具(ToolCallback)等,具体可查看其源码。

    @Resource
    private ChatModel dashscopeChatModel;
    
    // 创建构建器实例
    ChatClient.Builder builder = ChatClient.builder(this.dashscopeChatModel);
    
    // 创建 ChatClient 实例
    ChatClient chatClient = builder.defaultOptions(DashScopeChatOptions.builder().build())   // 设置全局大模型交互参数
      .defaultSystem()   // 设置全局系统提示词
      .defaultUser()   // 设置全局用户提示词
      .defaultAdvisors()   // 设置全局增强器
      .defaultTools()   // 设置全局工具
      .build();
    
  • 临时配置

      可在具体某词请求(交互)时配置该实例,如系统提示词、用户提示词、增强器、工具等,具体可查看其源码。

    // 先通过 ChatClient.Builder 创建 ChatClient 实例 然后配置本次请求环境
    String content = chatClient.prompt()
      .options()   // 设置临时大模型交互参数
      .system()   // 设置临时系统提示词
      .user()   // 用户提示词(即用户输入/用户问题)
      .advisors()   // 设置临时增强器
      .tools()   // 设置临时工具
      .call()   // 调用大模型
      .content();   // 获取模型响应
    

  注:除了 ChatClient#tools() 方法外,其余的临时配置会覆盖全局配置(即通过 ChatClient.Builder.defaultXXX() 方法设置的默认配置)。

3.3 PromptTemplate 提示词模版

  ChatClient 的方法链式 API 提供了可在运行时替换变量的提示词模版,包括系统提示词和用户提示词。

  在内部,ChatClient 使用 PromptTemplate 类来处理系统/用户提示词文本,并将文本中的变量替换为运行时提供的值。这些值依赖于给定的 TemplateRenderer 实现,默认情况下使用 StTemplateRenderer 实现,该实现基于 Terence Parr 开源的 StringTemplate 引擎。

3.3.1 默认模版渲染器

  默认情况下使用 StTemplateRenderer(字符串模版渲染器)。

ChatResponse chatResponse = chatClient.prompt()
  .user(prompt -> prompt.text("吧啦吧啦,吧啦 {key}。")
        .param("key", "value"))
  .call()
  .chatResponse();

  StTemplateRenderer 中默认使用 {} 符号标识变量,如果该符号与变量冲突(假设你的变量是 JSON 格式),则可通过以下方式将其替换为 <>

ChatResponse chatResponse = chatClient.prompt()
  .user(prompt -> prompt.text("吧啦吧啦,吧啦 <key>。")
        .param("key", "json 串"))
  .templateRenderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
  .call()
  .chatResponse();
3.3.2 自定义模版渲染器

  若默认模版渲染器不能满足需求,则可通过实现 TemplateRenderer 接口来自定义模版渲染器,并通过 ChatClient#templateRenderer() 方法将其指定给 ChatClient。

3.4 ChatClient 响应

  ChatClient API 提供了各种各样的方式来格式化 AI 大模型的响应结果。

3.4.1 返回 ChatResponse

  AI 大模型的响应是一种由 ChatResponse 类型定义的丰富结构。它包含生成响应的相关元数据,同时也包含多个子响应(称为 Generation),每个子响应都有自己的元数据。元数据包含用于创建响应的 token 数量信息,token 数量信息是非常重要的,因为大模型供应商会根据每个请求的 token 数量信息进行收费。

// 请求大模型后返回 ChatResponse 对象
ChatResponse chatResponse = chatClient.prompt()
  .user("")
  .call()
  .chatResponse();
3.4.2 返回实体类(Entity)
  • 返回实体类类型

      当希望返回一个自定义的实体类类型时,可通过 entity() 方法设置一个实体类型,Spring AI 会自动将大模型的响应结果转换为实体类型。

    TestEntity entity = chatClient.prompt()
      .user("")
      .call()
      .entity(TestEntity.class);
    
  • 返回泛型类型

      也可以通过 entity() 的重载方法 entity(ParameterizedTypeReference<T> type) 返回一个泛型类型,如返回一个泛型 List 结果。

    List<TestEntity> list = chatClient.prompt()
      .user("")
      .call()
      .entity(new ParameterizedTypeReference<List<TestEntity>>() {});
    
3.4.3 返回流式响应

  当希望客户端异步的、持续的接收响应结果时,则可通过 stream() 方法实现。该方法提供了一种异步的、持续的获得响应的方式。

// 流式获得响应内容
Flux<String> content = chatClient.prompt()
  .user("")
  .stream()
  .content();

// 流式获得 ChatResponse 实例
Flux<ChatResponse> chatResponseFlux = chatClient.prompt()
  .user("")
  .stream()
  .chatResponse();

注:官方文档指出,未来将会提供一个便携方法将流式响应转换为 Java 实体,并给出了具体实例代码,若有需要可自行查看官方文档。

3.4.4 call() 返回值

  上述示例中展示了几种 call() 方法的使用情况,实际其支持下列几种响应格式:

  • String:使用 conent() 可直接返回大模型响应内容。
  • ChatResponse:使用 chatResponse() 可返回 ChatResponse 实例,其包含了模型响应结果、token 使用数量及其相关元数据等更多信息。
  • ChatClientResponse:使用 chatClientResponse() 可返回 ChatClientResponse 实例,其包含了ChatResponse 实例和 ChatClient 执行上下文信息,可通过该对象访问在增强器执行过程中使用到的其它额外数据(如在 RAG 流中检索到的相关文档)。
  • Entity:使用 entity() 可返回一个 Java 实体类型,包含一下几种重载方法:
    • entity(Class< T> type):返回一个指定的实体类型。
    • entity(ParameterizedTypeRederence< T> type):返回一个指定的泛型集合类型。
    • entity(StructuredOutputConverter< T> structuredOutputConverter):使用指定的结构化输出转换器将响应内容(String 类型)转换为实体类型。
3.4.5 stream() 返回值

  上述示例中展示了几种 stream() 方法的使用情况,实际其支持下列几种响应格式:

  • Flux< String>:使用 content() 方法返回大模型响应内容字符串的流式结果。
  • Flux< ChatResponse>:使用 chatResponse() 方法返回响应的 ChatResponse 对象的流式结果。
  • Flux< ChatClientResponse>:使用 chatClientResponse() 方法返回响应的 ChatClientResponse 对象的流式结果。

4 Model 大模型

4.1 Model 概述

  Model 是 Spring AI 提供的跨 AI 大模型供应商的模型对象,定义了与大模型直接交互的 API,支持会话模型、文生图、语音转写、文本转语音、嵌入模型等。并提供了以下扩展 API:

  • ChatModel:文本交互模型,支持纯文本输入,纯文本输出。
  • ImageModel:图像模型,接收文本输入,返回模型生成的图像。
  • AudioModel:音频模型,接收文本输入,返回模型合成的音频。
  • EmbeddingModel:嵌入模型,接收文本、图像、音视频等类型数据,并将其转换为数学向量的浮点型数组返回。这些向量表示文本、图像、音视频的语义含义,数组的长度称为向量的维度,数组越长,其语义越精确。

4.2 创建 Model

4.2.1 Spring Boot Starter 自动配置模型

一般情况下,Spring AI 或模型供应商会在其实现的 Spring Boot Starter 依赖中通过自动配置类自动注册 Model bean,如本例中使用的 Spring AI Alibaba 则通过自动配置类注册了以下 Model bean:

  • DashScopeChatAutoConfiguration :注册 DashScopeChatModel bean。
  • DashScopeEmbeddingAutoConfiguration :注册 DashScopeEmbeddingModel bean。
  • DashScopeImageAutoConfiguration:注册 DashScopeImageModel bean。
  • DashScopeAudioSpeechAutoConfiguration:注册 DashScopeSpeechSynthesisModel bean。
  • 。。。还有好几个。

  对于框架自动注册的 Model bean,直接注入即可使用:

4.2.2 编程式创建模型

  若不愿使用自动注册的 Model bean 或需要自定义配置模型对象,则可通过 Model.Builder 自定义构建。

DashScopeChatModel chatModel = DashScopeChatModel.builder()
        .dashScopeApi(DashScopeApi.builder()
                   .apiKey("")   // 大模型密钥 可在供应商官网获取
                   .build())
        .defaultOptions(DashScopeChatOptions.builder()   // 设置 ChatOptions
                   .build())
        .build();

4.3 ChatOptions 会话选项

  ChatOptions 为模型交互参数配置类,可用于配置以下属性:

  • model():设置模型名称,如 qwen-plus 等。
  • temperature():设置模型温度参数。大模型温度值用于控制模型生成内容的随机性,温度值越高,生成的内容越随机和多样化,温度值越低,生成的内容越确定和一致。通常介于 0.0 ~ 1.0
  • topK():设置模型 Top-K 采样参数,在生成每个 token 时,只考虑概率最高的前 K 个 token。可限制模型的选择范围,从而避免低概率的 token 被生成。通常用于减少生成无意义内容的风险。
  • topP():设置模型 Top-P 采样参数,也称为核采样。在生成每个token时,从累积概率超过 P 的最小 token 集合中随机选择。与 Top-K 相比,Top-P 能动态调整候选集大小,更适合处理概率分布不均的情况。
  • frequencyPenalty():设置模型频率惩罚系数,降低模型重复生成相同内容的可能性。值越高,模型越倾向于使用新词而不是重复已经使用过的词。通常介于 -2.0 ~ 2.0
  • presencePenalty():设置模型存在惩罚系数,与频率惩罚类似,但作用不同。存在惩罚会降低模型生成那些已经出现在生成文本中的词汇的概率,从而鼓励模型引入新的话题或词汇。通常介于 -2.0 ~ 2.0
  • maxTokens():设置模型最大 token 数,用于限制模型输入和输出的文本长度,从而控制成本。注意,这个限制包括输入和输出的总 token 数(取决于具体模型)。
  • stopSequences():设置模型停止序列列表,当模型生成包含这些序列中的任何一个时,会立即停止生成。常用于控制输出文本长度。

  如下所示,若未设置这些属性,则会使用具体模型对应属性的默认值,同时这些属性置的设置最好参考具体模型的相关说明。

DashScopeChatModel dashScopeChatModel = DashScopeChatModel.builder()
        .dashScopeApi(DashScopeApi.builder()
                .apiKey("")
                .build())
        .defaultOptions(DashScopeChatOptions.builder()
                .withModel("qwen-plus")
                .withTemperature(0.5)
                .withTopK(50)
                .withTopP(1.0)
                .withMaxToken(1000)
                .build())
        .build();

5 ChatMemory 会话记忆

5.1 ChatMemory 概述

  ChatMemory 是 Spring AI 提供的关于会话记忆的接口,其定义了用于存储和管理会话记忆内容的 API。包含以下两个方法:

  • add():将指定消息列表添加到指定会话 id 对应会话记忆中。
  • get():根据指定会话 id 获取对应会话记忆消息列表。
  • clear():根据指定会话 id 清除会话记忆。

  默认使用 MessageWindowChatMemory 实现,即消息窗口会话记忆,默认只存储每个会话的最新 20 条消息,超过 20 条则会讲历史消息移除掉(但不会移除系统消息)。

  ChatMemory 只定义了维护会话记忆的行为,并不负责会话记忆的实际存储,实际存储由 ChatMemoryRepository 接口负责。且默认使用 InMemoryChatMemoryRepository 实现。

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .chatMemoryRepository(new InMemoryChatMemoryRepository())   // 指定会话记忆消息储存器
        .maxMessages(100)   // 指定保留的消息数量
        .build();

5.2 Message 会话消息

  Spring AI 中会话消息会被封装为 Message 对象,包括消息类型、消息内容和元数据等信息。共内置以下四个实现:

  • SystemMessage:系统提示词消息。
  • UserMessage:用户提示词消息。
  • AssistantMessage:助手提示词消息,即大模型响应消息。
  • ToolResponseMessage:工具调用响应消息。

5.3 ChatMemoryRepository 会话记忆储存器

  ChatMemoryRepository 会话记忆储存器用来存储会话记忆消息,其定义了操作会话记忆消息的 增、删、查 四个操作的方法。同时内置了基于内存的实现 InMemoryChatMemoryRepository,其内部维护一个 ConcurrentHashMap 集合用来存储会话记忆消息,线程安全。当然,你也可以使用以下三种方式存储会话记忆消息。

5.3.1 JdbcChatMemoryRepository

  JdbcChatMemoryRepository 通过 JDBC 将消息存储在关系型数据库中。它支持多个数据库,如 PostgreSQL、MySQL/MariaDB、SQL Server、HSQLDB 等,适用于需要稳定持久话会话记忆消息的应用程序。当然,需要引入对应依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>

  该依赖通过自动配置注册了 JdbcChatMemoryRepository bean,可直接注入使用:

@Resource
JdbcChatMemoryRepository chatMemoryRepository;

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .chatMemoryRepository(this.chatMemoryRepository)   // 指定会话记忆消息储存器
        .maxMessages(100)   // 指定保留的消息数量
        .build();

  当然也支持通过 JdbcChatMemoryRepository.Builder 自定义构建,需要提供一个 JdbcTemplate 和一个 JdbcChatMemoryRepositoryDialect 实例。

ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
    .jdbcTemplate(jdbcTemplate)
    .dialect(new PostgresChatMemoryDialect())
    .build();

ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .chatMemoryRepository(chatMemoryRepository)
    .maxMessages(100)
    .build();

  关于 JdbcChatMemoryRepository 的更多使用信息,请查看官方文档。

5.3.2 CassandraChatMemoryRepository

  使用 Apache Cassandra 分布式数据库作为存储介质的实现,适用于高可用、高性能、高可扩展性以及利用存活时间(TTL)应用场景。需要引入以下依赖,且支持自动注入使用和编程式使用两种方式,与 JdbcChatMemoryRepository 相同,我懒得写了。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
</dependency>
5.3.3 Neo4jChatMemoryRepository

  使用 Neo4j 图数据库作为存储介质的实现,依赖在下面,使用方式同上,更多配置请查看官方文档。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-neo4j</artifactId>
</dependency>

5.4 在 ChatClient 中使用

  ChatMemoryChatClient 中的显示使用主要体现在 MessageChatMemoryAdvisorPromptChatMemoryAdvisorVectorStoreChatMemoryAdvisor 这三个增强器上,关于这三种增强器的更多信息及使用方式请查看 [7.4.1 会话记忆增强器 章节](#7.4.1 会话记忆增强器)。

5.5 在 ChatModel 中使用

  如果你不想使用 ChatClient,而是直接使用 ChatModel,则可以通过以下方式显示的管理消息:

// 创建会话记忆实例并定义会话 id
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = "3304";

// 首次交互
UserMessage userMessage1 = new UserMessage("你好 朋友");
chatMemory.add(conversationId, userMessage1);   // 将用户消息添加到历史消息中
ChatResponse response1 = chatModel.call(new Prompt(chatMemory.get(conversationId)));   // 请求大模型
chatMemory.add(conversationId, response1.getResult().getOutput());   // 将模型响应内容添加到历史消息中

// 第二次交互
UserMessage userMessage2 = new UserMessage("我刚说了什么?");
chatMemory.add(conversationId, userMessage2);   // 将用户消息添加到历史消息中
ChatResponse response2 = chatModel.call(new Prompt(chatMemory.get(conversationId)));   // 请求大模型
chatMemory.add(conversationId, response2.getResult().getOutput());   // 将模型响应内容添加到历史消息中

//如此往复

6 VectorStore 向量存储

6.1 VectorStore 概述

  为了确保含义相同表示不同的自然语言在语义上保持相同或相近,在 AI 领域中会通过嵌入大模型(EmbeddingModel)将自然语言转化为数学向量。数学向量的表示形式为浮点型数组,数组的长度表示向量的维度,数组越长,维度越高,其语义越精确。

  VectorStore 向量存储是一种用户存储和检索高维向量数据的数据库或存储解决方案,其特别适用于处理那些经过嵌入模型转化后的数据。在 VectorStore 中,向量检索为相似性检索,而不是传统数据库的精确检索。即给定一个向量作为查询时,VectorStore 返回的是与该向量相似的向量。

  VectorStore 定义了操作向量存储介质中的向量的方法,如 增、删、相似性查等。在使用 VectorStore 时需要引入向量存储介质(如向量数据库)对应的 Spring Boot Starter 依赖。

6.2 创建 VectorStore

6.2.1 Spring Boot Starter 自动配置

  如果你使用了 Spring Boot Starter,则其自动配置机制会自动注册 VectorStore bean,直接注入使用即可。

6.2.2 编程式创建

  如果你所选择向量存储没有对应的 Starter 或者没有自动配置 VectorStore bean,则可通过编程式创建。本例中使用 Spring AI Alibaba 提供的实现 DashScopeCloudStore,可在 阿里云百炼平台 获取 API-KEY。

@Bean
public VectorStore dashScopeCloudStore(@Value("${spring.ai.dash-scope.api-key}") String apiKey) {
    return new DashScopeCloudStore(DashScopeApi.builder().apiKey(apiKey).build(),
                                   new DashScopeStoreOptions("spring-ai 知识库"));
}

  当然,如果你只是想跑个 demo 或不想使用 Spring AI Alibaba 提供的实现,那么你可以使用 Spring AI 提供的内置向量存储 SimpleVectorStore,其基于内存实现,线程安全。

// 向量存储需要指定一个嵌入模型 用于将数据向量化
@Bean
public VectorStore simpleVectorStore(EmbeddingModel dashscopeEmbeddingModel) {
    return SimpleVectorStore.builder(dashscopeEmbeddingModel).build();
}

6.3 SearchRequest 检索请求

  SearchRequest 用于封装向量检索条件,其各属性说明如下:

  • query:检索内容。
  • topK:检索到的相关文档的 Top K 数量。在进行向量数据检索时会检索到一批文档,通过此设置只取 相关性前 K 的文档。默认值为 4。
  • similarityTHreshold:检索内容的向量值与向量存储中的向量值的相似性阈值,浮点类型,值介于 0.0 ~ 1.0 之间。越接近 1.0 相似性越高,即检索到的文档与检索内容在语义上越接近;越接近 0.0 相似性越低。等于 0.0 表示全部,等于 1.0 表示精确查询。默认值为 0.0
  • filterExpression:文档过滤表达式,即类 SQL 语法过滤表达式。

7 Advisor 增强器

7.1 Advisor 概述

  Advisor 是 Spring AI 提供的灵活而强大的基础组件接口,可用来实现对与大模型交互的请求和响应的拦截、修改、增强等操作。本质上为拦截器链模式,类似于 servlet 中的 Filter 接口。通过拦截应用程序与大模型交互的请求,来对请求体数据进行预处理,同时可拦截大模型的响应,并支持对响应结果进行自定义处理。在具体实现上与面向切面编程较为相似。典型使用场景如下:

  • 修改输入与输出:在请求发送到模型前或返回结果后,动添修改数据。
  • 添加上下文相关信息:在请求发送到模型前,可添加与用户问题相关的上下文信息,如注入提示词模板、添加元数据、添加 RAG 流中检索到的文档信息等。
  • 监控与审计:记录请求日志、计算请求延迟、统计 token 用量等。
  • 安全控制:过滤敏感内容、实施权限检查等。

7.2 核心属性和方法

  Advisor接口继承了 Ordered 接口,同时包括 CallAdvisorStreamAdvisorBaseAdvisor 扩展接口,其相关属性和方法如下:

  • Ordered:Spring 提供的排序接口,用来设置 Advisor 的顺序。值越低,被执行的优先级越高。
  • AdvisorChain:增强器链,同一个 ChatClient 实例同时可添加多个 Advisor,这些 Advisor 被维护在 AdvisorChain 中,并按照 order 排序,order 越低,将会被优先执行。Spring AI 为请求添加了默认增强器,并为高优先级执行的增强器预留了 1000 个插槽,这意味着开发者可以在默认增强器前插入 1000 个自定义增强器,当然在默认增强器可插入的数量是无限制的(但 order 值不能超过 Integer 最大值)。
    • nextCall():用来获取同步请求增强器链中下一个要执行的增强器。
    • nextStream():用来获取异步请求增强器链中下一个要执行的增强器。
  • CallAdvisor:同步请求增强器,其定义了 adviseCall() 方法,用来增强与处理同步请求。
  • StreamAdvisor:异步请求增强器,其定义了 adviseStream() 方法,用来增强与处理异步请求。
  • BaseAdvisor:基础增强器,同时继承了 CallAdvisorStreamAdvisor,提供了 adviseCall()adviseStream() 方法的默认实现,并将具体增强逻辑抽象为 before()after() 方法,bedore() 表示请求前增强,after() 表示请求后增强。换言之,若需要同时实现同步请求增强和异步请求增强,且二者增强逻辑相同,则可直接实现 BaseAdvisor 接口即可。当然,Spring AI 也建议通过实现该接口实现自定义增强器。

7.3 实现示例

  实现一个请求日志记录增强器,以 JSON 格式记录请求数据和响应数据:

  • LoggerAdvisor

    @Slf4j
    public class LoggerAdvisor implements BaseAdvisor {
    
        public static final Function<ChatClientRequest, String> DEFAULT_REQUEST_TO_STRING = ModelOptionsUtils::toJsonStringPrettyPrinter;
    
        public static final Function<ChatResponse, String> DEFAULT_RESPONSE_TO_STRING = ModelOptionsUtils::toJsonStringPrettyPrinter;
    
        // 实现请求前增强方法
        @Override
        public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
            log.info("request: {}", DEFAULT_REQUEST_TO_STRING.apply(chatClientRequest));
            return chatClientRequest;
        }
    
        // 实现请求后处理方法
        @Override
        public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
            log.info("response: {}", DEFAULT_RESPONSE_TO_STRING.apply(chatClientResponse.chatResponse()));
            return chatClientResponse;
        }
    
        @Override
        public int getOrder() {
            return 100;   // 值越小 越先执行
        }
    }
    
  • 使用:

    @Resource
    private ChatModel dashscopeChatModel;
    
    public void test() {
        ChatClient.Builder builder = ChatClient.builder(this.dashscopeChatModel);
        ChatClient chatClient = builder.defaultSystem("你是大聪明,请回答用户问题。")
            .defaultAdvisors(new LoggerAdvisor())   // 将其注册到 ChatClient 实例中。
            .build();
    
        ChatResponse chatResponse = chatClient.prompt()
            .user("吧啦吧啦")
            .call()
            .chatResponse();
    }
    

  可使用 ChatClient.Builder#defaultSystem()ChatClient#advisors() 两种方式来注册 Advisor,但 Spring AI 建议使用 defaultSystem() 方法将其注册为全局增强器。实际上 Spring AI 提供了内置的简单日志增强器 SimpleLoggerAdvisor,但其日志级别为 DEBUG,不太优雅,当然你也可以通过配置文件控制其日志输出。

7.4 内置 Advisors

  Spring AI 内置了几种增强器供开发者使用,分为 会话记忆增强器、问答增强器 和 内容安全增强器。

7.4.1 会话记忆增强器

  会话记忆增强器,会通过 ChatMemory 存储会话记忆消息,并将相关历史消息作为补充增强添加到对应文本中。

  • MessageChatMemoryAdvisor:使用会话历史消息增强用户提示词。根据会话 id 从会话记忆(ChatMemory)消息列表中检索会话历史消息,并将其添加到用户提示词文本中。这种方式保持了会话的历史结构,但请注意,并非所有大模型都支持方式。

  • PromptChatMemoryAdvisor:根据会话 id 从会话记忆(ChatMemory)消息列表中检索会话历史消息,并将其添加到系统提示词文本中。

      其默认系统提示词模板如下:

    {instructions}
    
    Use the conversation memory from the MEMORY section to provide accurate answers.
    
    ---------------------
    MEMORY:
    {memory}
    ---------------------
    

      若需调整系统提示词模板,则可通过 PromptChatMemoryAdvisor.Builder#systemPromptTemplate() 方法自定义设置,如下:

    ChatClient.Builder builder = ChatClient.builder(this.dashscopeChatModel);
    
    // 通过 PromptChatMemoryAdvisor.Builder 构建一个增强器实例 
    // 构建时需要一个 ChatMemory 此处 使用其默认实现 MessageWindowChatMemory
    // 然后通过 systemPromptTemplate() 设置自定义提示词模板
    ChatClient chatClient = builder.defaultAdvisors(PromptChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build())
                    .systemPromptTemplate(PromptTemplate.builder()
                            .template("")   // 自定义提示词模板
                            .build())
                    .build())
             .build();
    
  • VectorStoreChatMemoryAdvisor:使用会话历史消息增强系统提示词。根据会话 id 和用户问题从向量存储介质(VectorStore)中检索相关文本,并将其添加到系统提示词文本中。对于从大数据中检索相关信息非常有用。

      其默认系统提示词模板如下:

    {instructions}
    
    Use the long term conversation memory from the LONG_TERM_MEMORY section to provide accurate answers.
    
    ---------------------
    LONG_TERM_MEMORY:
    {long_term_memory}
    ---------------------
    

      若需调整系统提示词模板,则可通过 VectorStoreChatMemoryAdvisor.Builder#systemPromptTemplate() 方法自定义设置,如下:

    // 通过 VectorStoreChatMemoryAdvisor.Builder 构建该增强器时需要制定一个 VectorStore 实现
    // 此处使用其默认实现 SimpleVectorStore(基于内存的实现)
    // 可通过 @Bean 将其注册为 bean 然后注入到当前类中使用
    ChatClient.Builder builder = ChatClient.builder(this.dashscopeChatModel);
    ChatClient chatClient = builder.defaultAdvisors(VectorStoreChatMemoryAdvisor.builder(this.simpleVectorStore)
                    .systemPromptTemplate(PromptTemplate.builder()
                            .template("")   // 自定义提示词模板
                            .build())
                    .defaultTopK(3)
                    .build())
             .build();
    

      还可通过 VectorStoreChatMemoryAdvisor.Builder#defaultTopK() 方法设置检索到的相关文档的 Top K 数量,以节约 token 开支。

7.4.2 问答增强器

QuestionAnswerAdvisor:根据用户问题从向量存储介质(VectorStore)中检索相似性文本,并将其添加到用户提示词文本中,是 RAG(检索增强生成)模式的一种实现。

  其默认用户提示词如下:

{query}

Context information is below, surrounded by ---------------------

---------------------
{question_answer_context}
---------------------

Given the context and provided history information and not prior knowledge,
reply to the user comment. If the answer is not in the context, inform
the user that you can't answer the question.

  可通过 QuestionAnswerAdvisor.Builder 构建器指定提示词模板、相似性检索条件等,如下:

ChatClient.Builder builder = ChatClient.builder(this.dashscopeChatModel);
ChatClient chatClient = builder.defaultAdvisors(QuestionAnswerAdvisor.builder(this.simpleVectorStore)
                .promptTemplate(PromptTemplate.builder()   // 设置提示词模板
                        .template("")   // 设置自定义用户提示词模板
                        .build())
                .searchRequest(SearchRequest.builder()   // 设置相似性检索条件
                        .topK(3)
                        .similarityThreshold(0.3)
                        .filterExpression("")   // 设置文档过滤表达式
                        .build())
                .build())
        .build();

  还可通过 QuestionAnswerAdvisor.Builder#searchRequest() 方法设置向量数据检索参数,如下:

  • topK():检索到的相关文档的 Top K 数量。在进行向量数据检索时会检索到一批文档,通过此设置只取 相关性前 K 的文档。默认值为 4。
  • similarityTHreshold():检索内容的向量值与向量存储中的向量值的相似性阈值,浮点类型,值介于 0.0 ~ 1.0 之间。越接近 1.0 相似性越高,即检索到的文档与检索内容在语义上越接近;越接近 0.0 相似性越低。等于 0.0 表示全部,等于 1.0 表示精确查询。默认值为 0.0
  • filterExpression():文档过滤表达式,即类 SQL 语法过滤表达式。
7.4.3 检索增强器

  检索增强器 RetrievalAugmentationAdvisor 是 Spring AI 实现的 RAG 库中的增强器,其基于模块化架构,为最常见的 RAG 工作流提供了开箱即用的实现。详细信息请查看 [9.3 检索增强器 章节](#9.3 检索增强器)。

7.4.4 内容安全增强器

  SafeGuardAdvisor:一种简单的增强器,用来防止大模型生成违规或不恰当的内容。且支持自定义错误响应内容、敏感词等。

ChatClient.Builder builder = ChatClient.builder(this.dashscopeChatModel);
ChatClient chatClient = builder.defaultAdvisors(SafeGuardAdvisor.builder()
                .sensitiveWords(List.of("典", "孝", "急", "麻", "乐"))
                .failureResponse("涉及敏感内容,请合理使用!")
                .build())
         .build();

8 ETL 抽取-转换-加载

8.1 ETL 概述

  ETL 是 Spring AI 设计的用来处理原始数据的框架,主要任务是将原始数据清洗整理并切分成小而精的知识碎片,同时给每个知识碎片打上标签/关键词/元数据等,接着通过嵌入模型将知识碎片转换成数学向量,最后将知识碎片对应的数学向量与标签等存储到向量存储介质中(一般为向量数据库)。被存储起来的向量数据主要为 RAG 服务,即请求大模型前,会将与用户问题的向量值相似的向量对应的知识碎片检索出来,并作为用户问题的上下文一同发送给大模型,以提高大模型生成内容的准确性。ETL 流程如下:

  • E:即抽取(extract),指从多源异构数据中读取文档,并将读取到的文档转换为 List<Document> 对象返回。其 API 为 DocumentReader
  • T:即转换(Transformer),指对读取到的文档进行数据清洗,文档转换、添加标签/关键词/元数据等处理,其转换结果为 List<Document>,其 API 为 DocumentTransformer
  • L:即加载(Load),指将处理后的文档(List<Document>)以特定格式加载到存储介质中(保存到存储介质中),其 API 为 DocumentWriter

  Spring AI 官方和 Spring AI Alibaba 等团队提供了多种 ETL 的实现,如 markdownhtmlpdftika 等,只需引入其对应依赖即可。

8.2 DocumentReader 文档读取

8.2.1 读取 JSON 文件

  JsonReader 是 Spring AI 公共包内置的用来读取 JSON 文件的读取器,其定义了三个构造器,如下:

  • JsonReader(Resource resource)
  • JsonReader(Resource resource, String… jsonKeysToUse)
  • JsonReader(Resource resource, JsonMetadataGenerator jsonMetadataGenerator, String… jsonKeysToUse)

  构造器参数说明如下:

  • resource:要读取的 JSON 文件对应的 Resource 对象,必须指定。
  • jsonKeysToUse:将指定的 JSON key 对应的内容作为文档内容(即 Document 对象的内容),若未指定,则会读取所有 key 对应的内容。
  • jsonMetadataGenerator:JSON 元数据生成器,函数式接口,用来为读取到的文档生成元数据(即标签、关键词等)。
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

// 读取指定 json 文件
public List<Document> loadJsonAsDocuments(String path) {
  JsonReader reader = new JsonReader(new ClassPathResource(path));
  return reader.read();
}

// 读取指定 json 文件 并指定要读取的 json key
public List<Document> loadJsonAsDocuments(String path, String... keys) {
  JsonReader reader = new JsonReader(new ClassPathResource(path), keys);
  return reader.read();
}

// 读取指定 json 文件 指定要读取的 json key 并指定要作为元数据的 json key
public List<Document> loadJsonAsDocuments(String path, Set<String> metadataKeys, String... keys) {
  JsonReader reader = new JsonReader(new ClassPathResource(path), jsonMap -> this.getMetadata(jsonMap, metadataKeys), keys);
  return reader.read();
}

// 生成元数据
private Map<String, Object> getMetadata(Map<String, Object> jsonMap, Set<String> metadataKeys) {
  if (CollectionUtils.isEmpty(jsonMap) || CollectionUtils.isEmpty(metadataKeys)) {
    return Collections.emptyMap();
  }

  Map<String, Object> metadata = new HashMap<>();
  this.getMetadata(jsonMap, metadataKeys, metadata);

  return metadata;
}

private void getMetadata(Map<String, Object> jsonMap, Set<String> metadataKeys, Map<String, Object> metadata) {
  String key;
  Object value;
  for (Map.Entry<String, Object> entry : jsonMap.entrySet()) {
    if (!StringUtils.hasText(key = entry.getKey()) || (value = entry.getValue()) == null) {
      continue;
    }

    if (metadataKeys.contains(key)) {
      metadata.put(key, value);
    } else if (value instanceof Map<?,?>) {
      this.getMetadata(OBJECT_MAPPER.convertValue(value, new TypeReference<>() {}), metadataKeys, metadata);
    }
  }
}

注:

  • JsonReader 使用 Jackson 库解析 JSON 文件。
  • JsonReader 可读取单个 JSON 对象,也可读取 JSON 数组。
  • JsonReader 支持处理大 JSON 文件。
  • 最终返回的 Document 文档内容将由 JSON key 对应内容的拼接结果组成。
8.2.2 读取 Text 文件

  TextReader 是 Spring AI 公共包内置的用来读取 text 文件的读取器,其定义了两个构造器,如下:

  • TextReader(String resourceUrl)
  • TextReader(Resource resource)

  构造器参数说明如下:

  • resourceUrl:text 文件路径
  • resource:要读取的 text 文件对应 Resource 对象。

  相关配置方法如下:

  • getCustomMetadata():返回一个可更改的 map 对象,可添加自定义元数据信息。
  • setCharset(Charset charset):设置要读取的 text 文件的字符集,默认为 UTF-8。
public List<Document> loadTextAsDocuments(String path) {
    TextReader reader = new TextReader(new ClassPathResource(path));
    reader.getCustomMetadata().put("filename", "AI 秘籍");   // 将文件名添加为元数据信息
    return reader.read();
}

注:

  • TextReader 会将 text 文件的整个内容作为一个 Document 对象。
  • TextReader 默认会将 text 文件的字符集和文件名作为 Document 文档元数据信息,对应 keysourcefilename
8.2.3 读取 Markdown 文件

  MarkdownDocumentReader 是 Spring AI 额外提供的用来读取 markdown 文件的读取器,依赖为 spring-ai-markdown-document-reader。其提供了三个构造器,如下:

  • MarkdownDocumentReader(String markdownResource)
  • MarkdownDocumentReader(String markdownResource, MarkdownDocumentReaderConfig config)
  • MarkdownDocumentReader(Resource markdownResource, MarkdownDocumentReaderConfig config)

  构造器参数说明如下:

  • markdownResource:markdown 文件路径。
  • markdownResource:要读取的 markdown 文件对应的 Resource 对象。
  • config:对 MarkdownDocumentReader 读取器的自定义配置。若为空则使用默认配置。

  MarkdownDocumentReaderConfig 各属性说明如下:

  • horizontalRuleCreateDocument:是否根据水平分割线 --- 切割文档。若是则会将 markdown 文件内容分割成多个 Document 对象;否则只会生成一个 Docuemnt 对象。默认为 false。
  • includeCodeBlock:是否将代码块创建为单独的 Document 文档。若是则会将代码块创建为单独的 Document 对象;否则代码块会与其周围内容包含在同一个 Document 对象中。默认为 false。
  • includeBlockquote:是否将快引用创建为单独的 Document 文档。若是则会将快引用创建为单独的 Document 对象;否则快引用会与其周围内容包含在同一个 Document 对象中。默认为 false。
  • additionalMetadata:可更改的元数据信息,map 结构。支持添加自定义元数据。默认会将标题等级、标题名称添加为元数据。
// 使用默认配置读取
public List<Document> loadMarkdownAsDocuments(String path) {
    MarkdownDocumentReader reader = new MarkdownDocumentReader(new ClassPathResource(path), 
                                                               MarkdownDocumentReaderConfig.defaultConfig());
    return reader.read();
}

// 使用默认配置读取 并添加自定义元数据
public List<Document> loadMarkdownAsDocuments(String path, Map<String, Object> metadata) {
    MarkdownDocumentReader reader = new MarkdownDocumentReader(new ClassPathResource(path), MarkdownDocumentReaderConfig.builder()
                                                               .withAdditionalMetadata(metadata)
                                                               .build());
    return reader.read();
}

// 使用自定义配置读取(可通过 MarkdownDocumentReaderConfig#withAdditionalMetadata() 方法添加自定义元数据)
public List<Document> loadMarkdownAsDocuments(String path, MarkdownDocumentReaderConfig config) {
    MarkdownDocumentReader reader = new MarkdownDocumentReader(new ClassPathResource(path), config);
    return reader.read();
}

注:

  • 文档中的头部 Headers 将变成 Document 文档对象的元数据。
  • 文档中的段落 Paragraphs 将变成 Document 文档对象的内容。
  • 文档中的代码块可包含在周围内容对应的 Document 对象中,也可以分离为单独的 Document 对象。
  • 文档中的快引用可包含在周围内容对应的 Document 对象中,也可以分离为单独的 Document 对象。
  • 文档中的水平分割线可以将文档内容分割为独立的 Document 对象。
  • Document 对象中的文档内容会保留原文档中的内联代码、列表和文本样式等格式。

8.3 DocumentTransformer 文档转换

8.3.1 基于 Token 的文本分割器 TokenTextSplitter

  TokenTextSplitter 是 Spring AI 提供的基于 token 数量分割文档的分割器,其使用 CL100K_BASE 编码,根据指定的每个块的 token 数量将文本分割成块。继承自 TextSplitter,还提供了基于 NLP 模型的 SentenceSplitter 实现。TokenTextSplitter 提供了三个构造器,如下:

  • TokenTextSplitter()
  • TokenTextSplitter(boolean keepSeparator)
  • TokenTextSplitter(int chunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator)

  构造器参数说明如下:

  • chunkSize:每一个文本块的目标 token 大小,默认 800。
  • minChunkSizeChars:每一个文本块的最小字符大小,默认 350。
  • minChunkLengthToEmbed:要包含的块的最小长度,默认 5。
  • maxNumChunks:从文本生成块的最大块数,默认 10000。
  • keepSeparator:是否在块中保留换行符,默认 true。
public List<Document> splitDocuments(List<Document> documents) {
    // 使用构建器构建默认设置的 token 文本分割器
    TokenTextSplitter splitter = TokenTextSplitter.builder().build();
    return splitter.transform(documents);
}

public List<Document> splitDocuments(List<Document> documents, boolean keepSeparator) {
    // 使用构建器构建指定 keepSeparator 属性值的 token 文本分割器
    TokenTextSplitter splitter = TokenTextSplitter.builder()
        .withKeepSeparator(keepSeparator)
        .build();
    return splitter.transform(documents);
}

public List<Document> splitDocuments(List<Document> documents, int chunkSize, int minChunkSizeChars,
                                     int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator) {
    // 使用构建器构建所有指定属性值的 token 文本分割器
    TokenTextSplitter splitter = TokenTextSplitter.builder()
        .withChunkSize(chunkSize)
        .withMinChunkSizeChars(minChunkSizeChars)
        .withMinChunkLengthToEmbed(minChunkLengthToEmbed)
        .withMaxNumChunks(maxNumChunks)
        .withKeepSeparator(keepSeparator)
        .build();
    return splitter.transform(documents);
}

  分割过程如下:

  • TokenTextSplitter 使用 CL100K_BASE 编码将读取到的文本编码为 token。
  • 基于块大小 chunkSize 将编码后的文本分割为多个块。
  • 遍历每一个块:
    • 将块解码回文本。
    • 在达到块的最小字符数 minChunkSizeChars 后,会尝试找一个合适的断点,如句号、问号、感叹号或换行符等。
    • 当找到断点后,它会断点处阶段块。
    • 根据 keepSeparator 属性设置来决定是否要移除换行符。
    • 当生成的块的数量超过 minChunkLengthToEmbed 时,则将其添加到输出中。
    • 循环此过程,直到处理完所有的 token 或总块数达到文本最大块数 maxNumChunks 时结束。
  • 如果剩余的文本的长度超过 minChunkLengthToEmbed,则添加到最终块中。

注:

  • TokenTextSplitter 会试图打破句子上的边界来创建语义上有意义的块。
  • 原始 Document 对象中携带的元数据信息会被保留并复制到所有块中。
  • 该分割器对有 token 数量限制的 LLM 非常有用,它可以确保每个块的大小都在模型的处理能力范围内。
8.3.2 关键字元数据增强器 KeywordMetadataEnricher

  KeywordMetadataEnricher 关键字元数据增强器,是 Spring AI 提供的用于增强 Document 文档元数据信息的转换器,它通过使用 AI 大模型从文档内容中抽取关键字,并将其添加到 Document 对象的元数据信息中。其提供了两个构造器,如下:

  • KeywordMetadataEnricher(ChatModel chatModel, int keywordCount)
  • KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate)

  构造器参数说明如下:

  • chatModel:用于从文档内容中抽取关键字的大模型,不能为空。

  • keywordCount:关键字的数据量,不能为空,且需 >= 1。

  • keywordsTemplate:用于让大模型抽取关键字的提示词模板,默认模板为:

    {context_str}. Give %s unique keywords for this
    document. Format as comma separated. Keywords: 
    
@Resource
private ChatModel dashscopeChatModel;

public List<Document> enrichDocuments(List<Document> documents) {
    // 构建指定大模型和关键字数量的增强器
    KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(this.dashscopeChatModel)
        .keywordCount(3)
        .build();
    return enricher.transform(documents);
}

public List<Document> enrichDocuments(List<Document> documents) {
    // 构建指定大模型、关键字数量和自定义模板的增强器
    KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(this.dashscopeChatModel)
        .keywordCount(3)
        .keywordsTemplate("你的自定义模板")
        .build();
    return enricher.transform(documents);
}

注:

  • 生成的关键字是以逗号分割的字符串。
  • 生辰的关键字会添加到 Document 对象的元数据信息中,key 为 excerpt_keywords(元数据信息为 Map 类型)。
  • 该增强器是非常有用的,尤其是对于提高文档的可检索性、生成标签或分类。关键字的检索属于精确检索,而非相似性检索。
  • 使用构建器创建时,如果指定了 keywordsTemplate 属性,则 keywordCount 属性将被忽略。
8.3.3 摘要元数据增强器 SummaryMetadataEnricher

  SummaryMetadataEnricher关键字元数据增强器,是 Spring AI 提供的用于增强 Document 文档元数据信息的转换器,它通过使用 AI 大模型为当前文档或相邻文档生成摘要信息,并将其添加到 Document 对象的元数据信息中。其提供了两个构造器,如下:

  • SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes)
  • SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes, String summaryTemplate, MetadataMode metadataMode)

  构造器参数说明如下:

  • chatModel:用于生成摘要的大模型,不能为空。

  • summaryTypes:摘要类型集合,表示要生成哪种类型的摘要。枚举类型 SummaryType,可选值为 PREVIOUS、CURRENT、NEXT。不能为空。

    • PREVIOUS:前一个文档的摘要。若集合中包含该枚举值,则当前文档元信息中将包含前一个文档的摘要。
    • CURRENT:当前文档的摘要。若集合中包含该枚举值,则当前文档元信息中将包含当前文档的摘要。
    • NEXT:后一个文档的摘要。若集合中包含该枚举值,则当前文档元信息中将包含后一个文档的摘要。
  • summaryTemplate:用于让大模型生成摘要的提示词模板,默认模板为:

    Here is the content of the section:
    {context_str}
    
    Summarize the key topics and entities of the section.
    
    Summary:
    
  • metadataMode:生成摘要时怎样处理文档元数据,即用于控制如何将现有元数据合并到摘要生成过程中。枚举类型 MetadataMode,可选值为 ALL、EMBED、INFERENCE、NONE。默认为 ALL。

@Resource
private ChatModel dashscopeChatModel;

public List<Document> enrichDocuments(List<Document> documents) {
    // 构建指定大模型、摘要类型的增强器
    SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(this.dashscopeChatModel,
                                                                   List.of(SummaryMetadataEnricher.SummaryType.CURRENT));
    return enricher.transform(documents);
}

public List<Document> enrichDocuments(List<Document> documents,
                                                 List<SummaryMetadataEnricher.SummaryType> summaryTypes,
                                                 String summaryTemplate,
                                                 MetadataMode metadataMode) {
    // 构建指定大模型、摘要类型、摘要模板和元数据模型的增强器
    SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(this.dashscopeChatModel, summaryTypes,
                                                                   summaryTemplate, metadataMode);
    return enricher.transform(documents);
}

  生成过程:

  • 遍历给定的 Document 文档列表,根据文档内容和指定的摘要提示词模板 summaryTemplate 创建提示词。
  • 将提示词发送给大模型 ChatModel 生成摘要。
  • 根据指定的摘要类型集合 summaryTypes,摘要添加到每一个文档的元数据中,元数据对应 key 为:
    • prev_section_summary:前一个文档的摘要。
    • section_summary:当前文档的摘要。
    • next_section_summary:后一个文档的摘要。

8.4 DocumentWriter 文档写入

8.4.1 将文档写入文件

  FileDocumentWriter 是 Spring AI 提供的将文档内容写入文件的写入器。其提供了三个构造器,如下:

  • FileDocumentWriter(String fileName)
  • FileDocumentWriter(String fileName, boolean widthDocumentMarkers)
  • FileDocumentWriter(String fileName, boolean widthDocumentMarkers, MetadataMode metadataMode, boolean append)

  构造器参数说明如下:

  • fileName:写入目标文件路径,不能为空。
  • widthDocumentMarkers:写入内容是否包含文档标记,文档标记是指文档索引、页码等信息,默认为 false。
  • metadataMode:文档元数据写入模式,默认为 NONE,即不写入元数据。
  • append:是否以追加方式将文档内容写入已存在文件的末尾,默认为 false。
public void writeDocuments(List<Document> documents, String filename) {
    // 根据目标文件路径创建写入器
    FileDocumentWriter writer = new FileDocumentWriter(filename);
    writer.write(documents);
}

public void writeDocuments(List<Document> documents, String filename, boolean widthDocumentMarkers) {
    // 根据目标文件路径和 widthDocumentMarkers 创建写入器
    FileDocumentWriter writer = new FileDocumentWriter(filename, widthDocumentMarkers);
    writer.write(documents);
}

public void writeDocuments(List<Document> documents, String filename, boolean widthDocumentMarkers, 
                        MetadataMode metadataMode, boolean append) {
    FileDocumentWriter writer = new FileDocumentWriter(filename, widthDocumentMarkers, metadataMode, append);
    writer.write(documents);
}

注:

  • FileDocumentWriter 使用 java.io.FilteWriter 写入文件,因此它使用操作系统默认的字符集编码写入文本文件。
  • 如果在写入期间发生错误,则会抛出 RuntimeException,并将原始原因作为异常信息。
8.4.2 将文档写入 VectorStore

  VectorStore 向量存储 API 继承了 DocumentWriter 文档写入器 API,所以 VectorStore 也具备 Document 文档内容写入功能。其写入过程如下:

  • 首先使用嵌入模型 EmbeddingModelDocument 文档内容转化为数学向量。
  • 然后将文档对应的数学向量与其元数据/标签/关键词等信息以同写入向量存储介质中,其中数学向量以向量形式存在,其它信息以字符形式存在。
  • 当然具体写入实现与表结构等信息由 VectorStore 的具体实现决定。

  关于 VectorStore 的更多信息,请查看 [6 VectorStore 向量存储 章节](#6 VectorStore 向量存储)。

9 RAG 检索增强生成

9.1 RAG 概述

  检索增强生成(Retrieval-Augmented-Generation),是一种结合信息检索技术与 AI 大模型内容生成的混合架构。其作用是通过信息检索技术将相关数据融入提示词来提高 AI 大模型生成内容的准确性、专业性和时效性,解决了大模型输出的幻觉问题。

  Spring AI 通过模块化的架构来支持 RAG,它允许你自己构建自定义的 RAG 工作流,也可以通过使用开箱即用的 Advisor API 来实现 RAG。Spring AI 提供了 Advisor 的多种内置实现,如下:

  • MessageChatMemoryAdvisor:消息会话记忆增强器,使用历史会话消息增强用户提示词。详见 [7.4.1 会话记忆增强器 章节](#7.4.1 会话记忆增强器)。
  • VectorStoreChatMemoryAdvisor:向量存储会话记忆增强器,使用向量存储介质中的历史会话消息增强系统提示词。详见 [7.4.1 会话记忆增强器 章节](#7.4.1 会话记忆增强器)。
  • QuestionAnswerAdvisor:问答增强器,根据用户问题检索向量知识库,增强用户提示词。详见 [7.4.2 问答增强器](#7.4.2 问答增强器)。
  • RetrievalAugmentationAdvisor:检索增强器,即 RAG 的模块化实现,可理解为更高级的 QuestionAnswerAdvisor

  当然,要使用上述各增强器,需引入以下依赖:

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

9.2 问答增强器

  问答增强器 QuestionAnswerAdvisor,根据用户问题检索向量知识库,以增强用户提示词。详见 [7.4.2 问答增强器](#7.4.2 问答增强器)。

9.3 检索增强器

  检索增强器 RetrievalAugmentationAdvisor 是 Spring AI 提供的开箱即用的 Advisor 的实现,其基于模块化架构实现了最常见的 RAG 工作流程。其未提供公共的构造器,但对外提供了构建器,通过构建器可指定各个阶段(检索前、检索时、检索后)的增强组件,其增强组件说明如下:

  • 检索前:检索前置处理模块,在检索前预处理用户查询,以获得最佳查询结果。共设计了两个接口,即 QueryTransformerQueryExpander
    • QueryTransformer:查询转换器,用于转换输入查询,使其可以更有效地检索到相关文档。如解决格式不佳的查询、模糊的术语、复杂的词汇或不支持的语言等。
      • CompressionQueryTransformer:压缩查询转换器,使用大语言模型将历史会话与当前问题压缩成一个独立的查询,适用于会话历时较长且当前查询与会话上下文相关的场景。
      • RewriteQueryTransformer:重写查询转换器,使用大语言模型重写用户问题,以提高查询的质量。适用于用户问题冗余、模糊或包含可能影响检索结果的信息的场景。
      • TranslationQueryTransformer:翻译查询转换器,使用大语言模型将用户问题翻译为嵌入模型支持的语言。适用于嵌入模型是用特定语言训练的,而用户查询是不同语言的场景。
    • QueryExpander:用于将复杂查询扩展成更简单的查询列表,通过精细化查询来提高查询质量。
      • MultiQueryExpander:多查询扩展器,使用大语言模型将一个查询扩展为多个语义上不同的变体,以通过不同的视角来检索到其它上下文信息和提高检索结果的相关性。
  • 检索时:检索处理模块,在检索时通过查询向量存储等系统,来检索与问题最相关的文档。设计的接口包括 DocumentRetrieverDocumentJoiner
    • DocumentRetriever:文档检索器,负责从底层数据源(如搜索引擎、向量存储、数据库或知识图谱)检索文档。
      • VectorStoreDocumentRetriever:向量存储文档检索器,从向量存储中检索语义上与输入查询相似的文档,同时支持基于元数据、相似性阈值和 top-k 结果的过滤。
    • DocumentJoiner:文档聚合器,负责将从多个数据源基于多个查询问题得到的多个文档汇聚成一个文档,同时支持处理重复文档和基于文档分数的重排序。
      • ConcatenationDocumentJoiner:相关文档聚合器,负责将从多个数据源基于多个查询问题得到的多个文档汇聚成一个文档,当出现重复文档时将保留第一个出现的文档,且保持文档分数不变。
  • 检索后:检索后置处理模块,负责处理检索到的文档,解决诸如 “丢失在中间” 或 “模型上下文长度限制” 等问题,以实现最佳的生成结果。如基于与问题的相关性对文档排序、移除不像话或冗余的文档或压缩每个文档的内容以减少噪音和冗余。设计的接口为 DocumentPostProcessor。(在 1.0.0 版本之前,检索后的增强是通过 DocumentRankerDocumentSelectorDocumentCompressor 这三个接口实现的,在 1.0.0 版本中,由文档后置处理器 DocumentPostProcessor 接口实现该功能。)
    • DocumentRanker:文档排序器,用于根据文档与查询问题的相关性重排序文档,以将最相关的文档放置在列表顶部,从而解决诸如 “丢失在中间” 之类的问题。与 DocumentSelector 不同的是,该组件不会从列表中删除文档,而是改变文档的顺序/分数。同时,该组件可基于大模型来实现。
    • DocumentSlector:文档选择器,用于删除文档列表中与查询问题不相干或冗余的文档,从而解决诸如 “丢失在中间” 或 “模型上下文长度限制” 等问题。与 DocumentRanker 不同的是,该组件不会更改文档的顺序/分数,而是直接删除文档;与 DocumentCompressor 不同的是,该组件不会修改文档内容,儿是直接删除文档。
    • DocumentCompressor:文档压缩器,用于压缩每个文档的内容,以减少检索到的信息中的冗余信息,解决诸如 “丢失在中间” 或 “模型上下文长度限制” 的问题。与 DocumentSelector 不同的是,该组件不同删除文档,而是修改文档内容;与 DocumentRanker 不同的是,该组件不会更改文档顺序/分数,而是修改文档内容。
  • 生成:生成模块,负责基于用户查询和检索到的文档生成最终的响应结果。
    • QueryAugmenter:查询增强器,负责使用额外的数据(检索到的文档)增强用户问题,为大语言模型提供必要的上下文来回答用户问题。
      • ContextualQueryAugmenter:上下文查询增强器,使用检索到的文档中的内容来作为上下文以增强用户查询。

  其构建器参数如下:

  • queryTransformers:查询转换器列表,可为空。
  • queryExpander:查询扩展器,可为空。
  • documentRetriever:文档检索器,必须指定。
  • documentJoiner:文档聚合器,默认为 ConcatenationDocumentJoiner 实现。
  • documentPostProcessors:文档后置处理器,可为空。
  • queryAugmenter:查询增强器,默认为 ContextualQueryAugmenter 实现。
  • taskExecutor:任务执行器,默认通过 RetrievalAugmentationAdvisor#buildDefaultTaskExecutor() 方法构建。
  • order:优先级,默认为 0。

  使用示例如下:

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
        .defaultAdvisors(RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()   // 设置文档检索器 DocumentRetriever
                        .vectorStore(this.simpleVectorStore)   // 给文档检索器设置向量存储 VectorStore
                        .build())
                .build())
        .build();

ChatResponse response = chatClient.prompt()
        .user("用户问题")
        .call()
        .chatResponse();

9.4 检索前增强

  检索前增强处理,在检索前预处理用户查询问题,以获得最佳的查询结果。提供了 QueryTransformerQueryExpander 两个 API。

9.4.1 查询转换器 QueryTransformer

  查询转换器 QueryTransformer 用于转换输入查询,使其可以更有效地检索到相关文档。如解决格式不佳的查询、模糊的术语、复杂的词汇或不支持的语言等。内置实现包括 CompressionQueryTransformerRewriteQueryTransformerTranslationQueryTransformer

  • 压缩查询转换器 CompressionQueryTransformer

      压缩查询转换器,使用大语言模型将会话历史与当前问题压缩成一个独立的查询,适用于会话历史较长且当前问题需要结合历史会话的场景。其构建器参数为:

    • chatClientBuilder:ChatClient.Builder 实例,用来构建大模型会话客户端 ChatClient。必须指定。

    • promptTemplate:提示词模版,可通过 CompressionQueryTransformer.Builder#promptTemplate() 方法自定义。默认提示词模版为:

      Given the following conversation history and a follow-up query, your task is to synthesize
      a concise, standalone query that incorporates the context from the history.
      Ensure the standalone query is clear, specific, and maintains the user's intent.
      
      Conversation history:
      {history}
      
      Follow-up query:
      {query}
      
      Standalone query:
      

      使用示例如下:

    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
            .defaultAdvisors(RetrievalAugmentationAdvisor.builder()   // 指定检索增强器
                    .documentRetriever(VectorStoreDocumentRetriever.builder()   // 指定文档检索器
                            .vectorStore(this.simpleVectorStore)   // 指定向量存储
                            .build())
                    .queryTransformers(CompressionQueryTransformer.builder()   // 指定压缩查询转换器
                            .chatClientBuilder(ChatClient.builder(this.dashscopeChatModel))   // 指定会话客户端构建器
                            .promptTemplate(PromptTemplate.builder()   // 指定自定义提示词模版
                                    .template("你的自定义提示词模版")
                                    .build())
                            .build())
                    .build())
            .build();
    
  • 重写查询转换器 RewriteQueryTransformer

      重写查询转换器,使用大语言模型重写用户问题,以提高查询质量。适用于用户问题冗余、模糊或包含可能影响检索结果的信息的场景。其构建器参数为:

    • chatClientBuilder:ChatClient.Builder 实例,用来构建大模型会话客户端 ChatClient。必须指定。

    • promptTemplate:提示词模版,可通过 RewriteQueryTransformer.Builder#promptTemplate() 方法自定义。默认提示词模版为:

      Given a user query, rewrite it to provide better results when querying a {target}.
      Remove any irrelevant information, and ensure the query is concise and specific.
      
      Original query:
      {query}
      
      Rewritten query:
      
    • targetSearchSystem:要查询的目标系统类型,默认为 vector store,用来替换提示词中的 {target} 变量。可通过 RewriteQueryTransformer.Builder#targetSearchSystem() 方法指定。

      使用示例如下:

    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
            .defaultAdvisors(RetrievalAugmentationAdvisor.builder()   // 指定检索增强器
                    .documentRetriever(VectorStoreDocumentRetriever.builder()   // 指定文档检索器
                            .vectorStore(this.simpleVectorStore)   // 指定向量存储
                            .build())
                    .queryTransformers(RewriteQueryTransformer.builder()   // 指定重写查询转换器
                            .chatClientBuilder(ChatClient.builder(this.dashscopeChatModel))   // 指定会话客户端构建器
                            .promptTemplate(PromptTemplate.builder()   // 指定自定义提示词模版
                                    .template("你的自定义提示词模版")
                                    .build())
                            .targetSearchSystem("vector store")   // 指定要查询的系统类型/描述/名称
                            .build())
                    .build())
            .build();
    
  • 翻译查询转换器 TranslationQueryTransformer

      翻译查询转换器,使用大语言模型将用户问题翻译为嵌入模型支持的语言。适用于嵌入模型是用特定语言训练的,而用户查询是不同语言的场景。其构建器参数为:

    • chatClientBuilder:ChatClient.Builder 实例,用来构建大模型会话客户端 ChatClient。必须指定。

    • promptTemplate:提示词模版,可通过 TranslationQueryTransformer.Builder#promptTemplate() 方法自定义。默认提示词模版为:

      Given a user query, translate it to {targetLanguage}.
      If the query is already in {targetLanguage}, return it unchanged.
      If you don't know the language of the query, return it unchanged.
      Do not add explanations nor any other text.
      
      Original query: {query}
      
      Translated query:
      
    • targetLanguage:目标语言,必须指定且不能为空。可通过 TranslationQueryTransformer.Builder#targetLanguage() 方法指定。

      使用示例如下:

    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
            .defaultAdvisors(RetrievalAugmentationAdvisor.builder()   // 指定检索增强器
                    .documentRetriever(VectorStoreDocumentRetriever.builder()   // 指定文档检索器
                            .vectorStore(this.simpleVectorStore)   // 指定向量存储
                            .build())
                    .queryTransformers(TranslationQueryTransformer.builder()   // 指定翻译查询转换器
                            .chatClientBuilder(ChatClient.builder(this.dashscopeChatModel))   // 指定会话客户端构建器
                            .promptTemplate(PromptTemplate.builder()   // 指定自定义提示词模版
                                    .template("你的自定义提示词模版")
                                    .build())
                            .targetLanguage("vector store")   // 指定目标语言
                            .build())
                    .build())
            .build()
    
9.4.2 查询扩展器 QueryExpander

  查询扩展器 QueryExpander 用于将复杂查询扩展成更简单的查询列表,通过精细化查询来提高查询质量。内置实现为 MultiQueryExpander,其作用是使用大语言模型将一个查询扩展为多个语义上不同的变体,以通过不同的视角来检索到其它上下文信息和提高检索结果的相关性。其构建器参数为:

  • chatClientBuilder:ChatClient.Builder 实例,用来构建大模型会话客户端 ChatClient。必须指定。

  • promptTemplate:提示词模版,可通过 MultiQueryExpander.Builder#promptTemplate() 方法自定义。默认提示词模版为:

    You are an expert at information retrieval and search optimization.
    Your task is to generate {number} different versions of the given query.
    
    Each variant must cover different perspectives or aspects of the topic,
    while maintaining the core intent of the original query. The goal is to
    expand the search space and improve the chances of finding relevant information.
    
    Do not explain your choices or add any other text.
    Provide the query variants separated by newlines.
    
    Original query: {query}
    
    Query variants:
    
  • includeOriginal:是否包含用户原始问题,默认为 true

  • numberOfQueries:问题的目标扩展数量,默认为 3 个。

  使用示例如下:

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
        .defaultAdvisors(RetrievalAugmentationAdvisor.builder()   // 指定检索增强器
                .documentRetriever(VectorStoreDocumentRetriever.builder()   // 指定文档检索器
                        .vectorStore(this.simpleVectorStore)   // 指定向量存储
                        .build())
                .queryExpander(MultiQueryExpander.builder()   // 指定查询扩展器
                        .chatClientBuilder(ChatClient.builder(this.dashscopeChatModel))   // 指定会话客户端构建器
                        .promptTemplate(PromptTemplate.builder()   // 指定自定义提示词模版
                                .template("你的自定义提示词模版")
                                .build())
                        .includeOriginal(false)   // 指定是否包含用户原始问题
                        .numberOfQueries(3)   // 指定问题的目标扩展数
                        .build())
                .build())
        .build()

9.5 检索时增强

  检索时增强处理,在检索时通过查询向量存储等系统,来检索与问题最相关的文档。提供了 DocumentRetrieverDocumentJoiner 两个 API。

9.5.1 文档检索器 DocumentRetriever

  文档检索器 DocumentRetriever 负责从底层数据源(搜索引擎、向量存储、数据库或知识图谱等)检索文档。内置实现为 VectorStoreDocumentRetriever,其作用是从向量存储中检索与以上与输入查询相似的文档,同时支持基于元数据、相似性阈值和 Top-K 结果的过滤。其构建器参数如下:

  • vectorStoreVectorStore 实例,即向量存储实现,必须指定。
  • similarityThreshold:向量检索时的相似性阈值,默认为 0.0,即全量检索。
  • topK:语义最相关文档数,默认为 4。
  • filterExpression:过滤表达式,可为空。可用来通过基于元数据的精确查询来过滤文档。类 SQL 表达式。

  使用示例如下:

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
        .defaultAdvisors(RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()   // 设置文档检索器 DocumentRetriever
                        .vectorStore(this.simpleVectorStore)   // 给文档检索器设置向量存储 VectorStore
                        .similarityThreshold(0.5)   // 设置相似度阈值
                        .topK(3)   // 设置 Top-K 数量
                        .filterExpression("")   // 设置过滤表达式
                        .build())
                .build())
        .build();
9.5.2 文档聚合器 DocumentJoiner

  文档聚合器 DocumentJoiner 负责将从多个数据源基于多个查询问题得到的多个文档聚合成一个文档,同时支持处理重复文档和基于文档分数的排序。内置实现为 ConcatenationDocumentJoiner,其作用是将从多个数据源基于多个查询问题得到的多个文档聚合成一个文档,当出现重复文档时保留第一个出现的文档,且保持文档分数不变。内部通过 DocumentJoiner#join() 方法来实现文档聚合,其源码如下:

@Override
public List<Document> join(Map<Query, List<List<Document>>> documentsForQuery) {
  return new ArrayList<>(documentsForQuery.values()
                         .stream()
                         .flatMap(List::stream)
                         .flatMap(List::stream)
                         .collect(Collectors.toMap(Document::getId, Function.identity(), (existing, duplicate) -> existing))
                         .values()
                         .stream()
                         .sorted(Comparator.comparingDouble((Document doc) -> doc.getScore() != null ? doc.getScore() : 0.0)
                                 .reversed())
                         .toList());
}

  使用示例如下:

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
        .defaultAdvisors(RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()   // 设置文档检索器 DocumentRetriever
                        .vectorStore(this.simpleVectorStore)
                        .build())
                .documentJoiner(new ConcatenationDocumentJoiner())   // 设置文档聚合器
                .build())
        .build();

9.6 检索后增强

  文档后置处理器 DocumentPostProcessor,负责处理检索到的文档,解决诸如 “丢失在中间” 或 “模型上下文长度限制” 等问题,以实现最佳的生成结果。如基于与问题的相关性对文档排序、移除不像话或冗余的文档或压缩每个文档的内容以减少噪音和冗余。Spring AI 没有提供内置实现,但 Spring AI Alibaba 提供了内置实现 KnowledgeRetrievalNode.KnowledgeRetrievalDocumentRanker。其构造器参数如下:

  • rerankModel:重排序大模型 RerankModel 实例,必须指定。
  • rerankOptions:重排序大模型交互参数 RerankOptions 实例,必须指定。

  使用示例如下:

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
        .defaultAdvisors(RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()   // 设置文档检索器 DocumentRetriever
                        .vectorStore(this.simpleVectorStore)
                        .build())
                .documentPostProcessors(new KnowledgeRetrievalNode.KnowledgeRetrievalDocumentRanker(this.dashscopeRerankModel,
                                DashScopeRerankOptions.builder()
                                        .build()))
                .build())
        .build();

9.7 生成时增强

  QueryAugmenter API 可用在生成时增强,内置实现为 ContextualQueryAugmenter。默认情况下,RetrievalAugmentationAdvisor 不允许检索到的上下文为空。当未检索到上下文时,它会要求大模型不会打用户问题。当然你可以通过下面的方式允许未检索到上下文:

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
        .defaultAdvisors(RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()   // 设置文档检索器 DocumentRetriever
                        .vectorStore(this.simpleVectorStore)
                        .build())
                .queryAugmenter(ContextualQueryAugmenter.builder()   // 指定上下文查询增强器
                        .allowEmptyContext(true)   // 允许未检索到上下文
                        .build())
                .build())
        .build();

10 Tool Calling 工具调用

10.1 Tool Calling 概述

  工具调用/函数调用 Tool Calling 是 Spring AI 提供的关于 AI 大模型工具调用相关的模块,它允许大模型与一系列外部的 API 或工具交互,以增强大模型的能力。

  Spring AI 提供的工具调用可分为两类操作,即 信息检索 和 执行操作:

  • 信息检索:这类工具可以从外部资源(如数据库、web service、文件系统或网页搜索引擎等)中检索信息。它的目的是增强大模型的知识,从而让大模型能够回答它不知道的问题。如它可以被用在 RAG 的场景中,例如,一个工具可以用来检索某个位置当前的天气信息、检索实时新闻或从数据库中查询特定记录等。
  • 执行操作:这类工具可用来在软件系统执行操作,如发送邮件、在数据库中增加记录、提交表单或触发工作流等。它的目的是自动化哪些需要人工干预或显示编程的任务。如,一个工具可以用于为与聊天机器人交互的顾客预定航班、填写网页表单或在代码生成场景中基于自动化测试实现 java 类。

  Spring AI 提供了下列核心 API 来实现工具调用:

  • ToolCallback:工具定义,包括工具定义信息 ToolDefinition、工具元数据信息 ToolMetadata 等。
  • @Tool@ToolParam:声明式定义工具的相关注解。
  • ToolCallResultConverter:工具调用结果转换器,用来转换工具调用的结果。
  • ToolCallbackProvider:工具提供器,维护定义好的工具实例,并将工具提供给需要的组件,如 ToolCallingManagerChatClient 等。
  • ToolCallbackResoler:工具解析器,负责在工具调用者调用工具时,通过其提供的工具名称找到对应的工具实例。
  • ToolCallingManager:工具调用管理器,负责调用工具,即管理工具调用的生命周期。
  • ToolContext:工具上下文信息,负责为工具调用时提供相关上下文信息,如用户身份信息等敏感或不能暴露给大模型的数据。

10.2 定义工具

  Spring AI 提供了两种类型的工具,分别是方法工具 MethodToolCallback 和函数式工具 FunctionToolCallback,二者皆为工具定义接口 ToolCallback 的实现类。 各自详述如下。

10.2.1 定义方法工具 MethodToolCallback

  方法工具 MethodToolCallback 可通过声明式和编程式两种方式定义。

  • 声明式定义

      声明式定义工具是指通过 @Tool@ToolParam 注解实现方法工具 MethodToolCallback 的定义。该定义在工具使用时会被工具提供器 ToolCallbackProvider 解析为 MethodToolCallback 实例。

    • @Tool:用来将类中的某个方法标记为方法工具。这个方法可以是静态的、也可以是非静态的,访问权限是任意的(如公共的、私有的、保护的等)。方法入参可以是任意数量的,参数类型也支持大多数类型(如基本数据类型、POJOs、美剧、集合、数组、键值对等)。方法返回值类型也可以是大多数类型、也可以返回为空,但如果有返回值,返回值类型必须支持可序列化。方法所在的类型可以是顶级父类,也可以是内嵌类,该类的访问权限也是任意的。其属性如下:
      • name:工具名称,工具唯一标识,不可重复。若不指定则默认使用方法名。
      • description:工具描述,若不指定则使用方法名。该属性非常重要,是大模型理解工具目的的重要依据,故 Spring AI 强烈建议为工具指定一个详细的描述。
      • returnRediect:工具调用后是否直接返回,有些工具没有返回值或工具调用较耗时,则可异步调用。默认为 false。
      • resultConverter:工具调用结果转换器 ToolCallResultConverter,负责将工具调用的结果转换为大模型可识别的格式(一般为 JSON 串)。默认使用 DefaultToolCallResultConverter
    • @ToolParam:为方法工具标记输入参数。其属性如下:
      • required:是否必传。默认为 true。该属性非常重要,建议合理设置。假设当前参数是必传的,但在某些场景下没有生成合理的参数时,那大模型可能会为其编造一个值(make one up),这就可能导致产生大模型幻觉问题。
      • description:参数描述,也非常重要。是大模型理解参数并为其准备实参的重要依据,故 Spring AI 强烈建议为参数提供一个详细的描述。
      • 注:除了使用 @ToolParam 注解标记方法工具入参外,还可以使用 Swagger 的 @Schema 注解或 Jackson 的 @JsonProperty 注解。

      通过声明式定义方法工具 MethodToolCallback 示例如下:

    public class WeatherTools {
    
        // 工具名称(建议声明为全局变量式 以便在使用工具时使用)
        public static final String GET_WEATHER_TOOL = "getWeather";
    
        // 根据指定城市获取天气信息的工具
        @Tool(name = GET_WEATHER_TOOL, description = "get the current weather by name of city")
        public String getWeather(@ToolParam(description = "name of city") String name) {
            // todo 此处调用实际获取天气的第三方 API
            return "北京今天多云,气温 36 度,闷热的一批!";
        }
    }
    
  • 编程式定义

      编程式定义工具是指通过 MethodToolCallback.Builder 构建器来构建 MethodToolCallbackMethodToolCallback.Builder 参数如下:

    • toolDefinition:工具定义信息 ToolDefinition 实例,可通过 ToolDefinition.Builder 构建。必须指定。
    • toolMetadata:工具元数据信息 ToolMetadata 实例,可通过 ToolMetadata.Builder 构建。默认为 DefaultToolMetadata
    • toolMethod:工具对应的方法 Method 实例,必须指定。
    • toolObject:包含工具对应方法的类的实例。若方法为静态方法,则可不指定。
    • toolCallResultConverter:工具调用结果转换器 ToolCallResultConverter 实例。默认使用 DefaultToolCallResultConverter。该默认转换器内部使用 Jackson 库转换工具调用结果。

      工具定义信息 ToolDefinition.Builder 参数如下:

    • name:工具名称,工具唯一标识,不可重复。必须指定。
    • description:工具描述,若不指定则使用方法名。该属性非常重要,是大模型理解工具目的的重要依据,故 Spring AI 强烈建议为工具指定一个详细的描述。
    • inputScheme:工具入参结构,JSON 串,可痛过 JsonSchemaGenerator#generateForMethodInput() 生成,必须指定。

      工具元数据信息 ToolMetadata.Builder 参数如下:

    • returnRedirect:工具调用后是否直接返回,有些工具没有返回值或工具调用较耗时,则可异步调用。默认为 false。

      通过编程式定义方法工具 MethodToolCallback 示例如下:

    // 定义工具类
    public class FileTools {
    
        public static final String FILE_WRITE_TOOL = "fileWriteTool";
        
        // 将指定内容写入指定文件的工具
        public void write(String content, String filename) {
            // todo 假装将内容写入文
        }
    }
    
    // 构建工具实例
    // 通过反射方式获取工具对应方法的 Method 实例
    Method method = ReflectionUtils.findMethod(FileTools.class, "write", String.class, String.class);
    assert method != null;
    ToolCallback fileWriteTool = MethodToolCallback.builder()
            .toolDefinition(ToolDefinition.builder()   // 指定 ToolDefinition
                    .name(FileTools.FILE_WRITE_TOOL)   // 指定工具名称
                            // 指定工具对应的方法入参结构
                    .inputSchema("""
                                {
                                      "type": "object",
                                      "properties": {
                                           "content": {
                                                 "type": "string"
                                           },
                                           "filename": {
                                                 "type": "string"
                                           }
                                      },
                                      "required": ["content", "filename"]
                                      }
                              """)
                    .description("write content to file")   // 指定工具描述
                    .build())
            .toolMethod(method)   // 指定工具对应的方法 Method 实例
            .toolObject(new FileTools())   // 因为工具对应方法为非静态方法 故指定方法所在类的实例
            .build()
    
10.2.2 定义函数式工具 FunctionToolCallback

  函数式工具 FunctionToolCallback 可通过编程式和动态指定两种方式定义。函数式工具支持 FunctionSupplierConsumerBiFunction 四种函数式类型。

  • 编程式

      编程式定义函数式工具可通过 FunctionToolCallback.Builder 构建。其构建器参数如下:

    • name:工具名称,工具唯一标识,不可重复。必须指定。
    • toolFunction:函数式接口实现,必须指定,支持 FunctionSupplierConsumerBiFunction 四种函数式类型。
    • inputType:工具入参类型(即函数式接口入参类型),必须指定。
    • description:工具描述,若不指定则使用方法名。该属性非常重要,是大模型理解工具目的的重要依据,故 Spring AI 强烈建议为工具指定一个详细的描述。
    • inputSchema:工具入参结构,JSON 串,若不指定则根据 inputType 通过 JsonSchemaGenerator#generateForMethodInput() 自动生成。
    • toolMetadata:工具元数据信息 ToolMetadata 实例,可通过 ToolMetadata.Builder 构建。默认为 DefaultToolMetadata
    • toolCallResultConverter:工具调用结果转换器 ToolCallResultConverter 实例。默认使用 DefaultToolCallResultConverter。该默认转换器内部使用 Jackson 库转换工具调用结果。

      通过编程式定义函数式工具 FunctionToolCallback 示例如下:

    ToolCallback webSearchTool = FunctionToolCallback.builder("webSearchTool", (content, result) -> {
          // todo 假装从 web 搜索引擎搜索指定内容
          return "搜索结果";
        }).inputType(String.class)
          .description("retrieve content from Baidu")
          .build();
    
  • 动态指定

      动态指定函数式工具是指通过 @Bean 将工具的函数式实现标记为 Spring bean,在运行时,工具解析器 SpringBeanToolCallbackResolver 会从 ioc 容器中找到该 bean,并将其转换为 ToolCallback 实例。其定义方式如下:

    @Bean
    @Description("工具描述吧啦吧啦")   // 通过 Spring 提供的注解指定工具描述
    public Function<String, String> testTool() {
        return param -> {
            // todo 工具具体实现逻辑
            return "工具执行结果";
        };
    }
    

  定义好的工具需要被注册到会话客户端 ChatClient 或会话模型 ChatModel 中,以便在应用程序与大模型交互时使用,详见下文。

10.3 向 ChatClient 注册工具

  会话客户端 ChatClient 支持注册默认工具(全局工具)和临时工具(局部工具),Spring AI 建议将常用的或通用的工具注册为默认工具,将特殊的或某次会话专门调用的工具注册为临时工具。

10.3.1 注册默认工具

  ChatClient.Builder 提供了下列方法来注册默认工具:

  • defaultToolNames(String... toolNames):根据工具名称 name 注册工具。该方式需要将工具实例列表 List<ToolCallback> 或工具提供器 ToolCallbackProvider 注册为 bean 才可生效。
  • defaultTools(Object... toolObjects):根据工具所在类的实例注册工具。该方式只能注册通过声明式定义的工具。
  • defaultToolCallbacks(ToolCallback... toolCallbacks):根据工具 ToolCallback 实例数组注册工具。
  • defaultToolCallbacks(List<ToolCallback> toolCallbacks):根据工具 ToolCallback 实例注册工具。
  • defaultToolCallbacks(ToolCallbackProvider... toolCallbackProviders):根据工具提供器 ToolCallbackProvider 实例注册工具。

  向 ChatClient 注册默认工具示例如下:

  1. 使用 defaultToolNames(String... toolNames) 注册工具示例

    注册工具相关 bean(下述任意一个 bean 都可以):

    @Bean   // 注册 List<ToolCallback> bean
    public List<ToolCallback> toolCallbacks() {
      // ToolCallbacks#from() 方法会将工具类实例转换为 ToolCallback 实例
      return List.of(ToolCallbacks.from(new WeatherTools(), new FileTools()));
    }
    
    @Bean   // 注册 ToolCallbackProvider bean
    public ToolCallbackProvider toolCallbackProvider() {
      return ToolCallbackProvider.from(ToolCallbacks.from(new WeatherTools(), new FileTools()));
    }
    

    注册工具:

    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
      			.defaultToolNames(WeatherTools.GET_WEATHER_TOOL, FileTools.FILE_WRITE_TOOL)
      			.build();
    
  2. 使用 defaultTools(Object... toolObjects) 注册工具示例

    // 该方式只能注册通过声明式定义的工具 即通过 @Tool 注解标注的工具
    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
      			.defaultTools(new WeatherTools())
            .build();
    
  3. 使用 defaultToolCallbacks(ToolCallback... toolCallbacks) 注册工具示例

    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
      			.defaultToolCallbacks(ToolCallbacks.from(new WeatherTools()))
      			.build();
    
  4. 使用 defaultToolCallbacks(List<ToolCallback> toolCallbacks) 注册工具示例

    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
      			.defaultToolCallbacks(List.of(ToolCallbacks.from(new WeatherTools())))
      			.build();
    

    当然,也可以先将 List<ToolCallback> 实例注册为 bean,然后将该 bean 注入使用。

  5. 使用 defaultToolCallbacks(ToolCallbackProvider... toolCallbackProviders) 注册工具示例

    ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
      			.defaultToolCallbacks(ToolCallbackProvider.from(ToolCallbacks.from(new WeatherTools())))
      			.build();
    

    当然,也可以先将 ToolCallbackProvider 实例注册为 bean,然后将该 bean 注入使用。

10.3.2 注册临时工具

  ChatClient.ChatClientRequestSpec 提供了下列方法来注册临时工具:

  • toolNames(String... toolNames):根据工具名称 name 注册工具。该方式需要将工具实例列表 List<ToolCallback> 或工具提供器 ToolCallbackProvider 注册为 bean 才可生效。
  • tools(Object... toolObjects):根据工具所在类的实例注册工具。该方式只能注册通过声明式定义的工具。
  • toolCallbacks(ToolCallback... toolCallbacks):根据工具 ToolCallback 实例数组注册工具。
  • toolCallbacks(List<ToolCallback> toolCallbacks):根据工具 ToolCallback 实例列表注册工具。
  • toolCallbacks(ToolCallbackProvider... toolCallbackProviders):根据工具提供器 ToolCallbackProvider 实例注册工具。

  向 ChatClient 注册临时工具和注册默认工具基本无差,只不过 API 少了 default 前缀罢辽。

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
  			.build();

ChatResponse response = chatClient.prompt()
  			.tools(new WeatherTools())   // 通过 tools() 方法注册临时工具
  			.user("北京今天天气怎么样")
  			.call()
  			.chatResponse();

10.4 向 ChatModel 注册工具

  会话模型 ChatModel 支持注册默认工具(全局工具)和临时工具(局部工具),Spring AI 建议将常用的或通用的工具注册为默认工具,将特殊的或某次会话专门调用的工具注册为临时工具。

10.4.1 注册默认工具

  ChatModel.Builder 支持通过指定默认 ChatOptions 来注册默认工具。ChatOptions 的扩展类 ToolCallingChatOptions.Builder 提供了以下方法来注册工具:

  • toolNames(String... toolNames):根据工具名称 name 注册工具。该方式需要将工具实例列表 List<ToolCallback> 或工具提供器 ToolCallbackProvider 注册为 bean 才可生效。
  • toolNames(Set<String> toolNames):根据工具名称 name 注册工具。该方式需要将工具实例列表 List<ToolCallback> 或工具提供器 ToolCallbackProvider 注册为 bean 才可生效。
  • toolCallbacks(ToolCallback... toolCallbacks):根据 ToolCallback 实例数组注册工具。
  • toolCallbacks(List<ToolCallback> toolCallbacks):根据 ToolCallback 实例列表注册工具。

  向 ChatModel 注册默认工具示例如下:

// 构建 ChatOptions
DashScopeChatOptions chatOptions = DashScopeChatOptions.builder()
  			.withModel("qwen-plus")   // 指定大模型名称
  			.withToolCallbacks(List.of(ToolCallbacks.from(new WeatherTools())))   // 指定工具对象 ToolCallback 列表
  			.build();

// 构建 CHatModel
DashScopeChatModel chatModel = DashScopeChatModel.builder()
  			.dashScopeApi(DashScopeApi.builder()   // 指定 api
                .apiKey(this.apiKey)   // api-key 可通过 @Value 注解注入
                .build())
  			.defaultOptions(chatOptions)   // 指定默认 ChatOptions ChatOptions 中的工具会被注册为默认工具
  			.build();

String response = chatModel.call("北京今天天气怎么样");

  其它注册方式参考上述示例如法泡制即可。

10.4.2 注册临时工具

  ChatModel 支持通过 ChatModel#call(Prompt prompt) 方法指定提示词实例 PromptchatOptions 属性时来注册临时工具。其中通过 ToolCallingChastOptions.Builder 构建 ChatOptions 的方式和上一章节相同,此处不再赘述。

  向 ChatModel 注册临时工具示例如下:

// 构建 ChatOptions 实例并指定工具
DashScopeChatOptions chatOptions = DashScopeChatOptions.builder()
  			.withToolCallbacks(List.of(ToolCallbacks.from(new WeatherTools())))
  			.build();

// 构建 Prompt 实例并指定 Chatoptions
Prompt prompt = Prompt.builder()
  			.chatOptions(chatOptions)
  			.content("北京今天天气怎么样")
  			.build();

ChatResponse response = this.dashscopeChatModel.call(prompt);

10.5 工具调用及结果转换

  在 Spring AI 中,工具调用是由 ToolCallingManager 实现的。该 API 不仅实现了工具调用,还管理着工具实例的生命周期。ChatModel 通过聚合 ToolCallingManager 的方式来实现工具调用。当 Spring AI 将注册的工具列表和提示词发送给大模型后,大模型会根据用户提示词推断要调用的工具以及为要调用的工具准备参数,然后将要调用的工具及参数响应给 Spring AI,接着 Spring AI 通过 ToolCallingManager 根据大模型返回的要调用的工具名称找到工具实例并进行调用,调用完成后将工具调用结果通过工具实例所持有的 ToolCallResultConverter 转换器转换成大模型可识别的格式,最后将工具调用的结果响应给大模型。

  综上所述,在 Spring AI 中,工具调用完全是由 ToolCallingManager 控制的,开发者只需要定义工具并注册工具即可。在 Spring Boot 环境下,会通过自动配置的方式配置 ToolCallingManager bean,当然, 开发者可以通过自定义实现 ToolCallingManager 的方式来自定义控制工具的调用。自定义实现的 ToolCallingManager 注册为 bean 即可生效,也可通过 ChatModel.Builder#toolCallingManager() 指定。

10.6 工具上下文

  工具上下文信息 ToolContext,负责为工具调用时提供相关上下文信息,如用户身份信息等敏感或不能暴露给大模型的数据。其使用方式如下:

public class WeatherTools {

    public static final String GET_WEATHER_TOOL = "getWeather";

    @Tool(name = GET_WEATHER_TOOL, description = "get the current weather by name of city")
    public String getWeather(@ToolParam(description = "name of city") String name, ToolContext toolContext) {
        // todo 此处调用实际获取天气的第三方 API
      
        // 可获取到工具上下文信息并使用 
        Map<String, Object> context = toolContext.getContext();
      
        return "北京今天多云,气温 36 度,闷热的一批!";
    }
}

  可通过 ChatClient.Builder#defaultToolContext() 方法设置默认工具上下文信息,也可通过 ChatClient.ChatClientRequestSpec#toolContext() 方法设置临时工具上下文信息。同时也支持对 ChatModel 设置默认工具上下文信息和临时工具上下文信息,设置方式与注册工具雷同。

ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
  			.defaultToolCallbacks(ToolCallbacks.from(new WeatherTools()))   // 注册默认工具
  			.build();

ChatResponse response = chatClient.prompt()
  			.toolContext(Map.of("userId", 123, "orderId", "qwerdfa"))   // 设置临时工具上下文信息
  			.user("北京今天天气怎么样")
  			.call()
  			.chatResponse();

11 MCP 模型上下文协议

11.1 MCP 概述

  MCP(Model Context Protocol),即模型上下文协议,是一种标准化协议,使模型能够以结构化方式与外部资源或工具交互。它支持多种传输机制,以提供跨不同环境的灵活性。

  Spring AI 基于 MCP Java SDK 实现了 MCP,并提供了 MCP 服务端与客户端相关的 Spring Boot Starter 依赖,开发者可以此快速开发 MCP 服务端与客户端。

11.2 创建 MCP 服务端

11.2.1 服务端相关依赖
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

  此例使用基于 WebMVC 的实现,若需使用基于 WebFlux 的实现,需要引入以下依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
11.2.2 相关配置
################### spring 配置 ###################
spring:
  application:
    name: spring-ai-mcp-server
  profiles:
    active: dev
  main:
    allow-bean-definition-overriding: true
    allow-circular-references: true
    
################### spring ai mcp 配置 ###################
  ai:
    mcp:
      server:
        name: spring-ai-mcp-server   # mcp 服务名
        version: 1.0.0   # mcp 版本
        type: SYNC   # 同步阻塞方式通信
        instructions: "This reactive server provides weather information tools and resources"   # mcp 服务描述
        sse-message-endpoint: /spring-ai-mcp
        capabilities:   # 该 mcp 服务启用的能力
          tool: true
          resource: true
          prompt: true
          completion: true

################### tomcat 配置 ###################
server:
  port: 9636
  servlet:
    context-path: /spring-ai-mcp-server
  tomcat:
    uri-encoding: UTF-8
  netty:
    connection-timeout: 5000
11.2.3 定义 MCP 服务端能力

  该 mcp 服务向外提供一个根据城市名称获取天气情况的工具。

@Service
public class WeatherTool {

    @Tool(description = "Get weather information by city name")
    public String getWeather(@ToolParam(description = "city name") String cityName) {
        // 访问气象相关第三方 API 获取天气信息
        return "今天暴晒,气温 42 度,要死人了!";
    }
}

  mcp 服务端提供的工具调用能力需要注册为 Spring bean。

@Bean
public ToolCallbackProvider weatherTools(WeatherTool weatherTool) {
  	return MethodToolCallbackProvider.builder().toolObjects(weatherTool).build();
}

11.3 创建 MCP 客户端

11.3.1 客户端相关依赖
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

  此例使用基于 WebMVC 的实现,若需使用基于 WebFlux 的实现,需要引入以下依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
11.3.2 相关配置
spring:
	ai:
  	mcp:
      client:
        name: mcp-test-client
        sse:
            connections:   # mcp 服务端地址 可配置多个
              server1:
                url: http://localhost:9636/spring-ai-mcp-server
11.3.3 MCP 能力使用

  MCP 客服端启动时会通过自动配置类 McpToolCallbackAUtoConfiguration 将 MCP 服务端提供的工具能力注册为 ToolCallbackProvider bean,开发者只需在需要使用时注入使用即可。如下:

@Resource   // 该 bean 由 mcp 客户端 starter 自动注入
private ToolCallbackProvider mcpToolCallbacks;

public void test() {
  	ChatClient chatClient = ChatClient.builder(this.dashscopeChatModel)
          .defaultToolCallbacks(this.mcpToolCallbacks)
  				.build();
  
  	ChatResponse response = chatClient.prompt()
    				.user("北京今天天气怎么样")
    				.call()
    				.chatResponse();
}

11.4 使用公共 MCP 服务

  除了上述自定义实现 MCP 服务端外,还可使用第三方 MCP 服务供应商提供的 MCP 服务,如:MCP.soGitHub MCP阿里云百炼 MCP 等。下文展示了在 MCP 客户端如何使用 MCP.so 服务市场提供的百度地图 MCP 服务。

11.4.1 添加服务配置

  从 MCP 服务市场获取相关 MVP 服务的 Server Config,并将其添加至 resources 目录下的 mcp-servers.json 文件中,没有则创建,文件名随意。

{
  "mcpServers": {
    "baidu-map": {
      "command": "npx",   // windows 环境下需要将命令改为 npx.cmd
      "args": [
        "-y",
        "@baidumap/mcp-server-baidu-map"
      ],
      "env": {
        "BAIDU_MAP_API_KEY": "你的百度地图 API key 从百度地图开放平台获取"
      }
    }
  }
}
11.4.2 添加相关配置
spring:
	ai:
    	mcp:
      		client:
        		name: mcp-test-client
        		stdio:
          			servers-configuration: classpath:/config/mcp-servers.json

  此时,即可通过注入 ToolCallbackProvider bean 使用。

  这种方式是本地通信,其原理是程序启动时会通过 npx 命令下载 mcp 服务 jar 包,并启动。注意,虽然 mcp 支持 SYNCASYNC 模式,但不能混合使用。

  在实际需要调用工具时,mcp 客户端会向服务端请求服务端所提供的工具列表,并将其转换为 ToolCallback 实例。通过此方式,将 mcp 服务端提供的工具能力与 Spring AI 的 Tool Calling 模块无缝集成在了一起。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐