<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>技术之家</title><link>https://catnav.cn/</link><description>前沿计算机技术分享</description><item><title>Spring AI ​Azure Cosmos DB</title><link>https://catnav.cn/post/89.html</link><description>&lt;h2&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;什么是 Azure Cosmos DB？&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Azure Cosmos DB 是微软推出的全球分布式云原生数据库服务，专为关键业务应用设计。它具备高可用性、低延迟的特性，支持水平扩展以满足现代应用的需求。该服务从底层构建时就以全球分布式部署、细粒度多租户和水平扩展为核心。它是 Azure 的基础服务之一，被微软大多数全球级关键业务应用使用，包括 Teams、Skype、Xbox Live、Office 365、必应、Azure 活动目录、Azure 门户、微软应用商店等。同时，数千家外部客户也在使用该服务，其中包括 OpenAI 的 ChatGPT 及其他需要弹性扩展、一键式全球部署、全球低延迟和高可用性的关键人工智能应用。&lt;/p&gt;&lt;h3&gt;什么是 DiskANN？&lt;/h3&gt;&lt;p&gt;DiskANN（基于磁盘的近似最近邻搜索）是 Azure Cosmos DB 中用于提升向量搜索性能的创新技术。它通过为存储在 Cosmos DB 中的嵌入向量建立索引，实现对高维数据高效、可扩展的相似度搜索。&lt;/p&gt;&lt;p&gt;DiskANN 具备以下优势：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;高效性：与传统方法相比，DiskANN 采用基于磁盘的结构，大幅缩短了查找最近邻的时间。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;可扩展性：能够处理超出内存容量的大型数据集，适用于机器学习、人工智能驱动解决方案等各类应用。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;低延迟：DiskANN 最大限度降低搜索操作的延迟，确保应用即使在数据量庞大的情况下也能快速获取结果。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;在 Spring AI 集成 Azure Cosmos DB 的场景中，向量搜索会创建并使用 DiskANN 索引，保障相似度查询的最优性能。&lt;/p&gt;&lt;h3&gt;通过自动配置设置 Azure Cosmos DB 向量存储&lt;/h3&gt;&lt;p&gt;以下代码演示了如何通过自动配置方式设置 CosmosDBVectorStore：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;package&amp;nbsp;com.example.demo;

import&amp;nbsp;io.micrometer.observation.ObservationRegistry;
import&amp;nbsp;org.slf4j.Logger;
import&amp;nbsp;org.slf4j.LoggerFactory;
import&amp;nbsp;org.springframework.ai.document.Document;
import&amp;nbsp;org.springframework.ai.vectorstore.SearchRequest;
import&amp;nbsp;org.springframework.ai.vectorstore.VectorStore;
import&amp;nbsp;org.springframework.beans.factory.annotation.Autowired;
import&amp;nbsp;org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import&amp;nbsp;org.springframework.boot.CommandLineRunner;
import&amp;nbsp;org.springframework.boot.SpringApplication;
import&amp;nbsp;org.springframework.boot.autoconfigure.SpringBootApplication;
import&amp;nbsp;org.springframework.context.annotation.Bean;
import&amp;nbsp;org.springframework.context.annotation.Lazy;

import&amp;nbsp;java.util.List;
import&amp;nbsp;java.util.Map;
import&amp;nbsp;java.util.UUID;

import&amp;nbsp;static&amp;nbsp;org.assertj.core.api.Assertions.assertThat;

@SpringBootApplication
@EnableAutoConfiguration
public&amp;nbsp;class&amp;nbsp;DemoApplication&amp;nbsp;implements&amp;nbsp;CommandLineRunner&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;static&amp;nbsp;final&amp;nbsp;Logger&amp;nbsp;log&amp;nbsp;=&amp;nbsp;LoggerFactory.getLogger(DemoApplication.class);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Lazy
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Autowired
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;VectorStore&amp;nbsp;vectorStore;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(DemoApplication.class,&amp;nbsp;args);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;run(String...&amp;nbsp;args)&amp;nbsp;throws&amp;nbsp;Exception&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Document&amp;nbsp;document1&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(UUID.randomUUID().toString(),&amp;nbsp;&amp;quot;Sample&amp;nbsp;content1&amp;quot;,&amp;nbsp;Map.of(&amp;quot;key1&amp;quot;,&amp;nbsp;&amp;quot;value1&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Document&amp;nbsp;document2&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(UUID.randomUUID().toString(),&amp;nbsp;&amp;quot;Sample&amp;nbsp;content2&amp;quot;,&amp;nbsp;Map.of(&amp;quot;key2&amp;quot;,&amp;nbsp;&amp;quot;value2&amp;quot;));
		this.vectorStore.add(List.of(document1,&amp;nbsp;document2));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Listresults&amp;nbsp;=&amp;nbsp;this.vectorStore.similaritySearch(SearchRequest.builder().query(&amp;quot;Sample&amp;nbsp;content&amp;quot;).topK(1).build());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;Search&amp;nbsp;results:&amp;nbsp;{}&amp;quot;,&amp;nbsp;results);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;从向量存储中删除文档
		this.vectorStore.delete(List.of(document1.getId(),&amp;nbsp;document2.getId()));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ObservationRegistry&amp;nbsp;observationRegistry()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ObservationRegistry.create();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;自动配置&lt;/h3&gt;&lt;p&gt;Spring AI 自动配置、启动器模块的构件名称发生了重大变更。更多信息请参考升级说明。&lt;/p&gt;&lt;p&gt;将以下依赖添加到你的 Maven 项目中：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-starter-vector-store-azure-cosmos-db&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h3&gt;配置属性&lt;/h3&gt;&lt;p&gt;Cosmos DB 向量存储支持以下配置属性：&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr class=&quot;firstRow&quot;&gt;&lt;th&gt;属性&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.databaseName&lt;/td&gt;&lt;td&gt;要使用的 Cosmos DB 数据库名称&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.containerName&lt;/td&gt;&lt;td&gt;要使用的 Cosmos DB 容器名称&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.partitionKeyPath&lt;/td&gt;&lt;td&gt;分区键路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.metadataFields&lt;/td&gt;&lt;td&gt;以英文逗号分隔的元数据字段列表&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.vectorStoreThroughput&lt;/td&gt;&lt;td&gt;向量存储的吞吐量&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.vectorDimensions&lt;/td&gt;&lt;td&gt;向量的维度数量&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.endpoint&lt;/td&gt;&lt;td&gt;Cosmos DB 的服务终结点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.cosmosdb.key&lt;/td&gt;&lt;td&gt;Cosmos DB 的访问密钥（若未配置密钥，将使用 DefaultAzureCredential 进行身份验证）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h3&gt;带过滤器的复杂搜索&lt;/h3&gt;&lt;p&gt;你可以在 Cosmos DB 向量存储中使用过滤器执行更复杂的搜索。以下示例演示了如何在搜索查询中使用过滤器：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Map&amp;lt;String,&amp;nbsp;Object&amp;gt;&amp;nbsp;metadata1&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;();
metadata1.put(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;UK&amp;quot;);
metadata1.put(&amp;quot;year&amp;quot;,&amp;nbsp;2021);
metadata1.put(&amp;quot;city&amp;quot;,&amp;nbsp;&amp;quot;London&amp;quot;);

Map&amp;lt;String,&amp;nbsp;Object&amp;gt;&amp;nbsp;metadata2&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;();
metadata2.put(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;NL&amp;quot;);
metadata2.put(&amp;quot;year&amp;quot;,&amp;nbsp;2022);
metadata2.put(&amp;quot;city&amp;quot;,&amp;nbsp;&amp;quot;Amsterdam&amp;quot;);

Document&amp;nbsp;document1&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;1&amp;quot;,&amp;nbsp;&amp;quot;A&amp;nbsp;document&amp;nbsp;about&amp;nbsp;the&amp;nbsp;UK&amp;quot;,&amp;nbsp;this.metadata1);
Document&amp;nbsp;document2&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;2&amp;quot;,&amp;nbsp;&amp;quot;A&amp;nbsp;document&amp;nbsp;about&amp;nbsp;the&amp;nbsp;Netherlands&amp;quot;,&amp;nbsp;this.metadata2);

vectorStore.add(List.of(document1,&amp;nbsp;document2));

FilterExpressionBuilder&amp;nbsp;builder&amp;nbsp;=&amp;nbsp;new&amp;nbsp;FilterExpressionBuilder();
List&amp;lt;Document&amp;gt;&amp;nbsp;results&amp;nbsp;=&amp;nbsp;vectorStore.similaritySearch(SearchRequest.builder().query(&amp;quot;The&amp;nbsp;World&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(10)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression((this.builder.in(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;UK&amp;quot;,&amp;nbsp;&amp;quot;NL&amp;quot;)).build()).build());&lt;/pre&gt;&lt;h3&gt;不使用自动配置设置 Azure Cosmos DB 向量存储&lt;/h3&gt;&lt;p&gt;以下代码演示了如何不依赖自动配置设置 CosmosDBVectorStore。建议使用 DefaultAzureCredential 进行 Azure Cosmos DB 的身份验证。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Bean
public&amp;nbsp;VectorStore&amp;nbsp;vectorStore(ObservationRegistry&amp;nbsp;observationRegistry)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;创建&amp;nbsp;Cosmos&amp;nbsp;DB&amp;nbsp;客户端
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CosmosAsyncClient&amp;nbsp;cosmosClient&amp;nbsp;=&amp;nbsp;new&amp;nbsp;CosmosClientBuilder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.endpoint(System.getenv(&amp;quot;COSMOSDB_AI_ENDPOINT&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.credential(new&amp;nbsp;DefaultAzureCredentialBuilder().build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.userAgentSuffix(&amp;quot;SpringAI-CDBNoSQL-VectorStore&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.gatewayMode()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.buildAsyncClient();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;创建并配置向量存储
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CosmosDBVectorStore.builder(cosmosClient,&amp;nbsp;embeddingModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.databaseName(&amp;quot;test-database&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.containerName(&amp;quot;test-container&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;配置用于过滤的元数据字段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.metadataFields(List.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;year&amp;quot;,&amp;nbsp;&amp;quot;city&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;设置分区键路径（可选）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.partitionKeyPath(&amp;quot;/id&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;配置性能参数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStoreThroughput(1000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorDimensions(1536)&amp;nbsp;&amp;nbsp;//&amp;nbsp;与你的嵌入模型维度保持一致
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;添加自定义批处理策略（可选）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.batchingStrategy(new&amp;nbsp;TokenCountBatchingStrategy())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;添加用于指标监控的观测注册表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.observationRegistry(observationRegistry)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
}

@Bean
public&amp;nbsp;EmbeddingModel&amp;nbsp;embeddingModel()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;TransformersEmbeddingModel();
}&lt;/pre&gt;&lt;p&gt;该配置展示了所有可用的构建器选项：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;databaseName：Cosmos DB 数据库名称&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;containerName：数据库内的容器名称&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;partitionKeyPath：分区键路径（如 &amp;quot;/id&amp;quot;）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;metadataFields：用于过滤的元数据字段列表&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;vectorStoreThroughput：向量存储容器的吞吐量（RU/s）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;vectorDimensions：向量维度（需与嵌入模型匹配）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;batchingStrategy：文档操作批处理策略（可选）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;手动依赖配置&lt;/h3&gt;&lt;p&gt;在你的 Maven 项目中添加以下依赖：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-azure-cosmos-db-store&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h3&gt;访问原生客户端&lt;/h3&gt;&lt;p&gt;Azure Cosmos DB 向量存储实现通过 getNativeClient() 方法提供对底层原生 Azure Cosmos DB 客户端（CosmosClient）的访问：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;CosmosDBVectorStore&amp;nbsp;vectorStore&amp;nbsp;=&amp;nbsp;context.getBean(CosmosDBVectorStore.class);
Optional&amp;lt;CosmosClient&amp;gt;&amp;nbsp;nativeClient&amp;nbsp;=&amp;nbsp;vectorStore.getNativeClient();

if&amp;nbsp;(nativeClient.isPresent())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CosmosClient&amp;nbsp;client&amp;nbsp;=&amp;nbsp;nativeClient.get();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用原生客户端执行&amp;nbsp;Azure&amp;nbsp;Cosmos&amp;nbsp;DB&amp;nbsp;专属操作
}&lt;/pre&gt;&lt;p&gt;原生客户端允许你访问 VectorStore 接口未暴露的 Azure Cosmos DB 专属特性和操作。&lt;/p&gt;</description><pubDate>Thu, 28 May 2026 11:57:03 +0800</pubDate></item><item><title>Spring AI Azure AI 服务</title><link>https://catnav.cn/post/88.html</link><description>&lt;p&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;本节将指导你设置 AzureVectorStore，用于存储文档嵌入向量，并通过 Azure AI 搜索服务执行相似度搜索。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Azure AI 搜索是一款多功能的云托管信息检索系统，隶属于微软大型人工智能平台。其核心特性之一是支持用户基于向量的存储与检索方式查询信息。&lt;/p&gt;&lt;h2&gt;前提条件&lt;/h2&gt;&lt;p&gt;Azure 订阅：使用任何 Azure 服务都需要具备 Azure 订阅权限。&lt;/p&gt;&lt;p&gt;Azure AI 搜索服务：创建一个 AI 搜索服务。服务创建完成后，在「设置」下的「密钥」部分获取管理员 API 密钥，并在「概述」部分的「URL」字段中获取服务终结点。&lt;/p&gt;&lt;p&gt;（可选）Azure OpenAI 服务：创建一个 Azure OpenAI 服务。注意：你可能需要填写单独的申请表单才能获取 Azure OpenAI 服务的访问权限。服务创建完成后，在「资源管理」下的「密钥和终结点」部分获取终结点地址和 API 密钥。&lt;/p&gt;&lt;h2&gt;配置&lt;/h2&gt;&lt;p&gt;启动时，如果你通过在构造函数中将相关的 initialize-schema 布尔属性设置为 true 启用了该功能，AzureVectorStore 会尝试在你的 AI 搜索服务实例中创建新索引；若使用 Spring Boot，则在 application.properties 文件中设置 …initialize-schema=true。&lt;/p&gt;&lt;p&gt;这是一项不兼容变更！在早期版本的 Spring AI 中，该索引结构初始化是默认执行的。&lt;/p&gt;&lt;p&gt;你也可以手动创建索引。&lt;/p&gt;&lt;p&gt;配置 AzureVectorStore 时，你需要上述前提条件中获取的配置信息以及索引名称：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;Azure AI 搜索终结点&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Azure AI 搜索密钥&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;（可选）Azure OpenAI API 终结点&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;（可选）Azure OpenAI API 密钥&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;你可以将这些值配置为操作系统环境变量。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;export&amp;nbsp;AZURE_AI_SEARCH_API_KEY=&amp;lt;My&amp;nbsp;AI&amp;nbsp;Search&amp;nbsp;API&amp;nbsp;Key&amp;gt;
export&amp;nbsp;AZURE_AI_SEARCH_ENDPOINT=&amp;lt;My&amp;nbsp;AI&amp;nbsp;Search&amp;nbsp;Index&amp;gt;
export&amp;nbsp;OPENAI_API_KEY=&amp;lt;My&amp;nbsp;Azure&amp;nbsp;AI&amp;nbsp;API&amp;nbsp;Key&amp;gt;&amp;nbsp;(Optional)&lt;/pre&gt;&lt;p&gt;你可以将 Azure OpenAI 实现替换为任意支持 Embeddings 接口的有效 OpenAI 实现。例如，你可以使用 Spring AI 的 OpenAI 或 TransformersEmbedding 实现替代 Azure 实现来生成嵌入向量。&lt;/p&gt;&lt;h2&gt;依赖项&lt;/h2&gt;&lt;p&gt;Spring AI 自动配置、启动器模块的构件名称发生了重大变更。更多信息请参考升级说明。&lt;/p&gt;&lt;p&gt;将以下依赖项添加到你的项目中：&lt;/p&gt;&lt;p&gt;1. 选择一个 Embeddings 接口实现，可选项包括：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;OpenAI 嵌入向量&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Azure AI 嵌入向量&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;本地句子转换器嵌入向量&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-starter-model-openai&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;p&gt;2. Azure（AI 搜索）向量存储&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-azure-store&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;p&gt;参考依赖管理章节，将 Spring AI BOM 添加到你的构建文件中。&lt;/p&gt;&lt;h2&gt;配置属性&lt;/h2&gt;&lt;p&gt;你可以在 Spring Boot 配置中使用以下属性自定义 Azure 向量存储。&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr class=&quot;firstRow&quot;&gt;&lt;th&gt;属性&lt;/th&gt;&lt;th&gt;默认值&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.url&lt;/td&gt;&lt;td&gt;&lt;br/&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.api-key&lt;/td&gt;&lt;td&gt;&lt;br/&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.useKeylessAuth&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.initialize-schema&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.index-name&lt;/td&gt;&lt;td&gt;spring_ai_azure_vector_store&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.default-top-k&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.default-similarity-threshold&lt;/td&gt;&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.content-field-name&lt;/td&gt;&lt;td&gt;content&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.embedding-field-name&lt;/td&gt;&lt;td&gt;embedding&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;spring.ai.vectorstore.azure.metadata-field-name&lt;/td&gt;&lt;td&gt;metadata&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h2&gt;示例代码&lt;/h2&gt;&lt;p&gt;在应用中配置 SearchIndexClient，可使用以下代码：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Bean
public&amp;nbsp;SearchIndexClient&amp;nbsp;searchIndexClient()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;SearchIndexClientBuilder().endpoint(System.getenv(&amp;quot;AZURE_AI_SEARCH_ENDPOINT&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.credential(new&amp;nbsp;AzureKeyCredential(System.getenv(&amp;quot;AZURE_AI_SEARCH_API_KEY&amp;quot;)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.buildClient();
}&lt;/pre&gt;&lt;p&gt;创建向量存储，可通过注入上述示例中创建的 SearchIndexClient Bean，以及 Spring AI 库提供的、实现目标 Embeddings 接口的 EmbeddingModel，使用以下代码：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Bean
public&amp;nbsp;VectorStore&amp;nbsp;vectorStore(SearchIndexClient&amp;nbsp;searchIndexClient,&amp;nbsp;EmbeddingModel&amp;nbsp;embeddingModel)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;return&amp;nbsp;AzureVectorStore.builder(searchIndexClient,&amp;nbsp;embeddingModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.initializeSchema(true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;定义用于相似度搜索过滤器的元数据字段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterMetadataFields(List.of(MetadataField.text(&amp;quot;country&amp;quot;),&amp;nbsp;MetadataField.int64(&amp;quot;year&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MetadataField.date(&amp;quot;activationDate&amp;quot;)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.defaultTopK(5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.defaultSimilarityThreshold(0.7)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.indexName(&amp;quot;spring-ai-document-index&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
}&lt;/pre&gt;&lt;p&gt;你必须显式列出过滤表达式中使用的所有元数据字段名称和类型。上述代码注册了可过滤的元数据字段：文本类型的 country、64 位整型的 year 以及布尔类型的 active。&lt;/p&gt;&lt;p&gt;如果可过滤元数据字段新增了内容，你必须重新上传/更新包含该元数据的文档。&lt;/p&gt;&lt;p&gt;在主代码中，创建若干文档：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;List.of(
	new&amp;nbsp;Document(&amp;quot;Spring&amp;nbsp;AI&amp;nbsp;rocks!!&amp;nbsp;Spring&amp;nbsp;AI&amp;nbsp;rocks!!&amp;nbsp;Spring&amp;nbsp;AI&amp;nbsp;rocks!!&amp;nbsp;Spring&amp;nbsp;AI&amp;nbsp;rocks!!&amp;nbsp;Spring&amp;nbsp;AI&amp;nbsp;rocks!!&amp;quot;,&amp;nbsp;Map.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;BG&amp;quot;,&amp;nbsp;&amp;quot;year&amp;quot;,&amp;nbsp;2020)),
	new&amp;nbsp;Document(&amp;quot;The&amp;nbsp;World&amp;nbsp;is&amp;nbsp;Big&amp;nbsp;and&amp;nbsp;Salvation&amp;nbsp;Lurks&amp;nbsp;Around&amp;nbsp;the&amp;nbsp;Corner&amp;quot;),
	new&amp;nbsp;Document(&amp;quot;You&amp;nbsp;walk&amp;nbsp;forward&amp;nbsp;facing&amp;nbsp;the&amp;nbsp;past&amp;nbsp;and&amp;nbsp;you&amp;nbsp;turn&amp;nbsp;back&amp;nbsp;toward&amp;nbsp;the&amp;nbsp;future.&amp;quot;,&amp;nbsp;Map.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;NL&amp;quot;,&amp;nbsp;&amp;quot;year&amp;quot;,&amp;nbsp;2023)));&lt;/pre&gt;&lt;p&gt;将文档添加到向量存储：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;vectorStore.add(documents);&lt;/pre&gt;&lt;p&gt;最后，检索与查询语句相似的文档：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;List&amp;lt;Document&amp;gt;&amp;nbsp;results&amp;nbsp;=&amp;nbsp;vectorStore.similaritySearch(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(&amp;quot;Spring&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(5).build());&lt;/pre&gt;&lt;p&gt;如果一切正常，你将检索到包含文本「Spring AI rocks!!」的文档。&lt;/p&gt;&lt;h2&gt;元数据过滤&lt;/h2&gt;&lt;p&gt;你也可以在 AzureVectorStore 中使用通用、可移植的元数据过滤器。&lt;/p&gt;&lt;p&gt;例如，你可以使用文本表达式语言：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;vectorStore.similaritySearch(
&amp;nbsp;&amp;nbsp;&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(&amp;quot;The&amp;nbsp;World&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(TOP_K)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(SIMILARITY_THRESHOLD)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression(&amp;quot;country&amp;nbsp;in&amp;nbsp;[&amp;#39;UK&amp;#39;,&amp;nbsp;&amp;#39;NL&amp;#39;]&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;year&amp;nbsp;&amp;gt;=&amp;nbsp;2020&amp;quot;).build());&lt;/pre&gt;&lt;p&gt;或通过表达式 DSL 编程实现：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;FilterExpressionBuilder&amp;nbsp;b&amp;nbsp;=&amp;nbsp;new&amp;nbsp;FilterExpressionBuilder();

vectorStore.similaritySearch(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(&amp;quot;The&amp;nbsp;World&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(TOP_K)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(SIMILARITY_THRESHOLD)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression(b.and(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;b.in(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;UK&amp;quot;,&amp;nbsp;&amp;quot;NL&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;b.gte(&amp;quot;year&amp;quot;,&amp;nbsp;2020)).build()).build());&lt;/pre&gt;&lt;p&gt;可移植的过滤表达式会自动转换为 Azure 搜索专用的 OData 过滤器。例如，以下可移植过滤表达式：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;country&amp;nbsp;in&amp;nbsp;[&amp;#39;UK&amp;#39;,&amp;nbsp;&amp;#39;NL&amp;#39;]&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;year&amp;nbsp;&amp;gt;=&amp;nbsp;2020&lt;/pre&gt;&lt;p&gt;会被转换为以下 Azure OData 过滤表达式：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;$filter&amp;nbsp;search.in(meta_country,&amp;nbsp;&amp;#39;UK,NL&amp;#39;,&amp;nbsp;&amp;#39;,&amp;#39;)&amp;nbsp;and&amp;nbsp;meta_year&amp;nbsp;ge&amp;nbsp;2020&lt;/pre&gt;&lt;h2&gt;自定义字段名&lt;/h2&gt;&lt;p&gt;默认情况下，Azure 向量存储在 Azure AI 搜索索引中使用以下字段名：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;content - 存储文档文本&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;embedding - 存储向量嵌入&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;metadata - 存储文档元数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;但是，当使用已存在的、采用不同字段名的 Azure AI 搜索索引时，你可以配置自定义字段名以匹配索引结构。这让你无需修改现有索引，即可将 Spring AI 与之集成。&lt;/p&gt;&lt;h2&gt;使用场景&lt;/h2&gt;&lt;p&gt;自定义字段名在以下场景中尤为实用：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;集成现有索引：你的组织已拥有遵循既定字段命名规范（如 chunk_text、vector、meta_data）的 Azure AI 搜索索引。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;遵循命名标准：你的团队使用与默认值不同的特定命名规范。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;从其他系统迁移：你正在从其他向量数据库或搜索系统迁移，并希望保持字段名一致。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;通过属性配置&lt;/h2&gt;&lt;p&gt;你可以通过 Spring Boot 应用属性配置自定义字段名：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;spring.ai.vectorstore.azure.url=${AZURE_AI_SEARCH_ENDPOINT}
spring.ai.vectorstore.azure.api-key=${AZURE_AI_SEARCH_API_KEY}
spring.ai.vectorstore.azure.index-name=my-existing-index
spring.ai.vectorstore.azure.initialize-schema=false

#&amp;nbsp;匹配现有索引结构的自定义字段名
spring.ai.vectorstore.azure.content-field-name=chunk_text
spring.ai.vectorstore.azure.embedding-field-name=vector
spring.ai.vectorstore.azure.metadata-field-name=meta_data&lt;/pre&gt;&lt;p&gt;使用带有自定义字段名的现有索引时，设置 initialize-schema=false，避免 Spring AI 尝试使用默认结构创建新索引。&lt;/p&gt;&lt;h2&gt;通过构建器 API 配置&lt;/h2&gt;&lt;p&gt;此外，你可以通过构建器 API 编程配置自定义字段名：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Bean
public&amp;nbsp;VectorStore&amp;nbsp;vectorStore(SearchIndexClient&amp;nbsp;searchIndexClient,&amp;nbsp;EmbeddingModel&amp;nbsp;embeddingModel)&amp;nbsp;{

	return&amp;nbsp;AzureVectorStore.builder(searchIndexClient,&amp;nbsp;embeddingModel)
		.indexName(&amp;quot;my-existing-index&amp;quot;)
		.initializeSchema(false)&amp;nbsp;//&amp;nbsp;不创建结构&amp;nbsp;-&amp;nbsp;使用现有索引
		//&amp;nbsp;配置自定义字段名以匹配现有索引
		.contentFieldName(&amp;quot;chunk_text&amp;quot;)
		.embeddingFieldName(&amp;quot;vector&amp;quot;)
		.metadataFieldName(&amp;quot;meta_data&amp;quot;)
		.filterMetadataFields(List.of(
			MetadataField.text(&amp;quot;category&amp;quot;),
			MetadataField.text(&amp;quot;source&amp;quot;)))
		.build();
}&lt;/pre&gt;&lt;h2&gt;完整示例：使用现有索引&lt;/h2&gt;&lt;p&gt;以下是一个完整示例，展示如何将 Spring AI 与带有自定义字段名的现有 Azure AI 搜索索引配合使用：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Configuration
public&amp;nbsp;class&amp;nbsp;VectorStoreConfig&amp;nbsp;{

	@Bean
	public&amp;nbsp;SearchIndexClient&amp;nbsp;searchIndexClient()&amp;nbsp;{
		return&amp;nbsp;new&amp;nbsp;SearchIndexClientBuilder()
			.endpoint(System.getenv(&amp;quot;AZURE_AI_SEARCH_ENDPOINT&amp;quot;))
			.credential(new&amp;nbsp;AzureKeyCredential(System.getenv(&amp;quot;AZURE_AI_SEARCH_API_KEY&amp;quot;)))
			.buildClient();
	}

	@Bean
	public&amp;nbsp;VectorStore&amp;nbsp;vectorStore(SearchIndexClient&amp;nbsp;searchIndexClient,
			EmbeddingModel&amp;nbsp;embeddingModel)&amp;nbsp;{

		return&amp;nbsp;AzureVectorStore.builder(searchIndexClient,&amp;nbsp;embeddingModel)
			.indexName(&amp;quot;production-documents-index&amp;quot;)
			.initializeSchema(false)&amp;nbsp;//&amp;nbsp;使用现有索引
			//&amp;nbsp;映射到现有索引字段名
			.contentFieldName(&amp;quot;document_text&amp;quot;)
			.embeddingFieldName(&amp;quot;text_vector&amp;quot;)
			.metadataFieldName(&amp;quot;document_metadata&amp;quot;)
			//&amp;nbsp;定义现有结构中的可过滤元数据字段
			.filterMetadataFields(List.of(
				MetadataField.text(&amp;quot;department&amp;quot;),
				MetadataField.int64(&amp;quot;year&amp;quot;),
				MetadataField.date(&amp;quot;created_date&amp;quot;)))
			.defaultTopK(10)
			.defaultSimilarityThreshold(0.75)
			.build();
	}
}&lt;/pre&gt;&lt;p&gt;之后你可以正常使用向量存储：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;//&amp;nbsp;使用带有自定义字段名的现有索引进行搜索
List&amp;lt;Document&amp;gt;&amp;nbsp;results&amp;nbsp;=&amp;nbsp;vectorStore.similaritySearch(
	SearchRequest.builder()
		.query(&amp;quot;artificial&amp;nbsp;intelligence&amp;quot;)
		.topK(5)
		.filterExpression(&amp;quot;department&amp;nbsp;==&amp;nbsp;&amp;#39;Engineering&amp;#39;&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;year&amp;nbsp;&amp;gt;=&amp;nbsp;2023&amp;quot;)
		.build());

//&amp;nbsp;结果包含来自&amp;nbsp;document_text&amp;nbsp;字段的文档文本
results.forEach(doc&amp;nbsp;-&amp;gt;&amp;nbsp;System.out.println(doc.getText()));&lt;/pre&gt;&lt;h2&gt;使用自定义字段名创建新索引&lt;/h2&gt;&lt;p&gt;你也可以通过设置 initialize-schema=true，使用自定义字段名创建新索引：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Bean
public&amp;nbsp;VectorStore&amp;nbsp;vectorStore(SearchIndexClient&amp;nbsp;searchIndexClient,
		EmbeddingModel&amp;nbsp;embeddingModel)&amp;nbsp;{

	return&amp;nbsp;AzureVectorStore.builder(searchIndexClient,&amp;nbsp;embeddingModel)
		.indexName(&amp;quot;new-custom-index&amp;quot;)
		.initializeSchema(true)&amp;nbsp;//&amp;nbsp;使用自定义字段名创建新索引
		.contentFieldName(&amp;quot;text_content&amp;quot;)
		.embeddingFieldName(&amp;quot;content_vector&amp;quot;)
		.metadataFieldName(&amp;quot;doc_metadata&amp;quot;)
		.filterMetadataFields(List.of(
			MetadataField.text(&amp;quot;category&amp;quot;),
			MetadataField.text(&amp;quot;author&amp;quot;)))
		.build();
}&lt;/pre&gt;&lt;p&gt;这会创建一个包含你自定义字段名的新 Azure AI 搜索索引，让你从一开始就确立专属的命名规范。&lt;/p&gt;&lt;h2&gt;访问原生客户端&lt;/h2&gt;&lt;p&gt;Azure 向量存储实现通过 getNativeClient() 方法提供对底层原生 Azure 搜索客户端（SearchClient）的访问：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;AzureVectorStore&amp;nbsp;vectorStore&amp;nbsp;=&amp;nbsp;context.getBean(AzureVectorStore.class);
Optional&amp;lt;SearchClient&amp;gt;&amp;nbsp;nativeClient&amp;nbsp;=&amp;nbsp;vectorStore.getNativeClient();

if&amp;nbsp;(nativeClient.isPresent())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SearchClient&amp;nbsp;client&amp;nbsp;=&amp;nbsp;nativeClient.get();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用原生客户端执行&amp;nbsp;Azure&amp;nbsp;搜索专属操作
}&lt;/pre&gt;&lt;p&gt;原生客户端允许你访问 VectorStore 接口未暴露的 Azure 搜索专属特性和操作。&lt;/p&gt;</description><pubDate>Thu, 28 May 2026 11:54:43 +0800</pubDate></item><item><title>Spring AI 向量数据库</title><link>https://catnav.cn/post/87.html</link><description>&lt;p&gt;向量数据库是一种专用数据库类型，在人工智能应用中发挥着至关重要的作用。&lt;/p&gt;&lt;p&gt;在向量数据库中，查询方式与传统关系型数据库不同。向量数据库不进行精确匹配，而是执行相似度搜索。当传入一个向量作为查询条件时，向量数据库会返回与该查询向量“相似”的向量。关于相似度的高层级计算方式，将在&lt;strong&gt;向量相似度&lt;/strong&gt;部分进一步说明。&lt;/p&gt;&lt;p&gt;向量数据库用于将数据与人工智能模型进行集成。使用向量数据库的第一步是将数据加载到其中。之后，当用户查询需要发送至AI模型时，系统会先检索出一批相似文档。这些文档将作为用户问题的上下文，与用户查询一同发送给AI模型。该技术被称为检索增强生成（RAG）。&lt;/p&gt;&lt;p&gt;后续章节将介绍Spring AI中可适配多种向量数据库实现的接口，以及部分高层级使用示例。&lt;/p&gt;&lt;p&gt;最后一部分旨在揭秘向量数据库中相似度搜索的底层实现方式。&lt;/p&gt;&lt;h3&gt;接口概述&lt;/h3&gt;&lt;p&gt;本章节对Spring AI框架内的VectorStore接口及其相关类进行说明。&lt;/p&gt;&lt;p&gt;Spring AI通过VectorStore接口及其只读子接口VectorStoreRetriever，提供了一套可与向量数据库交互的抽象API。&lt;/p&gt;&lt;h4&gt;VectorStoreRetriever接口&lt;/h4&gt;&lt;p&gt;Spring AI提供了名为VectorStoreRetriever的只读接口，该接口仅暴露文档检索功能：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@FunctionalInterface
public&amp;nbsp;interface&amp;nbsp;VectorStoreRetriever&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;similaritySearch(SearchRequest&amp;nbsp;request);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;similaritySearch(String&amp;nbsp;query)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;this.similaritySearch(SearchRequest.builder().query(query).build());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;该函数式接口适用于仅需从向量存储库检索文档、无需执行任何修改操作的场景。它遵循最小权限原则，仅开放文档检索所需的必要功能。&lt;/p&gt;&lt;h4&gt;VectorStore接口&lt;/h4&gt;&lt;p&gt;VectorStore接口继承自VectorStoreRetriever，并新增了数据修改能力：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;interface&amp;nbsp;VectorStore&amp;nbsp;extends&amp;nbsp;DocumentWriter,&amp;nbsp;VectorStoreRetriever&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;String&amp;nbsp;getName()&amp;nbsp;{
		return&amp;nbsp;this.getClass().getSimpleName();
	}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;add(List&amp;lt;Document&amp;gt;&amp;nbsp;documents);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;delete(List&amp;lt;String&amp;gt;&amp;nbsp;idList);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void&amp;nbsp;delete(Filter.Expression&amp;nbsp;filterExpression);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;void&amp;nbsp;delete(String&amp;nbsp;filterExpression)&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;lt;T&amp;gt;&amp;nbsp;Optional&amp;lt;T&amp;gt;&amp;nbsp;getNativeClient()&amp;nbsp;{
		return&amp;nbsp;Optional.empty();
	}
}&lt;/pre&gt;&lt;p&gt;VectorStore接口整合了读写操作，支持在向量数据库中添加、删除和检索文档。&lt;/p&gt;&lt;h4&gt;SearchRequest构建器&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;class&amp;nbsp;SearchRequest&amp;nbsp;{

	public&amp;nbsp;static&amp;nbsp;final&amp;nbsp;double&amp;nbsp;SIMILARITY_THRESHOLD_ACCEPT_ALL&amp;nbsp;=&amp;nbsp;0.0;

	public&amp;nbsp;static&amp;nbsp;final&amp;nbsp;int&amp;nbsp;DEFAULT_TOP_K&amp;nbsp;=&amp;nbsp;4;

	private&amp;nbsp;String&amp;nbsp;query&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;;

	private&amp;nbsp;int&amp;nbsp;topK&amp;nbsp;=&amp;nbsp;DEFAULT_TOP_K;

	private&amp;nbsp;double&amp;nbsp;similarityThreshold&amp;nbsp;=&amp;nbsp;SIMILARITY_THRESHOLD_ACCEPT_ALL;

	@Nullable
	private&amp;nbsp;Filter.Expression&amp;nbsp;filterExpression;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;Builder&amp;nbsp;from(SearchRequest&amp;nbsp;originalSearchRequest)&amp;nbsp;{
		return&amp;nbsp;builder().query(originalSearchRequest.getQuery())
			.topK(originalSearchRequest.getTopK())
			.similarityThreshold(originalSearchRequest.getSimilarityThreshold())
			.filterExpression(originalSearchRequest.getFilterExpression());
	}

	public&amp;nbsp;static&amp;nbsp;class&amp;nbsp;Builder&amp;nbsp;{

		private&amp;nbsp;final&amp;nbsp;SearchRequest&amp;nbsp;searchRequest&amp;nbsp;=&amp;nbsp;new&amp;nbsp;SearchRequest();

		public&amp;nbsp;Builder&amp;nbsp;query(String&amp;nbsp;query)&amp;nbsp;{
			Assert.notNull(query,&amp;nbsp;&amp;quot;Query&amp;nbsp;can&amp;nbsp;not&amp;nbsp;be&amp;nbsp;null.&amp;quot;);
			this.searchRequest.query&amp;nbsp;=&amp;nbsp;query;
			return&amp;nbsp;this;
		}

		public&amp;nbsp;Builder&amp;nbsp;topK(int&amp;nbsp;topK)&amp;nbsp;{
			Assert.isTrue(topK&amp;nbsp;&amp;gt;=&amp;nbsp;0,&amp;nbsp;&amp;quot;TopK&amp;nbsp;should&amp;nbsp;be&amp;nbsp;positive.&amp;quot;);
			this.searchRequest.topK&amp;nbsp;=&amp;nbsp;topK;
			return&amp;nbsp;this;
		}

		public&amp;nbsp;Builder&amp;nbsp;similarityThreshold(double&amp;nbsp;threshold)&amp;nbsp;{
			Assert.isTrue(threshold&amp;nbsp;&amp;gt;=&amp;nbsp;0&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;threshold&amp;nbsp;向向量数据库插入数据时，需将数据封装为Document对象。Document类封装了PDF、Word等数据源中的文本内容，以字符串形式存储；同时以键值对形式保存元数据，例如文件名等信息。数据插入向量数据库时，文本内容会通过嵌入模型转换为数值数组（float[]），即向量嵌入。Word2Vec、GLoVE、BERT、OpenAI的text‑embedding‑ada‑002等嵌入模型，可将词语、句子或段落转换为这类向量嵌入。向量数据库的作用是存储向量嵌入并支持相似度检索，其本身不生成嵌入向量。生成向量嵌入需要使用EmbeddingModel。接口中的similaritySearch方法可检索与指定查询字符串相似的文档，可通过以下参数进行精细化配置：k：整数类型，指定返回的最大相似文档数量，常被称为Top‑K检索或K近邻（KNN）。threshold：0‑1之间的双精度数值，数值越接近1代表相似度越高。例如默认阈值设为0.75时，仅返回相似度高于该值的文档。Filter.Expression：用于传入流式领域特定语言（DSL）表达式，作用类似SQL的where子句，但仅作用于Document的元数据键值对。filterExpression：基于ANTLR4的外部DSL，支持传入字符串格式的过滤表达式。例如元数据包含国家、年份、是否启用等字段时，可使用表达式：country&amp;nbsp;==&amp;nbsp;&amp;#39;UK&amp;#39;&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;year&amp;nbsp;&amp;gt;=&amp;nbsp;2020&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;isActive&amp;nbsp;==&amp;nbsp;true。关于Filter.Expression的更多信息可参考元数据过滤章节。模式初始化部分向量存储库使用前需要初始化后端模式，默认不会自动初始化。需要手动开启：传入对应构造函数的布尔参数；若使用Spring&amp;nbsp;Boot，则在application.properties或application.yml中将initialize‑schema属性设为true。具体属性名需查阅对应向量存储库的文档。批处理策略使用向量存储库时，通常需要对大量文档进行嵌入处理。直接一次性调用接口处理全部文档看似简单，但会引发问题。嵌入模型以令牌为单位处理文本，存在最大令牌限制，即上下文窗口大小。该限制约束单次嵌入请求可处理的文本量，单次处理过多令牌会引发报错或嵌入向量截断。为解决令牌限制问题，Spring&amp;nbsp;AI实现了批处理策略。该策略将大量文档拆分为多个小批次，确保每批文本不超出嵌入模型的最大上下文窗口。批处理不仅解决令牌限制问题，还可提升性能、高效利用API调用频率限制。Spring&amp;nbsp;AI通过BatchingStrategy接口提供该功能，可根据文档令牌数量进行子批次处理。核心BatchingStrategy接口定义如下：public&amp;nbsp;interface&amp;nbsp;BatchingStrategy&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt;&amp;nbsp;batch(List&amp;lt;Document&amp;gt;&amp;nbsp;documents);
}该接口仅定义一个batch方法，接收文档列表，返回分批次后的文档集合。默认实现Spring&amp;nbsp;AI提供默认实现类TokenCountBatchingStrategy。该策略根据文档令牌数进行批处理，确保每批文档不超过计算后的最大输入令牌数。TokenCountBatchingStrategy核心特性：默认以OpenAI最大输入令牌数（8191）为上限。预留百分比（默认10%），用于应对潜在开销。实际最大输入令牌数计算公式：实际最大输入令牌数&amp;nbsp;=&amp;nbsp;原始最大输入令牌数&amp;nbsp;×&amp;nbsp;(1&amp;nbsp;−&amp;nbsp;预留百分比)。该策略会估算每个文档的令牌数，在不超过最大输入令牌数的前提下分组；若单个文档超出限制则抛出异常。可自定义TokenCountBatchingStrategy适配业务需求，在Spring&amp;nbsp;Boot的@Configuration类中创建自定义参数实例即可。自定义TokenCountBatchingStrategy&amp;nbsp;Bean示例：@Configuration
public&amp;nbsp;class&amp;nbsp;EmbeddingConfig&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;BatchingStrategy&amp;nbsp;customTokenCountBatchingStrategy()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;TokenCountBatchingStrategy(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EncodingType.CL100K_BASE,&amp;nbsp;&amp;nbsp;//&amp;nbsp;指定编码类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;8000,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;设置最大输入令牌数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0.1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;设置预留百分比
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}配置说明：EncodingType.CL100K_BASE：指定分词编码类型，JTokkitTokenCountEstimator使用该编码精准估算令牌数量。8000：设置最大输入令牌数，需小于等于嵌入模型的最大上下文窗口。0.1：设置预留百分比，从最大输入令牌数中预留部分额度，应对处理过程中令牌数增加的情况。默认构造函数使用Document.DEFAULT_CONTENT_FORMATTER格式化内容，MetadataMode.NONE处理元数据。如需自定义，可使用包含更多参数的完整构造函数。定义完成后，该自定义TokenCountBatchingStrategy&amp;nbsp;Bean会自动被应用内的EmbeddingModel实现类使用，替代默认策略。TokenCountBatchingStrategy内部使用TokenCountEstimator（具体为JTokkitTokenCountEstimator）计算令牌数以实现高效批处理，基于指定编码类型精准估算令牌数。此外，TokenCountBatchingStrategy支持传入自定义TokenCountEstimator接口实现类，适配个性化令牌统计策略。示例如下：TokenCountEstimator&amp;nbsp;customEstimator&amp;nbsp;=&amp;nbsp;new&amp;nbsp;YourCustomTokenCountEstimator();
TokenCountBatchingStrategy&amp;nbsp;strategy&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TokenCountBatchingStrategy(
		this.customEstimator,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;8000,&amp;nbsp;&amp;nbsp;//&amp;nbsp;最大输入令牌数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0.1,&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;预留百分比
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Document.DEFAULT_CONTENT_FORMATTER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MetadataMode.NONE
);自动截断处理部分嵌入模型（如Vertex&amp;nbsp;AI文本嵌入）支持auto_truncate特性。开启后，模型会静默截断超出最大长度的文本并继续处理；关闭后，超长输入会直接抛出异常。批处理策略搭配自动截断时，需将批处理策略的输入令牌数上限设置为远高于模型实际最大值，避免批处理策略对超长文档抛出异常，由嵌入模型内部完成截断。自动截断配置开启自动截断时，批处理策略的最大输入令牌数需远大于模型实际限制，避免批处理策略拦截超长文档，由嵌入模型内部处理截断。Vertex&amp;nbsp;AI开启自动截断、自定义批处理策略并适配PgVectorStore的配置示例：@Configuration
public&amp;nbsp;class&amp;nbsp;AutoTruncationEmbeddingConfig&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;VertexAiTextEmbeddingModel&amp;nbsp;vertexAiEmbeddingModel(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VertexAiEmbeddingConnectionDetails&amp;nbsp;connectionDetails)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VertexAiTextEmbeddingOptions&amp;nbsp;options&amp;nbsp;=&amp;nbsp;VertexAiTextEmbeddingOptions.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.model(VertexAiTextEmbeddingOptions.DEFAULT_MODEL_NAME)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.autoTruncate(true)&amp;nbsp;&amp;nbsp;//&amp;nbsp;开启自动截断
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;VertexAiTextEmbeddingModel(connectionDetails,&amp;nbsp;options);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;BatchingStrategy&amp;nbsp;batchingStrategy()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;仅在嵌入模型开启自动截断时使用超高令牌限制
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;设置远高于模型实际支持的令牌数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;例如Vertex&amp;nbsp;AI实际上限20000，此处设为132900
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;TokenCountBatchingStrategy(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EncodingType.CL100K_BASE,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;132900,&amp;nbsp;&amp;nbsp;//&amp;nbsp;人为设置的超高上限
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0.1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;10%预留额度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;VectorStore&amp;nbsp;vectorStore(JdbcTemplate&amp;nbsp;jdbcTemplate,&amp;nbsp;EmbeddingModel&amp;nbsp;embeddingModel,&amp;nbsp;BatchingStrategy&amp;nbsp;batchingStrategy)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;PgVectorStore.builder(jdbcTemplate,&amp;nbsp;embeddingModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;此处省略其他属性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}配置说明：嵌入模型开启自动截断，可平稳处理超长输入。批处理策略使用人为设置的超高令牌上限（132900），远大于模型实际限制（20000）。向量存储库使用配置好的嵌入模型和自定义批处理策略Bean。实现原理TokenCountBatchingStrategy会校验单个文档是否超出配置上限，超出则抛出IllegalArgumentException。批处理策略设置超高上限，可避免该校验触发异常。超出模型限制的文档或批次，由嵌入模型的自动截断功能静默截断并处理。最佳实践使用自动截断时：批处理策略最大输入令牌数至少设为模型实际上限的5‑10倍，避免提前触发批处理策略异常。监控嵌入模型的截断警告日志（部分模型无截断日志）。评估静默截断对嵌入质量的影响。使用样本文档测试，确保截断后的嵌入向量满足业务需求。记录该非常规配置，便于后续维护。自动截断虽可避免报错，但会生成不完整的嵌入向量，长文档末尾的关键信息可能丢失。若应用需要完整嵌入全部内容，需在嵌入前将文档拆分为更小片段。Spring&amp;nbsp;Boot自动配置使用Spring&amp;nbsp;Boot自动配置时，需自定义BatchingStrategy&amp;nbsp;Bean以覆盖Spring&amp;nbsp;AI默认实现：@Bean
public&amp;nbsp;BatchingStrategy&amp;nbsp;customBatchingStrategy()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该Bean将覆盖默认批处理策略
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;TokenCountBatchingStrategy(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EncodingType.CL100K_BASE,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;132900,&amp;nbsp;&amp;nbsp;//&amp;nbsp;远高于模型实际限制
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0.1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
}应用上下文内存在该Bean时，所有向量存储库会自动替换为该批处理策略。自定义实现TokenCountBatchingStrategy提供了稳健的默认实现，也可基于Spring&amp;nbsp;Boot自动配置自定义批处理策略。自定义方式为在Spring&amp;nbsp;Boot应用中定义BatchingStrategy&amp;nbsp;Bean：@Configuration
public&amp;nbsp;class&amp;nbsp;EmbeddingConfig&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;BatchingStrategy&amp;nbsp;customBatchingStrategy()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;CustomBatchingStrategy();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}应用内的EmbeddingModel实现类会自动使用该自定义BatchingStrategy。Spring&amp;nbsp;AI支持的向量存储库默认使用TokenCountBatchingStrategy，SAP&amp;nbsp;Hana向量存储库暂未配置批处理功能。VectorStore实现类以下为VectorStore接口的可用实现：Azure&amp;nbsp;Vector&amp;nbsp;Search&amp;nbsp;-&amp;nbsp;Azure向量存储库Apache&amp;nbsp;Cassandra&amp;nbsp;-&amp;nbsp;Apache&amp;nbsp;Cassandra向量存储库Chroma&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Chroma向量存储库Elasticsearch&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Elasticsearch向量存储库GemFire&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;GemFire向量存储库MariaDB&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;MariaDB向量存储库Milvus&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Milvus向量存储库MongoDB&amp;nbsp;Atlas&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;MongoDB&amp;nbsp;Atlas向量存储库Neo4j&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Neo4j向量存储库OpenSearch&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;OpenSearch向量存储库Oracle&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Oracle数据库向量存储库PgVector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;PostgreSQL/PGVector向量存储库Pinecone&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Pinecone向量存储库Qdrant&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Qdrant向量存储库Redis&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Redis向量存储库SAP&amp;nbsp;Hana&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;SAP&amp;nbsp;HANA向量存储库Typesense&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Typesense向量存储库Weaviate&amp;nbsp;Vector&amp;nbsp;Store&amp;nbsp;-&amp;nbsp;Weaviate向量存储库SimpleVectorStore&amp;nbsp;-&amp;nbsp;简易向量存储实现，仅用于测试SimpleVectorStore不可用于生产环境，仅适用于测试或演示场景。后续版本可能支持更多实现类。若需Spring&amp;nbsp;AI支持其他向量数据库，可在GitHub提交Issue，或直接提交实现代码的Pull&amp;nbsp;Request。各VectorStore实现类的详细信息见本章子章节。使用示例为向量数据库计算嵌入向量时，需选择与上层AI模型匹配的嵌入模型。例如使用OpenAI的ChatGPT时，需搭配OpenAiEmbeddingModel及text‑embedding‑ada‑002模型。Spring&amp;nbsp;Boot&amp;nbsp;Starter的OpenAI自动配置会在Spring应用上下文注入EmbeddingModel实现类，支持依赖注入。向向量存储库写入数据批量向向量存储库加载数据时，先将数据封装为Spring&amp;nbsp;AI的Document类，再调用VectorStore接口的add方法。以JSON源文件为例，使用Spring&amp;nbsp;AI的JsonReader读取JSON中指定字段，拆分为小片段后传入向量存储库；向量存储库计算嵌入向量，并将JSON数据与嵌入向量存入数据库：@Autowired
VectorStore&amp;nbsp;vectorStore;

void&amp;nbsp;load(String&amp;nbsp;sourceFile)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JsonReader&amp;nbsp;jsonReader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;JsonReader(new&amp;nbsp;FileSystemResource(sourceFile),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;price&amp;quot;,&amp;nbsp;&amp;quot;name&amp;quot;,&amp;nbsp;&amp;quot;shortDescription&amp;quot;,&amp;nbsp;&amp;quot;description&amp;quot;,&amp;nbsp;&amp;quot;tags&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;jsonReader.get();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.vectorStore.add(documents);
}从向量存储库读取数据用户问题传入AI模型时，系统会执行相似度检索获取相似文档，作为上下文填充至提示词中。只读操作可使用VectorStore接口或更精简的VectorStoreRetriever接口：@Autowired
VectorStoreRetriever&amp;nbsp;retriever;&amp;nbsp;//&amp;nbsp;此处也可使用VectorStore

String&amp;nbsp;question&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;lt;用户的问题&amp;gt;&amp;quot;;
List&amp;lt;Document&amp;gt;&amp;nbsp;similarDocuments&amp;nbsp;=&amp;nbsp;retriever.similaritySearch(question);

//&amp;nbsp;或使用更精细的检索参数
SearchRequest&amp;nbsp;request&amp;nbsp;=&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(question)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(5)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;返回前5条结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(0.7)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;仅返回相似度≥0.7的结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

List&amp;lt;Document&amp;gt;&amp;nbsp;filteredDocuments&amp;nbsp;=&amp;nbsp;retriever.similaritySearch(request);可通过similaritySearch方法传入额外参数，指定检索文档数量及相似度阈值。读写操作分离分离的接口可清晰区分需要写入权限和仅需读取权限的组件：//&amp;nbsp;拥有完整权限、执行写入操作的服务
@Service
class&amp;nbsp;DocumentIndexer&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;VectorStore&amp;nbsp;vectorStore;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DocumentIndexer(VectorStore&amp;nbsp;vectorStore)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.vectorStore&amp;nbsp;=&amp;nbsp;vectorStore;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;indexDocuments(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectorStore.add(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;仅需检索、执行只读操作的服务
@Service
class&amp;nbsp;DocumentRetriever&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;VectorStoreRetriever&amp;nbsp;retriever;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DocumentRetriever(VectorStoreRetriever&amp;nbsp;retriever)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.retriever&amp;nbsp;=&amp;nbsp;retriever;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;findSimilar(String&amp;nbsp;query)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;retriever.similaritySearch(query);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}该关注点分离方式仅为真正需要的组件开放修改权限，便于构建更易维护、安全性更高的应用。基于VectorStoreRetriever的检索操作VectorStoreRetriever接口提供向量存储库的只读视图，仅开放相似度检索功能。该接口遵循最小权限原则，尤其适用于RAG（检索增强生成）应用——仅需检索文档、无需修改底层数据。使用VectorStoreRetriever的优势：关注点分离：清晰区分读写操作。接口隔离：仅需检索的客户端不会暴露修改方法。函数式接口：简单场景可通过Lambda表达式或方法引用实现。降低依赖：仅检索的组件无需依赖完整的VectorStore接口。使用示例仅需执行相似度检索时，可直接使用VectorStoreRetriever：@Service
public&amp;nbsp;class&amp;nbsp;DocumentRetrievalService&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;VectorStoreRetriever&amp;nbsp;retriever;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;DocumentRetrievalService(VectorStoreRetriever&amp;nbsp;retriever)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.retriever&amp;nbsp;=&amp;nbsp;retriever;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;findSimilarDocuments(String&amp;nbsp;query)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;retriever.similaritySearch(query);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;findSimilarDocumentsWithFilters(String&amp;nbsp;query,&amp;nbsp;String&amp;nbsp;country)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SearchRequest&amp;nbsp;request&amp;nbsp;=&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(query)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression(&amp;quot;country&amp;nbsp;==&amp;nbsp;&amp;#39;&amp;quot;&amp;nbsp;+&amp;nbsp;country&amp;nbsp;+&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;retriever.similaritySearch(request);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}该示例中，服务仅依赖VectorStoreRetriever接口，明确仅执行检索操作、不修改向量存储库。与RAG应用集成VectorStoreRetriever接口在RAG应用中尤为实用，可检索相关文档为AI模型提供上下文：@Service
public&amp;nbsp;class&amp;nbsp;RagService&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;VectorStoreRetriever&amp;nbsp;retriever;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;ChatModel&amp;nbsp;chatModel;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;RagService(VectorStoreRetriever&amp;nbsp;retriever,&amp;nbsp;ChatModel&amp;nbsp;chatModel)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.retriever&amp;nbsp;=&amp;nbsp;retriever;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.chatModel&amp;nbsp;=&amp;nbsp;chatModel;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;generateResponse(String&amp;nbsp;userQuery)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;检索相关文档
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;relevantDocs&amp;nbsp;=&amp;nbsp;retriever.similaritySearch(userQuery);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;提取文档内容作为上下文
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;context&amp;nbsp;=&amp;nbsp;relevantDocs.stream()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(Document::getContent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.collect(Collectors.joining(&amp;quot;\n\n&amp;quot;));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;基于检索上下文生成响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;prompt&amp;nbsp;=&amp;nbsp;&amp;quot;上下文信息：\n&amp;quot;&amp;nbsp;+&amp;nbsp;context&amp;nbsp;+&amp;nbsp;&amp;quot;\n\n用户查询：&amp;quot;&amp;nbsp;+&amp;nbsp;userQuery;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;chatModel.generate(prompt);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}该模式可实现RAG应用中检索组件与生成组件的清晰分离。元数据过滤本章节介绍可用于过滤查询结果的各类筛选方式。字符串过滤表达式可向similaritySearch重载方法传入类SQL的字符串过滤表达式。示例如下：&amp;quot;country&amp;nbsp;==&amp;nbsp;&amp;#39;BG&amp;#39;&amp;quot;&amp;quot;genre&amp;nbsp;==&amp;nbsp;&amp;#39;drama&amp;#39;&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;year&amp;nbsp;&amp;gt;=&amp;nbsp;2020&amp;quot;&amp;quot;genre&amp;nbsp;in&amp;nbsp;[&amp;#39;comedy&amp;#39;,&amp;nbsp;&amp;#39;documentary&amp;#39;,&amp;nbsp;&amp;#39;drama&amp;#39;]&amp;quot;Filter.Expression可通过FilterExpressionBuilder的流式API创建Filter.Expression实例。简单示例如下：FilterExpressionBuilder&amp;nbsp;b&amp;nbsp;=&amp;nbsp;new&amp;nbsp;FilterExpressionBuilder();
Expression&amp;nbsp;expression&amp;nbsp;=&amp;nbsp;this.b.eq(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;BG&amp;quot;).build();可使用以下运算符构建复杂表达式：等于：==、减：‑、加：+、大于：&amp;gt;、大于等于：&amp;gt;=、小于：&amp;lt;、小于等于：&amp;lt;=、不等于：!=可通过以下运算符组合表达式：与：AND/and/&amp;amp;&amp;amp;；或：OR/or/||示例：Expression&amp;nbsp;exp&amp;nbsp;=&amp;nbsp;b.and(b.eq(&amp;quot;genre&amp;quot;,&amp;nbsp;&amp;quot;drama&amp;quot;),&amp;nbsp;b.gte(&amp;quot;year&amp;quot;,&amp;nbsp;2020)).build();还支持以下运算符：包含：IN/in；不包含：NIN/nin；非：NOT/not示例：Expression&amp;nbsp;exp&amp;nbsp;=&amp;nbsp;b.and(b.in(&amp;quot;genre&amp;quot;,&amp;nbsp;&amp;quot;drama&amp;quot;,&amp;nbsp;&amp;quot;documentary&amp;quot;),&amp;nbsp;b.not(b.lt(&amp;quot;year&amp;quot;,&amp;nbsp;2020))).build();还支持以下运算符：为空：IS/is；空值：NULL/null；非空：NOT&amp;nbsp;NULL/not&amp;nbsp;null示例：Expression&amp;nbsp;exp&amp;nbsp;=&amp;nbsp;b.and(b.isNull(&amp;quot;year&amp;quot;)).build();
Expression&amp;nbsp;exp&amp;nbsp;=&amp;nbsp;b.and(b.isNotNull(&amp;quot;year&amp;quot;)).build();并非所有向量存储库均已实现IS&amp;nbsp;NULL和NOT&amp;nbsp;NULL功能。从向量存储库删除文档Vector&amp;nbsp;Store接口提供多种文档删除方法，可根据指定文档ID或过滤表达式删除数据。根据文档ID删除最简单的删除方式为传入文档ID列表：void&amp;nbsp;delete(List&amp;lt;String&amp;gt;&amp;nbsp;idList);该方法删除所有ID匹配列表的文档；列表中不存在的ID会被忽略。使用示例：//&amp;nbsp;创建并添加文档
Document&amp;nbsp;document&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;世界很大&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;Netherlands&amp;quot;));
vectorStore.add(List.of(document));

//&amp;nbsp;根据ID删除文档
vectorStore.delete(List.of(document.getId()));根据过滤表达式删除复杂删除条件可使用过滤表达式：void&amp;nbsp;delete(Filter.Expression&amp;nbsp;filterExpression);该方法接收Filter.Expression对象，定义待删除文档的筛选条件，适用于基于元数据属性删除文档的场景。使用示例：//&amp;nbsp;创建带不同元数据的测试文档
Document&amp;nbsp;bgDocument&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;世界很大&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;Bulgaria&amp;quot;));
Document&amp;nbsp;nlDocument&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;世界很大&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;Netherlands&amp;quot;));

//&amp;nbsp;向存储库添加文档
vectorStore.add(List.of(bgDocument,&amp;nbsp;nlDocument));

//&amp;nbsp;通过过滤表达式删除保加利亚相关文档
Filter.Expression&amp;nbsp;filterExpression&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Filter.Expression(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Filter.ExpressionType.EQ,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Key(&amp;quot;country&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Value(&amp;quot;Bulgaria&amp;quot;)
);
vectorStore.delete(filterExpression);

//&amp;nbsp;检索验证删除结果
SearchRequest&amp;nbsp;request&amp;nbsp;=&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(&amp;quot;世界&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression(&amp;quot;country&amp;nbsp;==&amp;nbsp;&amp;#39;Bulgaria&amp;#39;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
List&amp;lt;Document&amp;gt;&amp;nbsp;results&amp;nbsp;=&amp;nbsp;vectorStore.similaritySearch(request);
//&amp;nbsp;结果为空，保加利亚相关文档已被删除根据字符串过滤表达式删除为方便使用，也可传入字符串格式的过滤表达式删除文档：void&amp;nbsp;delete(String&amp;nbsp;filterExpression);该方法内部将字符串过滤条件转换为Filter.Expression对象，适用于已有字符串格式筛选条件的场景。使用示例：//&amp;nbsp;创建并添加文档
Document&amp;nbsp;bgDocument&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;世界很大&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;Bulgaria&amp;quot;));
Document&amp;nbsp;nlDocument&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;世界很大&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(&amp;quot;country&amp;quot;,&amp;nbsp;&amp;quot;Netherlands&amp;quot;));
vectorStore.add(List.of(bgDocument,&amp;nbsp;nlDocument));

//&amp;nbsp;通过字符串过滤删除保加利亚相关文档
vectorStore.delete(&amp;quot;country&amp;nbsp;==&amp;nbsp;&amp;#39;Bulgaria&amp;#39;&amp;quot;);

//&amp;nbsp;检索验证剩余文档
SearchRequest&amp;nbsp;request&amp;nbsp;=&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(&amp;quot;世界&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
List&amp;lt;Document&amp;gt;&amp;nbsp;results&amp;nbsp;=&amp;nbsp;vectorStore.similaritySearch(request);
//&amp;nbsp;结果仅包含荷兰相关文档删除API异常处理所有删除方法执行出错时均可能抛出异常。最佳实践为将删除操作包裹在try‑catch代码块中：try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectorStore.delete(&amp;quot;country&amp;nbsp;==&amp;nbsp;&amp;#39;Bulgaria&amp;#39;&amp;quot;);
}
catch&amp;nbsp;(Exception&amp;nbsp;&amp;nbsp;e)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.error(&amp;quot;无效的过滤表达式&amp;quot;,&amp;nbsp;e);
}文档版本管理场景常见场景为文档版本管理：上传新版本文档的同时删除旧版本。可通过过滤表达式实现，示例如下：//&amp;nbsp;创建带版本元数据的初始文档（v1）
Document&amp;nbsp;documentV1&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;人工智能与机器学习最佳实践&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;docId&amp;quot;,&amp;nbsp;&amp;quot;AIML-001&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;version&amp;quot;,&amp;nbsp;&amp;quot;1.0&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;lastUpdated&amp;quot;,&amp;nbsp;&amp;quot;2024-01-01&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
);

//&amp;nbsp;将v1加入向量存储库
vectorStore.add(List.of(documentV1));

//&amp;nbsp;创建同一文档的更新版本（v2）
Document&amp;nbsp;documentV2&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;人工智能与机器学习最佳实践——更新版&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;docId&amp;quot;,&amp;nbsp;&amp;quot;AIML-001&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;version&amp;quot;,&amp;nbsp;&amp;quot;2.0&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;lastUpdated&amp;quot;,&amp;nbsp;&amp;quot;2024-02-01&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
);

//&amp;nbsp;先通过过滤表达式删除旧版本
Filter.Expression&amp;nbsp;deleteOldVersion&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Filter.Expression(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Filter.ExpressionType.AND,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Arrays.asList(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Expression(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Filter.ExpressionType.EQ,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Key(&amp;quot;docId&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Value(&amp;quot;AIML-001&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Expression(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Filter.ExpressionType.EQ,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Key(&amp;quot;version&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;Filter.Value(&amp;quot;1.0&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
);
vectorStore.delete(deleteOldVersion);

//&amp;nbsp;添加新版本
vectorStore.add(List.of(documentV2));

//&amp;nbsp;验证仅存在v2版本
SearchRequest&amp;nbsp;request&amp;nbsp;=&amp;nbsp;SearchRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.query(&amp;quot;人工智能与机器学习&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression(&amp;quot;docId&amp;nbsp;==&amp;nbsp;&amp;#39;AIML-001&amp;#39;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
List&amp;lt;Document&amp;gt;&amp;nbsp;results&amp;nbsp;=&amp;nbsp;vectorStore.similaritySearch(request);
//&amp;nbsp;结果仅包含文档v2版本也可通过字符串过滤表达式实现相同效果：//&amp;nbsp;通过字符串过滤删除旧版本
vectorStore.delete(&amp;quot;docId&amp;nbsp;==&amp;nbsp;&amp;#39;AIML-001&amp;#39;&amp;nbsp;AND&amp;nbsp;version&amp;nbsp;==&amp;nbsp;&amp;#39;1.0&amp;#39;&amp;quot;);

//&amp;nbsp;添加新版本
vectorStore.add(List.of(documentV2));删除文档的性能考量明确待删除文档ID时，按ID列表删除通常速度更快。基于过滤条件删除可能需要扫描索引匹配文档，具体取决于向量存储库实现。大批量删除操作建议分批执行，避免系统负载过高。基于文档属性删除时，优先使用过滤表达式，而非先收集ID再删除。&lt;/pre&gt;</description><pubDate>Wed, 20 May 2026 16:20:00 +0800</pubDate></item><item><title>Spring AI 评估测试</title><link>https://catnav.cn/post/86.html</link><description>&lt;h2&gt;&lt;br/&gt;&lt;/h2&gt;&lt;p&gt;测试人工智能应用需要对生成的内容进行评估，以确保人工智能模型没有产生幻觉式的响应。&lt;/p&gt;&lt;p&gt;评估响应的一种方法是使用人工智能模型本身进行评估。选择最适合评估的人工智能模型，该模型可能与生成响应所用的模型不同。&lt;/p&gt;&lt;p&gt;Spring AI 中用于评估响应的接口是 Evaluator，其定义如下：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@FunctionalInterface
public&amp;nbsp;interface&amp;nbsp;Evaluator&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EvaluationResponse&amp;nbsp;evaluate(EvaluationRequest&amp;nbsp;evaluationRequest);
}&lt;/pre&gt;&lt;p&gt;评估的输入是 EvaluationRequest，其定义如下：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;class&amp;nbsp;EvaluationRequest&amp;nbsp;{

	private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;userText;

	private&amp;nbsp;final&amp;nbsp;ListdataList;

	private&amp;nbsp;final&amp;nbsp;String&amp;nbsp;responseContent;

	public&amp;nbsp;EvaluationRequest(String&amp;nbsp;userText,&amp;nbsp;ListdataList,&amp;nbsp;String&amp;nbsp;responseContent)&amp;nbsp;{
		this.userText&amp;nbsp;=&amp;nbsp;userText;
		this.dataList&amp;nbsp;=&amp;nbsp;dataList;
		this.responseContent&amp;nbsp;=&amp;nbsp;responseContent;
	}

&amp;nbsp;&amp;nbsp;...
}&lt;/pre&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;userText：用户输入的原始文本字符串&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;dataList：附加到原始输入的上下文数据，例如来自检索增强生成的数据&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;responseContent：人工智能模型生成的响应内容字符串&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;相关性评估器&lt;/h3&gt;&lt;p&gt;RelevancyEvaluator 是 Evaluator 接口的实现类，用于评估人工智能生成的响应与所提供上下文的相关性。该评估器通过判断人工智能模型的响应在检索上下文的前提下是否与用户输入相关，来评估检索增强生成流程的质量。&lt;/p&gt;&lt;p&gt;评估基于用户输入、人工智能模型的响应和上下文信息。它使用提示模板询问人工智能模型，响应是否与用户输入和上下文相关。&lt;/p&gt;&lt;p&gt;以下是 RelevancyEvaluator 使用的默认提示模板：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Your&amp;nbsp;task&amp;nbsp;is&amp;nbsp;to&amp;nbsp;evaluate&amp;nbsp;if&amp;nbsp;the&amp;nbsp;response&amp;nbsp;for&amp;nbsp;the&amp;nbsp;query
is&amp;nbsp;in&amp;nbsp;line&amp;nbsp;with&amp;nbsp;the&amp;nbsp;context&amp;nbsp;information&amp;nbsp;provided.

You&amp;nbsp;have&amp;nbsp;two&amp;nbsp;options&amp;nbsp;to&amp;nbsp;answer.&amp;nbsp;Either&amp;nbsp;YES&amp;nbsp;or&amp;nbsp;NO.

Answer&amp;nbsp;YES,&amp;nbsp;if&amp;nbsp;the&amp;nbsp;response&amp;nbsp;for&amp;nbsp;the&amp;nbsp;query
is&amp;nbsp;in&amp;nbsp;line&amp;nbsp;with&amp;nbsp;context&amp;nbsp;information&amp;nbsp;otherwise&amp;nbsp;NO.

Query:
{query}

Response:
{response}

Context:
{context}

Answer:&lt;/pre&gt;&lt;p&gt;你可以通过 .promptTemplate() 建造者方法提供自定义的 PromptTemplate 对象来定制提示模板。详情参见自定义模板部分。&lt;/p&gt;&lt;h4&gt;集成测试中的用法&lt;/h4&gt;&lt;p&gt;以下是在集成测试中使用 RelevancyEvaluator 的示例，通过 RetrievalAugmentationAdvisor 验证检索增强生成流程的结果：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Test
void&amp;nbsp;evaluateRelevancy()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;question&amp;nbsp;=&amp;nbsp;&amp;quot;Where&amp;nbsp;does&amp;nbsp;the&amp;nbsp;adventure&amp;nbsp;of&amp;nbsp;Anacletus&amp;nbsp;and&amp;nbsp;Birba&amp;nbsp;take&amp;nbsp;place?&amp;quot;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RetrievalAugmentationAdvisor&amp;nbsp;ragAdvisor&amp;nbsp;=&amp;nbsp;RetrievalAugmentationAdvisor.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.documentRetriever(VectorStoreDocumentRetriever.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStore(pgVectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ChatResponse&amp;nbsp;chatResponse&amp;nbsp;=&amp;nbsp;ChatClient.builder(chatModel).build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.prompt(question)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(ragAdvisor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatResponse();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EvaluationRequest&amp;nbsp;evaluationRequest&amp;nbsp;=&amp;nbsp;new&amp;nbsp;EvaluationRequest(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;用户的原始问题
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;question,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;从检索增强生成流程中检索到的上下文
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chatResponse.getMetadata().get(RetrievalAugmentationAdvisor.DOCUMENT_CONTEXT),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;人工智能模型的响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chatResponse.getResult().getOutput().getText()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RelevancyEvaluator&amp;nbsp;evaluator&amp;nbsp;=&amp;nbsp;new&amp;nbsp;RelevancyEvaluator(ChatClient.builder(chatModel));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EvaluationResponse&amp;nbsp;evaluationResponse&amp;nbsp;=&amp;nbsp;evaluator.evaluate(evaluationRequest);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;assertThat(evaluationResponse.isPass()).isTrue();
}&lt;/pre&gt;&lt;p&gt;你可以在 Spring AI 项目中找到多个使用 RelevancyEvaluator 测试 QuestionAnswerAdvisor（查看测试）和 RetrievalAugmentationAdvisor（查看测试）功能的集成测试。&lt;/p&gt;&lt;h4&gt;自定义模板&lt;/h4&gt;&lt;p&gt;RelevancyEvaluator 使用默认模板提示人工智能模型进行评估。你可以通过 .promptTemplate() 建造者方法提供自定义的 PromptTemplate 对象来定制该行为。&lt;/p&gt;&lt;p&gt;自定义 PromptTemplate 可以使用任意 TemplateRenderer 实现（默认使用基于 StringTemplate 引擎的 StPromptTemplate）。重要的要求是，模板必须包含以下占位符：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;query 占位符：用于接收用户问题&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;response 占位符：用于接收人工智能模型的响应&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;context 占位符：用于接收上下文信息&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;事实核查评估器&lt;/h3&gt;&lt;p&gt;FactCheckingEvaluator 是 Evaluator 接口的另一个实现类，用于评估人工智能生成的响应相对于所提供上下文的事实准确性。该评估器通过验证给定陈述（主张）是否有所提供的上下文（文档）的逻辑支持，帮助检测并减少人工智能输出中的幻觉现象。&lt;/p&gt;&lt;p&gt;评估时会将「主张」和「文档」提供给人工智能模型。目前已有更小、更高效的专用模型用于此任务，例如 Bespoke 的 Minicheck，与 GPT-4 等旗舰模型相比，它能降低执行此类核查的成本。Minicheck 也可通过 Ollama 使用。&lt;/p&gt;&lt;h4&gt;用法&lt;/h4&gt;&lt;p&gt;FactCheckingEvaluator 构造方法接收一个 ChatClient.Builder 作为参数：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;FactCheckingEvaluator(ChatClient.Builder&amp;nbsp;chatClientBuilder)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;this.chatClientBuilder&amp;nbsp;=&amp;nbsp;chatClientBuilder;
}&lt;/pre&gt;&lt;p&gt;该评估器使用以下提示模板进行事实核查：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Document:&amp;nbsp;{document}
Claim:&amp;nbsp;{claim}&lt;/pre&gt;&lt;p&gt;其中 {document} 是上下文信息，{claim} 是待评估的人工智能模型响应。&lt;/p&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;p&gt;以下是如何将 FactCheckingEvaluator 与基于 Ollama 的 ChatModel（特指 Bespoke-Minicheck 模型）结合使用的示例：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Test
void&amp;nbsp;testFactChecking()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;//&amp;nbsp;设置&amp;nbsp;Ollama&amp;nbsp;API
&amp;nbsp;&amp;nbsp;OllamaApi&amp;nbsp;ollamaApi&amp;nbsp;=&amp;nbsp;new&amp;nbsp;OllamaApi(&amp;quot;http://localhost:11434&amp;quot;);

&amp;nbsp;&amp;nbsp;ChatModel&amp;nbsp;chatModel&amp;nbsp;=&amp;nbsp;new&amp;nbsp;OllamaChatModel(ollamaApi,
				OllamaChatOptions.builder().model(BESPOKE_MINICHECK).numPredict(2).temperature(0.0d).build())


&amp;nbsp;&amp;nbsp;//&amp;nbsp;创建事实核查评估器
&amp;nbsp;&amp;nbsp;var&amp;nbsp;factCheckingEvaluator&amp;nbsp;=&amp;nbsp;new&amp;nbsp;FactCheckingEvaluator(ChatClient.builder(chatModel));

&amp;nbsp;&amp;nbsp;//&amp;nbsp;示例上下文和主张
&amp;nbsp;&amp;nbsp;String&amp;nbsp;context&amp;nbsp;=&amp;nbsp;&amp;quot;The&amp;nbsp;Earth&amp;nbsp;is&amp;nbsp;the&amp;nbsp;third&amp;nbsp;planet&amp;nbsp;from&amp;nbsp;the&amp;nbsp;Sun&amp;nbsp;and&amp;nbsp;the&amp;nbsp;only&amp;nbsp;astronomical&amp;nbsp;object&amp;nbsp;known&amp;nbsp;to&amp;nbsp;harbor&amp;nbsp;life.&amp;quot;;
&amp;nbsp;&amp;nbsp;String&amp;nbsp;claim&amp;nbsp;=&amp;nbsp;&amp;quot;The&amp;nbsp;Earth&amp;nbsp;is&amp;nbsp;the&amp;nbsp;fourth&amp;nbsp;planet&amp;nbsp;from&amp;nbsp;the&amp;nbsp;Sun.&amp;quot;;

&amp;nbsp;&amp;nbsp;//&amp;nbsp;创建评估请求
&amp;nbsp;&amp;nbsp;EvaluationRequest&amp;nbsp;evaluationRequest&amp;nbsp;=&amp;nbsp;new&amp;nbsp;EvaluationRequest(context,&amp;nbsp;Collections.emptyList(),&amp;nbsp;claim);

&amp;nbsp;&amp;nbsp;//&amp;nbsp;执行评估
&amp;nbsp;&amp;nbsp;EvaluationResponse&amp;nbsp;evaluationResponse&amp;nbsp;=&amp;nbsp;factCheckingEvaluator.evaluate(evaluationRequest);

&amp;nbsp;&amp;nbsp;assertFalse(evaluationResponse.isPass(),&amp;nbsp;&amp;quot;The&amp;nbsp;claim&amp;nbsp;should&amp;nbsp;not&amp;nbsp;be&amp;nbsp;supported&amp;nbsp;by&amp;nbsp;the&amp;nbsp;context&amp;quot;);

}&lt;/pre&gt;</description><pubDate>Wed, 20 May 2026 16:07:58 +0800</pubDate></item><item><title>Spring AI ETL 流水线</title><link>https://catnav.cn/post/85.html</link><description>&lt;p&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;抽取、转换与加载（ETL）框架是检索增强生成（RAG）应用场景中数据处理的核心支柱。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;ETL 流水线统筹管理从原始数据源到结构化向量库的数据流，确保数据以最优格式供 AI 模型检索使用。&lt;/p&gt;&lt;p&gt;RAG 应用场景通过从数据集中检索相关信息来增强生成式模型的能力，以此提升生成输出的质量和相关性。&lt;/p&gt;&lt;h2&gt;API 概述&lt;/h2&gt;&lt;p&gt;ETL 流水线负责创建、转换并存储文档实例。&lt;/p&gt;&lt;h3&gt;Spring AI 消息 API&lt;/h3&gt;&lt;p&gt;文档类包含文本、元数据，以及可选的图片、音频、视频等其他媒体类型。&lt;/p&gt;&lt;p&gt;ETL 流水线包含三大核心组件：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;文档读取器，实现了 Supplier&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt; 接口&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;文档转换器，实现了 Function&amp;lt;List&amp;lt;Document&amp;gt;, List&amp;lt;Document&amp;gt;&amp;gt; 接口&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;文档写入器，实现了 Consumer&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt; 接口&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;借助文档读取器，可从 PDF、文本文件及其他文档类型中创建文档类的内容。&lt;/p&gt;&lt;p&gt;构建一条简单的 ETL 流水线，你可以将这三类组件的实例串联使用。&lt;/p&gt;&lt;h3&gt;ETL 流水线&lt;/h3&gt;&lt;p&gt;假设我们拥有以下三种 ETL 类型的实例：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;分页 PDF 文档读取器：文档读取器的一种实现&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;令牌文本分割器：文档转换器的一种实现&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;向量库：文档写入器的一种实现&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;若要按照检索增强生成模式将数据基础加载到向量数据库中，可使用以下 Java 函数式语法代码：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));&lt;/pre&gt;&lt;p&gt;你也可以使用更贴合业务领域的自然表达方法名：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;vectorStore.write(tokenTextSplitter.split(pdfReader.read()));&lt;/pre&gt;&lt;h2&gt;ETL 接口&lt;/h2&gt;&lt;p&gt;ETL 流水线由以下接口和实现类组成。详细的 ETL 类图见 ETL 类图章节。&lt;/p&gt;&lt;h3&gt;文档读取器&lt;/h3&gt;&lt;p&gt;从多种来源提供文档数据。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;interface&amp;nbsp;DocumentReader&amp;nbsp;extends&amp;nbsp;Supplier&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt;&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;read()&amp;nbsp;{
		return&amp;nbsp;get();
	}
}&lt;/pre&gt;&lt;h3&gt;文档转换器&lt;/h3&gt;&lt;p&gt;作为处理工作流的一部分，批量转换文档。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;interface&amp;nbsp;DocumentTransformer&amp;nbsp;extends&amp;nbsp;Function&amp;lt;List&amp;lt;Document&amp;gt;,&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;gt;&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;transform(List&amp;lt;Document&amp;gt;&amp;nbsp;transform)&amp;nbsp;{
		return&amp;nbsp;apply(transform);
	}
}&lt;/pre&gt;&lt;h3&gt;文档写入器&lt;/h3&gt;&lt;p&gt;管理 ETL 流程的最终阶段，为文档存储做准备。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;interface&amp;nbsp;DocumentWriter&amp;nbsp;extends&amp;nbsp;Consumer&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt;&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;void&amp;nbsp;write(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
		accept(documents);
	}
}&lt;/pre&gt;&lt;h2&gt;文档读取器&lt;/h2&gt;&lt;h3&gt;JSON 格式&lt;/h3&gt;&lt;p&gt;JSON 读取器用于处理 JSON 文档，将其转换为文档对象列表。&lt;/p&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyJsonReader&amp;nbsp;{

	private&amp;nbsp;final&amp;nbsp;Resource&amp;nbsp;resource;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MyJsonReader(@Value(&amp;quot;classpath:bikes.json&amp;quot;)&amp;nbsp;Resource&amp;nbsp;resource)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.resource&amp;nbsp;=&amp;nbsp;resource;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

	List&amp;lt;Document&amp;gt;&amp;nbsp;loadJsonAsDocuments()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JsonReader&amp;nbsp;jsonReader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;JsonReader(this.resource,&amp;nbsp;&amp;quot;description&amp;quot;,&amp;nbsp;&amp;quot;content&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;jsonReader.get();
	}
}&lt;/pre&gt;&lt;h4&gt;构造方法选项&lt;/h4&gt;&lt;p&gt;JSON 读取器提供多种构造方法：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;JsonReader(Resource resource)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;JsonReader(Resource resource, String… jsonKeysToUse)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;JsonReader(Resource resource, JsonMetadataGenerator jsonMetadataGenerator, String… jsonKeysToUse)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;参数&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;resource：指向 JSON 文件的 Spring 资源对象&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;jsonKeysToUse：JSON 中需作为结果文档对象文本内容的键数组&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;jsonMetadataGenerator：可选的 JSON 元数据生成器，用于为每个文档创建元数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;JSON 读取器处理 JSON 内容的规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;可同时处理 JSON 数组和单个 JSON 对象&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;对每个 JSON 对象（数组内或单个对象）：&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;根据指定的 jsonKeysToUse 提取内容&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若未指定键，则使用整个 JSON 对象作为内容&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;通过提供的 JSON 元数据生成器生成元数据（未提供则生成空元数据）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;使用提取的内容和元数据创建文档对象&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;使用 JSON 指针&lt;/h4&gt;&lt;p&gt;JSON 读取器现已支持通过 JSON 指针获取 JSON 文档的指定部分。该功能可轻松从复杂 JSON 结构中提取嵌套数据。&lt;/p&gt;&lt;p&gt;get(String pointer) 方法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;get(String&amp;nbsp;pointer)&lt;/pre&gt;&lt;p&gt;该方法允许使用 JSON 指针获取 JSON 文档的指定部分。&lt;/p&gt;&lt;h4&gt;参数&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;pointer：符合 RFC 6901 规范的 JSON 指针字符串，用于定位 JSON 结构中的目标元素&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;返回值&lt;/h4&gt;&lt;p&gt;返回由 JSON 指针定位元素解析得到的文档列表。&lt;/p&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;该方法通过传入的 JSON 指针导航至 JSON 结构的指定位置&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若指针有效且指向存在的元素：&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;JSON 对象：返回包含单个文档的列表&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;JSON 数组：返回与数组元素一一对应的文档列表&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若指针无效或指向不存在的元素，抛出非法参数异常&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;JsonReader&amp;nbsp;jsonReader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;JsonReader(resource,&amp;nbsp;&amp;quot;description&amp;quot;);
List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;this.jsonReader.get(&amp;quot;/store/books/0&amp;quot;);&lt;/pre&gt;&lt;h4&gt;示例 JSON 结构&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;[
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;id&amp;quot;:&amp;nbsp;1,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;&amp;quot;Trek&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;description&amp;quot;:&amp;nbsp;&amp;quot;A&amp;nbsp;high-performance&amp;nbsp;mountain&amp;nbsp;bike&amp;nbsp;for&amp;nbsp;trail&amp;nbsp;riding.&amp;quot;
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;id&amp;quot;:&amp;nbsp;2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;&amp;quot;Cannondale&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;description&amp;quot;:&amp;nbsp;&amp;quot;An&amp;nbsp;aerodynamic&amp;nbsp;road&amp;nbsp;bike&amp;nbsp;for&amp;nbsp;racing&amp;nbsp;enthusiasts.&amp;quot;
&amp;nbsp;&amp;nbsp;}
]&lt;/pre&gt;&lt;p&gt;本示例中，若 JSON 读取器配置 &amp;quot;description&amp;quot; 为 jsonKeysToUse，会为数组中每辆自行车创建文档对象，内容为 &amp;quot;description&amp;quot; 字段的值。&lt;/p&gt;&lt;h4&gt;注意事项&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;JSON 读取器基于 Jackson 解析 JSON&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;通过流式处理数组，可高效处理大型 JSON 文件&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若 jsonKeysToUse 指定多个键，内容为这些键对应值的拼接结果&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;通过自定义 jsonKeysToUse 和 JSON 元数据生成器，可适配多种 JSON 结构&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;纯文本格式&lt;/h3&gt;&lt;p&gt;文本读取器用于处理纯文本文档，将其转换为文档对象列表。&lt;/p&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyTextReader&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Resource&amp;nbsp;resource;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MyTextReader(@Value(&amp;quot;classpath:text-source.txt&amp;quot;)&amp;nbsp;Resource&amp;nbsp;resource)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.resource&amp;nbsp;=&amp;nbsp;resource;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

	List&amp;lt;Document&amp;gt;&amp;nbsp;loadText()&amp;nbsp;{
		TextReader&amp;nbsp;textReader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TextReader(this.resource);
		textReader.getCustomMetadata().put(&amp;quot;filename&amp;quot;,&amp;nbsp;&amp;quot;text-source.txt&amp;quot;);

		return&amp;nbsp;textReader.read();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;构造方法选项&lt;/h4&gt;&lt;p&gt;文本读取器提供两种构造方法：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;TextReader(String resourceUrl)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;TextReader(Resource resource)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;参数&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;resourceUrl：表示待读取资源 URL 的字符串&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;resource：指向文本文件的 Spring 资源对象&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;配置&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;setCharset(Charset charset)：设置读取文本文件的字符集，默认为 UTF-8&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;getCustomMetadata()：返回可变映射，可添加文档的自定义元数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;文本读取器处理文本内容的规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;将文本文件全部内容读取到单个文档对象中&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;文件内容作为文档的内容&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;自动为文档添加元数据：&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;charset：读取文件使用的字符集（默认：UTF-8）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;source：源文本文件的文件名&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;通过 getCustomMetadata() 添加的自定义元数据也会包含在文档中&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;注意事项&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;文本读取器会将文件全部内容加载到内存，不适合超大文件&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若需将文本分割为更小片段，读取文档后可使用令牌文本分割器：&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;textReader.get();
List&amp;lt;Document&amp;gt;&amp;nbsp;splitDocuments&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TokenTextSplitter().apply(this.documents);&lt;/pre&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;读取器基于 Spring 资源抽象，可读取多种来源（类路径、文件系统、URL 等）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;可通过 getCustomMetadata() 方法为读取器创建的所有文档添加自定义元数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;HTML 格式（基于 JSoup）&lt;/h3&gt;&lt;p&gt;JSoup 文档读取器基于 JSoup 库处理 HTML 文档，将其转换为文档对象列表。&lt;/p&gt;&lt;h4&gt;依赖&lt;/h4&gt;&lt;p&gt;使用 Maven 或 Gradle 为项目添加依赖。&lt;/p&gt;&lt;p&gt;Maven&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-jsoup-document-reader&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyHtmlReader&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Resource&amp;nbsp;resource;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MyHtmlReader(@Value(&amp;quot;classpath:/my-page.html&amp;quot;)&amp;nbsp;Resource&amp;nbsp;resource)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.resource&amp;nbsp;=&amp;nbsp;resource;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;loadHtml()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JsoupDocumentReaderConfig&amp;nbsp;config&amp;nbsp;=&amp;nbsp;JsoupDocumentReaderConfig.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.selector(&amp;quot;article&amp;nbsp;p&amp;quot;)&amp;nbsp;//&amp;nbsp;提取&amp;nbsp;article&amp;nbsp;标签内的段落
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.charset(&amp;quot;ISO-8859-1&amp;quot;)&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用&amp;nbsp;ISO-8859-1&amp;nbsp;编码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.includeLinkUrls(true)&amp;nbsp;//&amp;nbsp;在元数据中包含链接&amp;nbsp;URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.metadataTags(List.of(&amp;quot;author&amp;quot;,&amp;nbsp;&amp;quot;date&amp;quot;))&amp;nbsp;//&amp;nbsp;提取作者和日期元标签
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.additionalMetadata(&amp;quot;source&amp;quot;,&amp;nbsp;&amp;quot;my-page.html&amp;quot;)&amp;nbsp;//&amp;nbsp;添加自定义元数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JsoupDocumentReader&amp;nbsp;reader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;JsoupDocumentReader(this.resource,&amp;nbsp;config);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;reader.get();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;JSoup 文档读取器配置可自定义读取器行为：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;charset：指定 HTML 文档的字符编码，默认 UTF-8&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;selector：JSoup CSS 选择器，指定提取文本的元素，默认 body&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;separator：拼接多个选中元素文本的字符串，默认换行符&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;allElements：若为 true，提取 body 内所有文本，忽略选择器，默认 false&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;groupByElement：若为 true，选择器匹配的每个元素创建独立文档，默认 false&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;includeLinkUrls：若为 true，提取绝对链接 URL 并加入元数据，默认 false&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;metadataTags：需提取内容的 meta 标签列表，默认 [&amp;quot;description&amp;quot;, &amp;quot;keywords&amp;quot;]&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;additionalMetadata：为所有创建的文档对象添加自定义元数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;示例文档：my-page.html&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;!DOCTYPE&amp;nbsp;html&amp;gt;
&amp;lt;html&amp;nbsp;lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;title&amp;gt;My&amp;nbsp;Web&amp;nbsp;Page&amp;lt;/title&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;name=&amp;quot;description&amp;quot;&amp;nbsp;content=&amp;quot;A&amp;nbsp;sample&amp;nbsp;web&amp;nbsp;page&amp;nbsp;for&amp;nbsp;Spring&amp;nbsp;AI&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;name=&amp;quot;keywords&amp;quot;&amp;nbsp;content=&amp;quot;spring,&amp;nbsp;ai,&amp;nbsp;html,&amp;nbsp;example&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;name=&amp;quot;author&amp;quot;&amp;nbsp;content=&amp;quot;John&amp;nbsp;Doe&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;name=&amp;quot;date&amp;quot;&amp;nbsp;content=&amp;quot;2024-01-15&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;link&amp;nbsp;rel=&amp;quot;stylesheet&amp;quot;&amp;nbsp;href=&amp;quot;style.css&amp;quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;header&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;h1&amp;gt;Welcome&amp;nbsp;to&amp;nbsp;My&amp;nbsp;Page&amp;lt;/h1&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/header&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;nav&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ul&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;li&amp;gt;&amp;lt;a&amp;nbsp;href=&amp;quot;/&amp;quot;&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;li&amp;gt;&amp;lt;a&amp;nbsp;href=&amp;quot;/about&amp;quot;&amp;gt;About&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/ul&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/nav&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;article&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;h2&amp;gt;Main&amp;nbsp;Content&amp;lt;/h2&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;gt;This&amp;nbsp;is&amp;nbsp;the&amp;nbsp;main&amp;nbsp;content&amp;nbsp;of&amp;nbsp;my&amp;nbsp;web&amp;nbsp;page.&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;gt;It&amp;nbsp;contains&amp;nbsp;multiple&amp;nbsp;paragraphs.&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;a&amp;nbsp;href=&amp;quot;https://www.example.com&amp;quot;&amp;gt;External&amp;nbsp;Link&amp;lt;/a&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/article&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;footer&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;gt;&amp;amp;copy;&amp;nbsp;2024&amp;nbsp;John&amp;nbsp;Doe&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/footer&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;JSoup 文档读取器处理 HTML 内容并根据配置创建文档对象：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;选择器决定提取文本的元素&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若 allElements 为 true，body 内所有文本提取到单个文档&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若 groupByElement 为 true，选择器匹配的每个元素创建独立文档&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若两者均为 false，选择器匹配的所有元素文本通过分隔符拼接&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;文档标题、指定 meta 标签内容、（可选）链接 URL 加入文档元数据&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;解析相对链接的基础 URI 从 URL 资源中提取&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;读取器保留选中元素的文本内容，移除内部 HTML 标签&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Markdown 格式&lt;/h3&gt;&lt;p&gt;Markdown 文档读取器用于处理 Markdown 文档，将其转换为文档对象列表。&lt;/p&gt;&lt;h4&gt;依赖&lt;/h4&gt;&lt;p&gt;使用 Maven 或 Gradle 为项目添加依赖。&lt;/p&gt;&lt;p&gt;Maven&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-markdown-document-reader&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyMarkdownReader&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Resource&amp;nbsp;resource;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MyMarkdownReader(@Value(&amp;quot;classpath:code.md&amp;quot;)&amp;nbsp;Resource&amp;nbsp;resource)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.resource&amp;nbsp;=&amp;nbsp;resource;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;loadMarkdown()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MarkdownDocumentReaderConfig&amp;nbsp;config&amp;nbsp;=&amp;nbsp;MarkdownDocumentReaderConfig.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withHorizontalRuleCreateDocument(true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withIncludeCodeBlock(false)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withIncludeBlockquote(false)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withAdditionalMetadata(&amp;quot;filename&amp;quot;,&amp;nbsp;&amp;quot;code.md&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MarkdownDocumentReader&amp;nbsp;reader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;MarkdownDocumentReader(this.resource,&amp;nbsp;config);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;reader.get();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;Markdown 文档读取器配置可自定义读取器行为：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;horizontalRuleCreateDocument：为 true 时，Markdown 中的水平分隔线创建新文档对象&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;includeCodeBlock：为 true 时，代码块与周围文本包含在同一文档；为 false 时，代码块创建独立文档&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;includeBlockquote：为 true 时，引用块与周围文本包含在同一文档；为 false 时，引用块创建独立文档&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;additionalMetadata：为所有创建的文档对象添加自定义元数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;示例文档：code.md&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;This&amp;nbsp;is&amp;nbsp;a&amp;nbsp;Java&amp;nbsp;sample&amp;nbsp;application:

```java
package&amp;nbsp;com.example.demo;

import&amp;nbsp;org.springframework.boot.SpringApplication;
import&amp;nbsp;org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public&amp;nbsp;class&amp;nbsp;DemoApplication&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(DemoApplication.class,&amp;nbsp;args);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
```

Markdown&amp;nbsp;also&amp;nbsp;provides&amp;nbsp;the&amp;nbsp;possibility&amp;nbsp;to&amp;nbsp;`use&amp;nbsp;inline&amp;nbsp;code&amp;nbsp;formatting&amp;nbsp;throughout`&amp;nbsp;the&amp;nbsp;entire&amp;nbsp;sentence.

---

Another&amp;nbsp;possibility&amp;nbsp;is&amp;nbsp;to&amp;nbsp;set&amp;nbsp;block&amp;nbsp;code&amp;nbsp;without&amp;nbsp;specific&amp;nbsp;highlighting:

```
./mvnw&amp;nbsp;spring-javaformat:apply
```&lt;/pre&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;Markdown 文档读取器处理 Markdown 内容并根据配置创建文档对象：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;标题转为文档对象的元数据&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;段落转为文档对象的内容&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;代码块可分离为独立文档或与周围文本合并&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;引用块可分离为独立文档或与周围文本合并&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;水平分隔线可将内容分割为独立文档&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;读取器保留文档内容中的行内代码、列表、文本样式等格式&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;PDF 分页格式&lt;/h3&gt;&lt;p&gt;分页 PDF 文档读取器基于 Apache PdfBox 库解析 PDF 文档。&lt;/p&gt;&lt;h4&gt;依赖&lt;/h4&gt;&lt;p&gt;使用 Maven 或 Gradle 为项目添加依赖。&lt;/p&gt;&lt;p&gt;Maven&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-pdf-document-reader&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;MyPagePdfDocumentReader&amp;nbsp;{

	List&amp;lt;Document&amp;gt;&amp;nbsp;getDocsFromPdf()&amp;nbsp;{

		PagePdfDocumentReader&amp;nbsp;pdfReader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;PagePdfDocumentReader(&amp;quot;classpath:/sample1.pdf&amp;quot;,
				PdfDocumentReaderConfig.builder()
					.withPageTopMargin(0)
					.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
						.withNumberOfTopTextLinesToDelete(0)
						.build())
					.withPagesPerDocument(1)
					.build());

		return&amp;nbsp;pdfReader.read();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

}&lt;/pre&gt;&lt;h3&gt;PDF 段落格式&lt;/h3&gt;&lt;p&gt;段落 PDF 文档读取器利用 PDF 目录（如目录）信息将输入 PDF 分割为文本段落，每个段落输出一个文档。注意：并非所有 PDF 文档都包含目录。&lt;/p&gt;&lt;h4&gt;依赖&lt;/h4&gt;&lt;p&gt;使用 Maven 或 Gradle 为项目添加依赖。&lt;/p&gt;&lt;p&gt;Maven&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-pdf-document-reader&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;MyPagePdfDocumentReader&amp;nbsp;{

	List&amp;lt;Document&amp;gt;&amp;nbsp;getDocsFromPdfWithCatalog()&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ParagraphPdfDocumentReader&amp;nbsp;pdfReader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ParagraphPdfDocumentReader(&amp;quot;classpath:/sample1.pdf&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PdfDocumentReaderConfig.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withPageTopMargin(0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withNumberOfTopTextLinesToDelete(0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withPagesPerDocument(1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build());

	&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;pdfReader.read();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;Tika 格式（DOCX、PPTX、HTML 等）&lt;/h3&gt;&lt;p&gt;Tika 文档读取器基于 Apache Tika 从多种文档格式提取文本，包括 PDF、DOC/DOCX、PPT/PPTX、HTML 等。完整支持格式列表参考 Tika 官方文档。&lt;/p&gt;&lt;h4&gt;依赖&lt;/h4&gt;&lt;p&gt;使用 Maven 或 Gradle 为项目添加依赖。&lt;/p&gt;&lt;p&gt;Maven&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-tika-document-reader&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyTikaDocumentReader&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Resource&amp;nbsp;resource;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MyTikaDocumentReader(@Value(&amp;quot;classpath:/word-sample.docx&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Resource&amp;nbsp;resource)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.resource&amp;nbsp;=&amp;nbsp;resource;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;loadText()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TikaDocumentReader&amp;nbsp;tikaDocumentReader&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TikaDocumentReader(this.resource);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;tikaDocumentReader.read();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;转换器&lt;/h2&gt;&lt;h3&gt;文本分割器&lt;/h3&gt;&lt;p&gt;文本分割器是抽象基类，用于分割文档以适配 AI 模型的上下文窗口。&lt;/p&gt;&lt;h3&gt;令牌文本分割器&lt;/h3&gt;&lt;p&gt;令牌文本分割器是文本分割器的实现类，基于令牌数量分割文本，使用 CL100K_BASE 编码。&lt;/p&gt;&lt;h4&gt;用法&lt;/h4&gt;&lt;h5&gt;基础用法&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyTokenTextSplitter&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;splitDocuments(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TokenTextSplitter&amp;nbsp;splitter&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TokenTextSplitter();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;splitter.apply(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;splitCustomized(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TokenTextSplitter&amp;nbsp;splitter&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TokenTextSplitter(1000,&amp;nbsp;400,&amp;nbsp;10,&amp;nbsp;5000,&amp;nbsp;true,&amp;nbsp;List.of(&amp;#39;.&amp;#39;,&amp;nbsp;&amp;#39;?&amp;#39;,&amp;nbsp;&amp;#39;!&amp;#39;,&amp;nbsp;&amp;#39;\n&amp;#39;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;splitter.apply(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h5&gt;使用建造者模式&lt;/h5&gt;&lt;p&gt;推荐使用建造者模式创建令牌文本分割器，API 更易读、更灵活：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyTokenTextSplitter&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;splitWithBuilder(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TokenTextSplitter&amp;nbsp;splitter&amp;nbsp;=&amp;nbsp;TokenTextSplitter.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withChunkSize(1000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withMinChunkSizeChars(400)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withMinChunkLengthToEmbed(10)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withMaxNumChunks(5000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withKeepSeparator(true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;splitter.apply(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h5&gt;自定义标点符号&lt;/h5&gt;&lt;p&gt;可自定义用于将文本分割为语义片段的标点符号，对国际化场景尤为实用：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyInternationalTextSplitter&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;splitChineseText(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用中文标点符号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TokenTextSplitter&amp;nbsp;splitter&amp;nbsp;=&amp;nbsp;TokenTextSplitter.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withChunkSize(800)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withMinChunkSizeChars(350)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withPunctuationMarks(List.of(&amp;#39;。&amp;#39;,&amp;nbsp;&amp;#39;？&amp;#39;,&amp;nbsp;&amp;#39;！&amp;#39;,&amp;nbsp;&amp;#39;；&amp;#39;))&amp;nbsp;&amp;nbsp;//&amp;nbsp;中文标点
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;splitter.apply(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;splitWithCustomMarks(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;中英文标点混合
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TokenTextSplitter&amp;nbsp;splitter&amp;nbsp;=&amp;nbsp;TokenTextSplitter.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withChunkSize(800)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withPunctuationMarks(List.of(&amp;#39;.&amp;#39;,&amp;nbsp;&amp;#39;?&amp;#39;,&amp;nbsp;&amp;#39;!&amp;#39;,&amp;nbsp;&amp;#39;\n&amp;#39;,&amp;nbsp;&amp;#39;;&amp;#39;,&amp;nbsp;&amp;#39;:&amp;#39;,&amp;nbsp;&amp;#39;。&amp;#39;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;splitter.apply(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;构造方法选项&lt;/h4&gt;&lt;p&gt;令牌文本分割器提供三种构造方法：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;TokenTextSplitter()：使用默认配置创建分割器&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;TokenTextSplitter(boolean keepSeparator)：自定义分隔符行为创建分割器&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;TokenTextSplitter(int chunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator, List&amp;lt;Character&amp;gt; punctuationMarks)：全参数自定义构造方法&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;推荐使用建造者模式创建自定义配置的实例。&lt;/p&gt;&lt;h4&gt;参数&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;chunkSize：每个文本片段的目标令牌大小，默认 800&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;minChunkSizeChars：每个文本片段的最小字符数，默认 350&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;minChunkLengthToEmbed：需纳入的片段最小长度，默认 5&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;maxNumChunks：单段文本生成的最大片段数，默认 10000&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;keepSeparator：是否在片段中保留分隔符（如换行符），默认 true&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;punctuationMarks：用于句子边界分割的字符列表，默认 . ? ! 换行符&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;令牌文本分割器处理文本内容的规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;通过 CL100K_BASE 编码将输入文本转为令牌&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;根据 chunkSize 将编码后的文本分割为片段&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;对每个片段：&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将片段解码为文本&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;仅当总令牌数超过 chunkSize 时，在 minChunkSizeChars 后通过配置的标点符号寻找合适分割点&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;找到分割点则在该位置截断片段&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;修剪片段，并根据 keepSeparator 设置可选移除换行符&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若结果片段长度大于 minChunkLengthToEmbed，加入输出列表&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;重复流程直至所有令牌处理完成或达到 maxNumChunks&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;剩余文本长度大于 minChunkLengthToEmbed 时，作为最终片段加入&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;仅当令牌数超过片段大小时，才基于标点分割。大小等于或小于片段大小的文本直接作为单个片段返回，避免小文本无意义分割。&lt;/p&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Document&amp;nbsp;doc1&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;This&amp;nbsp;is&amp;nbsp;a&amp;nbsp;long&amp;nbsp;piece&amp;nbsp;of&amp;nbsp;text&amp;nbsp;that&amp;nbsp;needs&amp;nbsp;to&amp;nbsp;be&amp;nbsp;split&amp;nbsp;into&amp;nbsp;smaller&amp;nbsp;chunks&amp;nbsp;for&amp;nbsp;processing.&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(&amp;quot;source&amp;quot;,&amp;nbsp;&amp;quot;example.txt&amp;quot;));
Document&amp;nbsp;doc2&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;Another&amp;nbsp;document&amp;nbsp;with&amp;nbsp;content&amp;nbsp;that&amp;nbsp;will&amp;nbsp;be&amp;nbsp;split&amp;nbsp;based&amp;nbsp;on&amp;nbsp;token&amp;nbsp;count.&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map.of(&amp;quot;source&amp;quot;,&amp;nbsp;&amp;quot;example2.txt&amp;quot;));

TokenTextSplitter&amp;nbsp;splitter&amp;nbsp;=&amp;nbsp;new&amp;nbsp;TokenTextSplitter();
List&amp;lt;Document&amp;gt;&amp;nbsp;splitDocuments&amp;nbsp;=&amp;nbsp;this.splitter.apply(List.of(this.doc1,&amp;nbsp;this.doc2));

for&amp;nbsp;(Document&amp;nbsp;doc&amp;nbsp;:&amp;nbsp;splitDocuments)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;Chunk:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;doc.getContent());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;Metadata:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;doc.getMetadata());
}&lt;/pre&gt;&lt;h4&gt;注意事项&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;令牌文本分割器使用 jtokkit 库的 CL100K_BASE 编码，兼容新版 OpenAI 模型&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;分割器尽可能在句子边界分割，创建语义完整的片段&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;原文档的元数据会保留并复制到所有派生片段&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若 copyContentFormatter 设为 true（默认行为），原文档的内容格式化器会复制到派生片段&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;该分割器适合为有令牌限制的大语言模型准备文本，确保片段在模型处理能力范围内&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;自定义标点符号：默认标点适配英文文本，其他语言或专业内容可通过建造者的 withPunctuationMarks 方法自定义&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;性能考量：分割器可处理任意数量标点，但为优化性能，建议列表长度控制在 20 个字符以内&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;可扩展性：getLastPunctuationIndex 方法为保护类型，子类可重写标点检测逻辑适配特殊场景&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;小文本处理：2.0 版本起，令牌数小于等于片段大小的小文本不再按标点分割，避免符合大小限制的内容无意义碎片化&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;内容格式转换器&lt;/h3&gt;&lt;p&gt;确保所有文档的内容格式统一。&lt;/p&gt;&lt;h3&gt;关键词元数据增强器&lt;/h3&gt;&lt;p&gt;关键词元数据增强器是一种文档转换器，使用生成式 AI 模型从文档内容提取关键词并作为元数据添加。&lt;/p&gt;&lt;h4&gt;用法&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyKeywordEnricher&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;ChatModel&amp;nbsp;chatModel;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MyKeywordEnricher(ChatModel&amp;nbsp;chatModel)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.chatModel&amp;nbsp;=&amp;nbsp;chatModel;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;enrichDocuments(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;KeywordMetadataEnricher&amp;nbsp;enricher&amp;nbsp;=&amp;nbsp;KeywordMetadataEnricher.builder(chatModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.keywordCount(5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;或使用自定义模板
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;KeywordMetadataEnricher&amp;nbsp;enricher&amp;nbsp;=&amp;nbsp;KeywordMetadataEnricher.builder(chatModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.keywordsTemplate(YOUR_CUSTOM_TEMPLATE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;enricher.apply(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;构造方法选项&lt;/h4&gt;&lt;p&gt;关键词元数据增强器提供两种构造方法：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;KeywordMetadataEnricher(ChatModel chatModel, int keywordCount)：使用默认模板提取指定数量关键词&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate)：使用自定义模板提取关键词&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;关键词元数据增强器处理文档的规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;为每个输入文档基于内容创建提示词&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将提示词发送给指定的对话模型生成关键词&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;生成的关键词以 &amp;quot;excerpt_keywords&amp;quot; 为键加入文档元数据&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;返回增强后的文档&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;自定义&lt;/h4&gt;&lt;p&gt;可使用默认模板或通过 keywordsTemplate 参数自定义模板。默认模板：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;{context_str}.&amp;nbsp;Give&amp;nbsp;%s&amp;nbsp;unique&amp;nbsp;keywords&amp;nbsp;for&amp;nbsp;this&amp;nbsp;document.&amp;nbsp;Format&amp;nbsp;as&amp;nbsp;comma&amp;nbsp;separated.&amp;nbsp;Keywords:&lt;/pre&gt;&lt;p&gt;其中 {context_str} 替换为文档内容，%s 替换为指定的关键词数量。&lt;/p&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;ChatModel&amp;nbsp;chatModel&amp;nbsp;=&amp;nbsp;//&amp;nbsp;初始化对话模型
KeywordMetadataEnricher&amp;nbsp;enricher&amp;nbsp;=&amp;nbsp;KeywordMetadataEnricher.builder(chatModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.keywordCount(5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

//&amp;nbsp;或使用自定义模板
KeywordMetadataEnricher&amp;nbsp;enricher&amp;nbsp;=&amp;nbsp;KeywordMetadataEnricher.builder(chatModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.keywordsTemplate(new&amp;nbsp;PromptTemplate(&amp;quot;Extract&amp;nbsp;5&amp;nbsp;important&amp;nbsp;keywords&amp;nbsp;from&amp;nbsp;the&amp;nbsp;following&amp;nbsp;text&amp;nbsp;and&amp;nbsp;separate&amp;nbsp;them&amp;nbsp;with&amp;nbsp;commas:\n{context_str}&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

Document&amp;nbsp;doc&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;This&amp;nbsp;is&amp;nbsp;a&amp;nbsp;document&amp;nbsp;about&amp;nbsp;artificial&amp;nbsp;intelligence&amp;nbsp;and&amp;nbsp;its&amp;nbsp;applications&amp;nbsp;in&amp;nbsp;modern&amp;nbsp;technology.&amp;quot;);

List&amp;lt;Document&amp;gt;&amp;nbsp;enrichedDocs&amp;nbsp;=&amp;nbsp;enricher.apply(List.of(this.doc));

Document&amp;nbsp;enrichedDoc&amp;nbsp;=&amp;nbsp;this.enrichedDocs.get(0);
String&amp;nbsp;keywords&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;this.enrichedDoc.getMetadata().get(&amp;quot;excerpt_keywords&amp;quot;);
System.out.println(&amp;quot;Extracted&amp;nbsp;keywords:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;keywords);&lt;/pre&gt;&lt;h4&gt;注意事项&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;关键词元数据增强器需要可用的对话模型生成关键词&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;关键词数量必须大于等于 1&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;增强器为每个处理后的文档添加 &amp;quot;excerpt_keywords&amp;quot; 元数据字段&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;生成的关键词以逗号分隔字符串返回&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;该增强器可提升文档检索性，用于生成文档标签或分类&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;建造者模式中，若设置 keywordsTemplate 参数，keywordCount 参数会被忽略&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;摘要元数据增强器&lt;/h3&gt;&lt;p&gt;摘要元数据增强器是一种文档转换器，使用生成式 AI 模型为文档生成摘要并作为元数据添加。可生成当前文档及相邻文档（上一个、下一个）的摘要。&lt;/p&gt;&lt;h4&gt;用法&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Configuration
class&amp;nbsp;EnricherConfig&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;SummaryMetadataEnricher&amp;nbsp;summaryMetadata(OpenAiChatModel&amp;nbsp;aiClient)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;SummaryMetadataEnricher(aiClient,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(SummaryType.PREVIOUS,&amp;nbsp;SummaryType.CURRENT,&amp;nbsp;SummaryType.NEXT));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Component
class&amp;nbsp;MySummaryEnricher&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;SummaryMetadataEnricher&amp;nbsp;enricher;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MySummaryEnricher(SummaryMetadataEnricher&amp;nbsp;enricher)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.enricher&amp;nbsp;=&amp;nbsp;enricher;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Document&amp;gt;&amp;nbsp;enrichDocuments(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;this.enricher.apply(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;构造方法&lt;/h4&gt;&lt;p&gt;摘要元数据增强器提供两种构造方法：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;SummaryMetadataEnricher(ChatModel chatModel, List&amp;lt;SummaryType&amp;gt; summaryTypes)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;SummaryMetadataEnricher(ChatModel chatModel, List&amp;lt;SummaryType&amp;gt; summaryTypes, String summaryTemplate, MetadataMode metadataMode)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;参数&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;chatModel：用于生成摘要的 AI 模型&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;summaryTypes：摘要类型枚举列表，指定生成哪些摘要（上一个、当前、下一个）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;summaryTemplate：摘要生成自定义模板（可选）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;metadataMode：生成摘要时处理文档元数据的方式（可选）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;摘要元数据增强器处理文档的规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;为每个输入文档基于内容和指定摘要模板创建提示词&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将提示词发送给指定的对话模型生成摘要&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;根据指定的 summaryTypes，为每个文档添加以下元数据：&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;section_summary：当前文档摘要&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;prev_section_summary：上一个文档摘要（可用且请求时）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;next_section_summary：下一个文档摘要（可用且请求时）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;返回增强后的文档&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;自定义&lt;/h4&gt;&lt;p&gt;可通过提供自定义 summaryTemplate 定制摘要生成提示词。默认模板：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;quot;&amp;quot;&amp;quot;
Here&amp;nbsp;is&amp;nbsp;the&amp;nbsp;content&amp;nbsp;of&amp;nbsp;the&amp;nbsp;section:
{context_str}

Summarize&amp;nbsp;the&amp;nbsp;key&amp;nbsp;topics&amp;nbsp;and&amp;nbsp;entities&amp;nbsp;of&amp;nbsp;the&amp;nbsp;section.

Summary:
&amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;ChatModel&amp;nbsp;chatModel&amp;nbsp;=&amp;nbsp;//&amp;nbsp;初始化对话模型
SummaryMetadataEnricher&amp;nbsp;enricher&amp;nbsp;=&amp;nbsp;new&amp;nbsp;SummaryMetadataEnricher(chatModel,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(SummaryType.PREVIOUS,&amp;nbsp;SummaryType.CURRENT,&amp;nbsp;SummaryType.NEXT));

Document&amp;nbsp;doc1&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;Content&amp;nbsp;of&amp;nbsp;document&amp;nbsp;1&amp;quot;);
Document&amp;nbsp;doc2&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Document(&amp;quot;Content&amp;nbsp;of&amp;nbsp;document&amp;nbsp;2&amp;quot;);

List&amp;lt;Document&amp;gt;&amp;nbsp;enrichedDocs&amp;nbsp;=&amp;nbsp;enricher.apply(List.of(this.doc1,&amp;nbsp;this.doc2));

//&amp;nbsp;检查增强后文档的元数据
for&amp;nbsp;(Document&amp;nbsp;doc&amp;nbsp;:&amp;nbsp;enrichedDocs)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;Current&amp;nbsp;summary:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;doc.getMetadata().get(&amp;quot;section_summary&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;Previous&amp;nbsp;summary:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;doc.getMetadata().get(&amp;quot;prev_section_summary&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;Next&amp;nbsp;summary:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;doc.getMetadata().get(&amp;quot;next_section_summary&amp;quot;));
}&lt;/pre&gt;&lt;p&gt;示例展示预期行为：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;两个文档的列表中，两个文档均获得 section_summary&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;第一个文档获得 next_section_summary，无 prev_section_summary&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;第二个文档获得 prev_section_summary，无 next_section_summary&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;第一个文档的 section_summary 与第二个文档的 prev_section_summary 一致&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;第一个文档的 next_section_summary 与第二个文档的 section_summary 一致&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;注意事项&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;摘要元数据增强器需要可用的对话模型生成摘要&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;增强器可处理任意长度的文档列表，正确处理首尾文档的边界情况&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;该增强器适合创建上下文感知摘要，更好理解序列中文档的关联关系&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;MetadataMode 参数可控制现有元数据如何纳入摘要生成流程&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;写入器&lt;/h2&gt;&lt;h3&gt;文件写入&lt;/h3&gt;&lt;p&gt;文件文档写入器是文档写入器的实现类，将文档列表内容写入文件。&lt;/p&gt;&lt;h4&gt;用法&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
class&amp;nbsp;MyDocumentWriter&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;writeDocuments(List&amp;lt;Document&amp;gt;&amp;nbsp;documents)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FileDocumentWriter&amp;nbsp;writer&amp;nbsp;=&amp;nbsp;new&amp;nbsp;FileDocumentWriter(&amp;quot;output.txt&amp;quot;,&amp;nbsp;true,&amp;nbsp;MetadataMode.ALL,&amp;nbsp;false);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;writer.accept(documents);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;构造方法&lt;/h4&gt;&lt;p&gt;文件文档写入器提供三种构造方法：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;FileDocumentWriter(String fileName)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;FileDocumentWriter(String fileName, boolean withDocumentMarkers)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;FileDocumentWriter(String fileName, boolean withDocumentMarkers, MetadataMode metadataMode, boolean append)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;参数&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;fileName：写入文档的目标文件名&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;withDocumentMarkers：是否在输出中包含文档标记，默认 false&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;metadataMode：指定写入文件的文档内容，默认 MetadataMode.NONE&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;append：为 true 时数据追加到文件末尾，否则覆盖开头，默认 false&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;行为&lt;/h4&gt;&lt;p&gt;文件文档写入器处理文档的规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;为指定文件名打开文件写入器&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;对输入列表中的每个文档：&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若 withDocumentMarkers 为 true，写入包含文档索引和页码的文档标记&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;根据指定的 metadataMode 写入格式化后的文档内容&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;所有文档写入完成后关闭文件&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;文档标记&lt;/h4&gt;&lt;p&gt;withDocumentMarkers 设为 true 时，写入器为每个文档添加以下格式标记：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;###&amp;nbsp;Doc:&amp;nbsp;[index],&amp;nbsp;pages:[start_page_number,end_page_number]&lt;/pre&gt;&lt;h4&gt;元数据处理&lt;/h4&gt;&lt;p&gt;写入器使用两个特定元数据键：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;page_number：文档的起始页码&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;end_page_number：文档的结束页码&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;写入文档标记时使用这两个键。&lt;/p&gt;&lt;h4&gt;示例&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;//&amp;nbsp;初始化文档
FileDocumentWriter&amp;nbsp;writer&amp;nbsp;=&amp;nbsp;new&amp;nbsp;FileDocumentWriter(&amp;quot;output.txt&amp;quot;,&amp;nbsp;true,&amp;nbsp;MetadataMode.ALL,&amp;nbsp;true);
writer.accept(documents);&lt;/pre&gt;&lt;p&gt;该代码将所有文档写入 output.txt，包含文档标记、所有元数据，文件已存在时追加内容。&lt;/p&gt;&lt;h4&gt;注意事项&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;写入器使用文件写入器，以操作系统默认字符编码写入文本文件&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;写入过程出错时，抛出运行时异常并封装原始异常&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;metadataMode 参数可控制现有元数据如何纳入写入内容&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;该写入器适合调试或创建文档集合的人类可读输出&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;向量库&lt;/h3&gt;&lt;p&gt;提供与多种向量库的集成。完整列表参考向量数据库文档。&lt;/p&gt;</description><pubDate>Wed, 20 May 2026 15:59:41 +0800</pubDate></item><item><title>Spring AI 检索增强生成（RAG）</title><link>https://catnav.cn/post/84.html</link><description>&lt;p&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;检索增强生成（RAG）是一项用于克服大语言模型局限性的技术，大语言模型在长文本内容生成、事实准确性和上下文感知方面存在短板。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Spring AI 通过提供模块化架构支持 RAG 技术，你可以自行构建自定义的 RAG 流程，也可以使用 Advisor API 开箱即用的 RAG 流程。&lt;/p&gt;&lt;p&gt;在概念章节中了解更多关于检索增强生成的信息。&lt;/p&gt;&lt;h3&gt;顾问（Advisors）&lt;/h3&gt;&lt;p&gt;Spring AI 通过 Advisor API 为常用的 RAG 流程提供开箱即用的支持。&lt;/p&gt;&lt;p&gt;若要使用 QuestionAnswerAdvisor 或 VectorStoreChatMemoryAdvisor，你需要在项目中添加 spring-ai-advisors-vector-store 依赖：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-advisors-vector-store&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h3&gt;QuestionAnswerAdvisor&lt;/h3&gt;&lt;p&gt;向量数据库用于存储 AI 模型未知的数据。当用户向 AI 模型发送问题时，QuestionAnswerAdvisor 会向向量数据库查询与用户问题相关的文档。&lt;/p&gt;&lt;p&gt;向量数据库返回的结果会追加到用户输入文本中，为 AI 模型生成回答提供上下文信息。&lt;/p&gt;&lt;p&gt;假设你已经将数据加载到 VectorStore 中，可通过为 ChatClient 提供 QuestionAnswerAdvisor 实例来实现检索增强生成（RAG）。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;ChatResponse&amp;nbsp;response&amp;nbsp;=&amp;nbsp;ChatClient.builder(chatModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build().prompt()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.user(userText)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatResponse();&lt;/pre&gt;&lt;p&gt;在本示例中，QuestionAnswerAdvisor 会对向量数据库中的所有文档执行相似度搜索。若要限制搜索的文档类型，SearchRequest 支持类 SQL 的过滤表达式，该表达式可在所有 VectorStore 中通用。&lt;/p&gt;&lt;p&gt;该过滤表达式可在创建 QuestionAnswerAdvisor 时配置（将应用于所有 ChatClient 请求），也可在每次请求运行时动态指定。&lt;/p&gt;&lt;p&gt;以下是创建相似度阈值为 0.8、返回前 6 条结果的 QuestionAnswerAdvisor 实例的方法：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;var&amp;nbsp;qaAdvisor&amp;nbsp;=&amp;nbsp;QuestionAnswerAdvisor.builder(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.searchRequest(SearchRequest.builder().similarityThreshold(0.8d).topK(6).build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/pre&gt;&lt;h4&gt;动态过滤表达式&lt;/h4&gt;&lt;p&gt;通过 FILTER_EXPRESSION 顾问上下文参数，在运行时更新 SearchRequest 过滤表达式：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;ChatClient&amp;nbsp;chatClient&amp;nbsp;=&amp;nbsp;ChatClient.builder(chatModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.searchRequest(SearchRequest.builder().build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

//&amp;nbsp;在运行时更新过滤表达式
String&amp;nbsp;content&amp;nbsp;=&amp;nbsp;this.chatClient.prompt()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.user(&amp;quot;Please&amp;nbsp;answer&amp;nbsp;my&amp;nbsp;question&amp;nbsp;XYZ&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(a&amp;nbsp;-&amp;gt;&amp;nbsp;a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION,&amp;nbsp;&amp;quot;type&amp;nbsp;==&amp;nbsp;&amp;#39;Spring&amp;#39;&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content();&lt;/pre&gt;&lt;p&gt;FILTER_EXPRESSION 参数允许你根据指定的表达式动态过滤搜索结果。&lt;/p&gt;&lt;h4&gt;自定义模板&lt;/h4&gt;&lt;p&gt;QuestionAnswerAdvisor 使用默认模板将检索到的文档与用户问题结合。你可以通过构建器方法 .promptTemplate() 提供自定义的 PromptTemplate 对象来修改这一行为。&lt;/p&gt;&lt;p&gt;此处提供的 PromptTemplate 用于自定义顾问将检索到的上下文与用户查询结合的方式。这与在 ChatClient 自身（使用 .templateRenderer()）上配置 TemplateRenderer 不同，后者会在顾问运行前影响初始用户/系统提示内容的渲染。有关客户端级模板渲染的更多详情，请参阅 ChatClient 提示模板。&lt;/p&gt;&lt;p&gt;自定义 PromptTemplate 可使用任意 TemplateRenderer 实现（默认使用基于 StringTemplate 引擎的 StPromptTemplate）。核心要求是模板必须包含以下两个占位符：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;query 占位符：用于接收用户问题&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;question_answer_context 占位符：用于接收检索到的上下文&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;PromptTemplate&amp;nbsp;customPromptTemplate&amp;nbsp;=&amp;nbsp;PromptTemplate.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.renderer(StPromptRenderer.builder().startDelimiterToken(&amp;#39;&amp;lt;&amp;#39;).endDelimiterToken(&amp;#39;&amp;gt;&amp;#39;).build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.template(&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;query&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Context&amp;nbsp;information&amp;nbsp;is&amp;nbsp;below.

			---------------------
			&amp;lt;question_answer_context&amp;gt;
			---------------------

			Given&amp;nbsp;the&amp;nbsp;context&amp;nbsp;information&amp;nbsp;and&amp;nbsp;no&amp;nbsp;prior&amp;nbsp;knowledge,&amp;nbsp;answer&amp;nbsp;the&amp;nbsp;query.

			Follow&amp;nbsp;these&amp;nbsp;rules:

			1.&amp;nbsp;If&amp;nbsp;the&amp;nbsp;answer&amp;nbsp;is&amp;nbsp;not&amp;nbsp;in&amp;nbsp;the&amp;nbsp;context,&amp;nbsp;just&amp;nbsp;say&amp;nbsp;that&amp;nbsp;you&amp;nbsp;don&amp;#39;t&amp;nbsp;know.
			2.&amp;nbsp;Avoid&amp;nbsp;statements&amp;nbsp;like&amp;nbsp;&amp;quot;Based&amp;nbsp;on&amp;nbsp;the&amp;nbsp;context...&amp;quot;&amp;nbsp;or&amp;nbsp;&amp;quot;The&amp;nbsp;provided&amp;nbsp;information...&amp;quot;.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;question&amp;nbsp;=&amp;nbsp;&amp;quot;Where&amp;nbsp;does&amp;nbsp;the&amp;nbsp;adventure&amp;nbsp;of&amp;nbsp;Anacletus&amp;nbsp;and&amp;nbsp;Birba&amp;nbsp;take&amp;nbsp;place?&amp;quot;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;QuestionAnswerAdvisor&amp;nbsp;qaAdvisor&amp;nbsp;=&amp;nbsp;QuestionAnswerAdvisor.builder(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.promptTemplate(customPromptTemplate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;response&amp;nbsp;=&amp;nbsp;ChatClient.builder(chatModel).build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.prompt(question)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(qaAdvisor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content();&lt;/pre&gt;&lt;p&gt;QuestionAnswerAdvisor.Builder.userTextAdvise() 方法已被弃用，推荐使用 .promptTemplate() 实现更灵活的自定义配置。&lt;/p&gt;&lt;h3&gt;RetrievalAugmentationAdvisor&lt;/h3&gt;&lt;p&gt;Spring AI 包含一套 RAG 模块库，你可用于构建自定义的 RAG 流程。RetrievalAugmentationAdvisor 是基于模块化架构、为最常用的 RAG 流程提供开箱即用实现的顾问组件。&lt;/p&gt;&lt;p&gt;若要使用 RetrievalAugmentationAdvisor，你需要在项目中添加 spring-ai-rag 依赖：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;lt;dependency&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;artifactId&amp;gt;spring-ai-rag&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;h4&gt;顺序式 RAG 流程&lt;/h4&gt;&lt;h5&gt;基础 RAG&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Advisor&amp;nbsp;retrievalAugmentationAdvisor&amp;nbsp;=&amp;nbsp;RetrievalAugmentationAdvisor.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.documentRetriever(VectorStoreDocumentRetriever.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(0.50)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStore(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

String&amp;nbsp;answer&amp;nbsp;=&amp;nbsp;chatClient.prompt()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(retrievalAugmentationAdvisor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.user(question)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content();&lt;/pre&gt;&lt;p&gt;默认情况下，RetrievalAugmentationAdvisor 不允许检索到的上下文为空。若上下文为空，组件会指示模型不回答用户查询。你可以按以下方式允许空上下文：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Advisor&amp;nbsp;retrievalAugmentationAdvisor&amp;nbsp;=&amp;nbsp;RetrievalAugmentationAdvisor.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.documentRetriever(VectorStoreDocumentRetriever.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(0.50)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStore(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.queryAugmenter(ContextualQueryAugmenter.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.allowEmptyContext(true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

String&amp;nbsp;answer&amp;nbsp;=&amp;nbsp;chatClient.prompt()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(retrievalAugmentationAdvisor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.user(question)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content();&lt;/pre&gt;&lt;p&gt;VectorStoreDocumentRetriever 支持通过 FilterExpression 根据元数据过滤搜索结果。你可以在实例化 VectorStoreDocumentRetriever 时指定过滤条件，也可以在每次请求运行时通过 FILTER_EXPRESSION 顾问上下文参数动态指定。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Advisor&amp;nbsp;retrievalAugmentationAdvisor&amp;nbsp;=&amp;nbsp;RetrievalAugmentationAdvisor.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.documentRetriever(VectorStoreDocumentRetriever.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(0.50)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStore(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

String&amp;nbsp;answer&amp;nbsp;=&amp;nbsp;chatClient.prompt()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(retrievalAugmentationAdvisor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(a&amp;nbsp;-&amp;gt;&amp;nbsp;a.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION,&amp;nbsp;&amp;quot;type&amp;nbsp;==&amp;nbsp;&amp;#39;Spring&amp;#39;&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.user(question)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content();&lt;/pre&gt;&lt;p&gt;更多信息请参阅 VectorStoreDocumentRetriever。&lt;/p&gt;&lt;h5&gt;高级 RAG&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Advisor&amp;nbsp;retrievalAugmentationAdvisor&amp;nbsp;=&amp;nbsp;RetrievalAugmentationAdvisor.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.queryTransformers(RewriteQueryTransformer.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatClientBuilder(chatClientBuilder.build().mutate())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.documentRetriever(VectorStoreDocumentRetriever.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(0.50)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStore(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

String&amp;nbsp;answer&amp;nbsp;=&amp;nbsp;chatClient.prompt()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.advisors(retrievalAugmentationAdvisor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.user(question)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content();&lt;/pre&gt;&lt;p&gt;你还可以使用 DocumentPostProcessor API 在将检索到的文档传递给模型之前进行后处理。例如，你可以通过该接口根据文档与查询的相关性对文档进行重排序、移除无关或冗余的文档，或压缩每个文档的内容以减少噪声和冗余信息。&lt;/p&gt;&lt;h3&gt;模块&lt;/h3&gt;&lt;p&gt;Spring AI 实现了模块化 RAG 架构，其设计灵感来源于论文《模块化 RAG：将 RAG 系统转变为乐高式可重构框架》中详述的模块化概念。&lt;/p&gt;&lt;h4&gt;检索前处理（Pre-Retrieval）&lt;/h4&gt;&lt;p&gt;检索前处理模块负责处理用户查询，以获取最优的检索结果。&lt;/p&gt;&lt;h5&gt;查询转换&lt;/h5&gt;&lt;p&gt;用于转换输入查询的组件，使查询更适用于检索任务，解决查询语句不规范、术语模糊、词汇复杂或语言不支持等问题。&lt;/p&gt;&lt;p&gt;使用 QueryTransformer 时，建议为 ChatClient.Builder 配置低温度值（例如 0.0），以确保结果更具确定性和准确性，提升检索质量。大多数对话模型的默认温度值通常过高，无法实现最优的查询转换效果，会降低检索效率。&lt;/p&gt;&lt;h6&gt;CompressionQueryTransformer&lt;/h6&gt;&lt;p&gt;CompressionQueryTransformer 使用大语言模型将对话历史和后续查询压缩为一个独立查询，保留对话的核心信息。&lt;/p&gt;&lt;p&gt;当对话历史较长且后续查询与对话上下文相关时，该转换器非常实用。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Query&amp;nbsp;query&amp;nbsp;=&amp;nbsp;Query.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.text(&amp;quot;And&amp;nbsp;what&amp;nbsp;is&amp;nbsp;its&amp;nbsp;second&amp;nbsp;largest&amp;nbsp;city?&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.history(new&amp;nbsp;UserMessage(&amp;quot;What&amp;nbsp;is&amp;nbsp;the&amp;nbsp;capital&amp;nbsp;of&amp;nbsp;Denmark?&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;AssistantMessage(&amp;quot;Copenhagen&amp;nbsp;is&amp;nbsp;the&amp;nbsp;capital&amp;nbsp;of&amp;nbsp;Denmark.&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

QueryTransformer&amp;nbsp;queryTransformer&amp;nbsp;=&amp;nbsp;CompressionQueryTransformer.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatClientBuilder(chatClientBuilder)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

Query&amp;nbsp;transformedQuery&amp;nbsp;=&amp;nbsp;queryTransformer.transform(query);&lt;/pre&gt;&lt;p&gt;该组件使用的提示词可通过构建器中的 promptTemplate() 方法自定义。&lt;/p&gt;&lt;h6&gt;RewriteQueryTransformer&lt;/h6&gt;&lt;p&gt;RewriteQueryTransformer 使用大语言模型重写用户查询，以在向目标系统（如向量库或网络搜索引擎）查询时获得更优结果。&lt;/p&gt;&lt;p&gt;当用户查询冗长、模糊或包含可能影响搜索结果质量的无关信息时，该转换器非常实用。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Query&amp;nbsp;query&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Query(&amp;quot;I&amp;#39;m&amp;nbsp;studying&amp;nbsp;machine&amp;nbsp;learning.&amp;nbsp;What&amp;nbsp;is&amp;nbsp;an&amp;nbsp;LLM?&amp;quot;);

QueryTransformer&amp;nbsp;queryTransformer&amp;nbsp;=&amp;nbsp;RewriteQueryTransformer.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatClientBuilder(chatClientBuilder)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

Query&amp;nbsp;transformedQuery&amp;nbsp;=&amp;nbsp;queryTransformer.transform(query);&lt;/pre&gt;&lt;p&gt;该组件使用的提示词可通过构建器中的 promptTemplate() 方法自定义。&lt;/p&gt;&lt;h6&gt;TranslationQueryTransformer&lt;/h6&gt;&lt;p&gt;TranslationQueryTransformer 使用大语言模型将查询翻译为文档嵌入模型支持的目标语言。如果查询已为目标语言，则直接返回；如果查询语言未知，也直接返回。&lt;/p&gt;&lt;p&gt;当嵌入模型基于特定语言训练，而用户查询为其他语言时，该转换器非常实用。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Query&amp;nbsp;query&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Query(&amp;quot;Hvad&amp;nbsp;er&amp;nbsp;Danmarks&amp;nbsp;hovedstad?&amp;quot;);

QueryTransformer&amp;nbsp;queryTransformer&amp;nbsp;=&amp;nbsp;TranslationQueryTransformer.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatClientBuilder(chatClientBuilder)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.targetLanguage(&amp;quot;english&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

Query&amp;nbsp;transformedQuery&amp;nbsp;=&amp;nbsp;queryTransformer.transform(query);&lt;/pre&gt;&lt;p&gt;该组件使用的提示词可通过构建器中的 promptTemplate() 方法自定义。&lt;/p&gt;&lt;h5&gt;查询扩展&lt;/h5&gt;&lt;p&gt;用于将输入查询扩展为多个查询的组件，通过提供替代查询语句或将复杂问题拆解为简单子查询，解决查询语句不规范等问题。&lt;/p&gt;&lt;h6&gt;MultiQueryExpander&lt;/h6&gt;&lt;p&gt;MultiQueryExpander 使用大语言模型将一个查询扩展为多个语义不同的变体，覆盖不同视角，有助于检索更多上下文信息，提升获取相关结果的概率。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;MultiQueryExpander&amp;nbsp;queryExpander&amp;nbsp;=&amp;nbsp;MultiQueryExpander.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatClientBuilder(chatClientBuilder)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.numberOfQueries(3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
List&amp;lt;Query&amp;gt;&amp;nbsp;queries&amp;nbsp;=&amp;nbsp;queryExpander.expand(new&amp;nbsp;Query(&amp;quot;How&amp;nbsp;to&amp;nbsp;run&amp;nbsp;a&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;app?&amp;quot;));&lt;/pre&gt;&lt;p&gt;默认情况下，MultiQueryExpander 会将原始查询包含在扩展查询列表中。你可以通过构建器中的 includeOriginal 方法禁用该行为。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;MultiQueryExpander&amp;nbsp;queryExpander&amp;nbsp;=&amp;nbsp;MultiQueryExpander.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.chatClientBuilder(chatClientBuilder)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.includeOriginal(false)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/pre&gt;&lt;p&gt;该组件使用的提示词可通过构建器中的 promptTemplate() 方法自定义。&lt;/p&gt;&lt;h4&gt;检索（Retrieval）&lt;/h4&gt;&lt;p&gt;检索模块负责查询向量库等数据系统，检索最相关的文档。&lt;/p&gt;&lt;h5&gt;文档搜索&lt;/h5&gt;&lt;p&gt;负责从底层数据源（如搜索引擎、向量库、数据库或知识图谱）检索文档的组件。&lt;/p&gt;&lt;h6&gt;VectorStoreDocumentRetriever&lt;/h6&gt;&lt;p&gt;VectorStoreDocumentRetriever 从向量库中检索与输入查询语义相似的文档。它支持基于元数据过滤、相似度阈值和 Top-K 结果筛选。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;DocumentRetriever&amp;nbsp;retriever&amp;nbsp;=&amp;nbsp;VectorStoreDocumentRetriever.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStore(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.similarityThreshold(0.73)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topK(5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression(new&amp;nbsp;FilterExpressionBuilder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.eq(&amp;quot;genre&amp;quot;,&amp;nbsp;&amp;quot;fairytale&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;retriever.retrieve(new&amp;nbsp;Query(&amp;quot;What&amp;nbsp;is&amp;nbsp;the&amp;nbsp;main&amp;nbsp;character&amp;nbsp;of&amp;nbsp;the&amp;nbsp;story?&amp;quot;));&lt;/pre&gt;&lt;p&gt;过滤表达式可以是静态或动态的。对于动态过滤表达式，你可以传入一个 Supplier。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;DocumentRetriever&amp;nbsp;retriever&amp;nbsp;=&amp;nbsp;VectorStoreDocumentRetriever.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.vectorStore(vectorStore)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterExpression(()&amp;nbsp;-&amp;gt;&amp;nbsp;new&amp;nbsp;FilterExpressionBuilder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.eq(&amp;quot;tenant&amp;quot;,&amp;nbsp;TenantContextHolder.getTenantIdentifier())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;retriever.retrieve(new&amp;nbsp;Query(&amp;quot;What&amp;nbsp;are&amp;nbsp;the&amp;nbsp;KPIs&amp;nbsp;for&amp;nbsp;the&amp;nbsp;next&amp;nbsp;semester?&amp;quot;));&lt;/pre&gt;&lt;p&gt;你还可以通过 Query API 使用 FILTER_EXPRESSION 参数提供请求级别的过滤表达式。如果同时指定了请求级别和检索器级别的过滤表达式，请求级别的优先级更高。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Query&amp;nbsp;query&amp;nbsp;=&amp;nbsp;Query.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.text(&amp;quot;Who&amp;nbsp;is&amp;nbsp;Anacletus?&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.context(Map.of(VectorStoreDocumentRetriever.FILTER_EXPRESSION,&amp;nbsp;&amp;quot;location&amp;nbsp;==&amp;nbsp;&amp;#39;Whispering&amp;nbsp;Woods&amp;#39;&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
List&amp;lt;Document&amp;gt;&amp;nbsp;retrievedDocuments&amp;nbsp;=&amp;nbsp;documentRetriever.retrieve(query);&lt;/pre&gt;&lt;h5&gt;文档合并&lt;/h5&gt;&lt;p&gt;用于将基于多个查询、从多个数据源检索到的文档合并为单个文档集合的组件。在合并过程中，该组件还可处理重复文档和互相关排序策略。&lt;/p&gt;&lt;h6&gt;ConcatenationDocumentJoiner&lt;/h6&gt;&lt;p&gt;ConcatenationDocumentJoiner 通过拼接的方式，将基于多个查询、从多个数据源检索到的文档合并为单个文档集合。如果存在重复文档，保留首次出现的文档，每个文档的分数保持不变。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;Map&amp;lt;Query,&amp;nbsp;List&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt;&amp;gt;&amp;nbsp;documentsForQuery&amp;nbsp;=&amp;nbsp;...
DocumentJoiner&amp;nbsp;documentJoiner&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ConcatenationDocumentJoiner();
List&amp;lt;Document&amp;gt;&amp;nbsp;documents&amp;nbsp;=&amp;nbsp;documentJoiner.join(documentsForQuery);&lt;/pre&gt;&lt;h4&gt;检索后处理（Post-Retrieval）&lt;/h4&gt;&lt;p&gt;检索后处理模块负责处理检索到的文档，以获取最优的生成结果。&lt;/p&gt;&lt;h5&gt;文档后处理&lt;/h5&gt;&lt;p&gt;基于查询对检索到的文档进行后处理的组件，解决中间信息丢失、模型上下文长度限制、减少检索信息中的噪声和冗余等问题。&lt;/p&gt;&lt;p&gt;例如，该组件可根据文档与查询的相关性排序、移除无关或冗余的文档，或压缩每个文档的内容以减少噪声和冗余。&lt;/p&gt;&lt;h4&gt;生成（Generation）&lt;/h4&gt;&lt;p&gt;生成模块负责根据用户查询和检索到的文档生成最终响应。&lt;/p&gt;&lt;h5&gt;查询增强&lt;/h5&gt;&lt;p&gt;为输入查询补充额外数据的组件，用于为大语言模型提供回答用户查询所需的上下文。&lt;/p&gt;&lt;h6&gt;ContextualQueryAugmenter&lt;/h6&gt;&lt;p&gt;ContextualQueryAugmenter 使用提供的文档内容中的上下文数据增强用户查询。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;QueryAugmenter&amp;nbsp;queryAugmenter&amp;nbsp;=&amp;nbsp;ContextualQueryAugmenter.builder().build();&lt;/pre&gt;&lt;p&gt;默认情况下，ContextualQueryAugmenter 不允许检索到的上下文为空。若上下文为空，组件会指示模型不回答用户查询。&lt;/p&gt;&lt;p&gt;你可以启用 allowEmptyContext 选项，允许模型在检索上下文为空时仍生成响应。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;QueryAugmenter&amp;nbsp;queryAugmenter&amp;nbsp;=&amp;nbsp;ContextualQueryAugmenter.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.allowEmptyContext(true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/pre&gt;&lt;p&gt;该组件使用的提示词可通过构建器中的 promptTemplate() 和 emptyContextPromptTemplate() 方法自定义。&lt;/p&gt;</description><pubDate>Mon, 18 May 2026 14:06:46 +0800</pubDate></item><item><title>Spring AI MCP注解示例</title><link>https://catnav.cn/post/83.html</link><description>&lt;p&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;本页面提供在Spring AI应用中使用MCP注解的全面示例。&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;完整应用示例&lt;/h3&gt;&lt;h4&gt;简易计算器服务端&lt;/h4&gt;&lt;p&gt;提供计算器工具的MCP服务端完整示例：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@SpringBootApplication
public&amp;nbsp;class&amp;nbsp;CalculatorServerApplication&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(CalculatorServerApplication.class,&amp;nbsp;args);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Component
public&amp;nbsp;class&amp;nbsp;CalculatorTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;add&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Add&amp;nbsp;two&amp;nbsp;numbers&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;double&amp;nbsp;add(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;First&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;a,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Second&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;b)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;a&amp;nbsp;+&amp;nbsp;b;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;subtract&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Subtract&amp;nbsp;two&amp;nbsp;numbers&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;double&amp;nbsp;subtract(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;First&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;a,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Second&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;b)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;a&amp;nbsp;-&amp;nbsp;b;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;multiply&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Multiply&amp;nbsp;two&amp;nbsp;numbers&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;double&amp;nbsp;multiply(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;First&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;a,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Second&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;b)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;a&amp;nbsp;*&amp;nbsp;b;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;divide&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Divide&amp;nbsp;two&amp;nbsp;numbers&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;double&amp;nbsp;divide(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Dividend&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;dividend,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Divisor&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;divisor)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(divisor&amp;nbsp;==&amp;nbsp;0)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;IllegalArgumentException(&amp;quot;Division&amp;nbsp;by&amp;nbsp;zero&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;dividend&amp;nbsp;/&amp;nbsp;divisor;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;calculate-expression&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Calculate&amp;nbsp;a&amp;nbsp;complex&amp;nbsp;mathematical&amp;nbsp;expression&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;CallToolResult&amp;nbsp;calculateExpression(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CallToolRequest&amp;nbsp;request,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapargs&amp;nbsp;=&amp;nbsp;request.arguments();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;expression&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;args.get(&amp;quot;expression&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Use&amp;nbsp;convenient&amp;nbsp;logging&amp;nbsp;method
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.info(&amp;quot;Calculating:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;expression);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;double&amp;nbsp;result&amp;nbsp;=&amp;nbsp;evaluateExpression(expression);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CallToolResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addTextContent(&amp;quot;Result:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;result)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(Exception&amp;nbsp;e)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CallToolResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.isError(true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addTextContent(&amp;quot;Error:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;e.getMessage())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;配置：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;spring:
&amp;nbsp;&amp;nbsp;ai:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mcp:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name:&amp;nbsp;calculator-server
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;version:&amp;nbsp;1.0.0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;SYNC
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol:&amp;nbsp;SSE&amp;nbsp;&amp;nbsp;#&amp;nbsp;or&amp;nbsp;STDIO,&amp;nbsp;STREAMABLE
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;capabilities:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tool:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resource:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;prompt:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;completion:&amp;nbsp;true&lt;/pre&gt;&lt;h4&gt;文档处理服务端&lt;/h4&gt;&lt;p&gt;包含资源和提示词的文档处理服务端示例：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;DocumentServer&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Mapdocuments&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ConcurrentHashMap&amp;lt;&amp;gt;();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpResource(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uri&amp;nbsp;=&amp;nbsp;&amp;quot;document://{id}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Document&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Access&amp;nbsp;stored&amp;nbsp;documents&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ReadResourceResult&amp;nbsp;getDocument(String&amp;nbsp;id,&amp;nbsp;McpMeta&amp;nbsp;meta)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Document&amp;nbsp;doc&amp;nbsp;=&amp;nbsp;documents.get(id);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(doc&amp;nbsp;==&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;document://&amp;quot;&amp;nbsp;+&amp;nbsp;id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;&amp;quot;Document&amp;nbsp;not&amp;nbsp;found&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Check&amp;nbsp;access&amp;nbsp;permissions&amp;nbsp;from&amp;nbsp;metadata
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;accessLevel&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;accessLevel&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(&amp;quot;restricted&amp;quot;.equals(doc.getClassification())&amp;nbsp;&amp;amp;&amp;amp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;!&amp;quot;admin&amp;quot;.equals(accessLevel))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;document://&amp;quot;&amp;nbsp;+&amp;nbsp;id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;&amp;quot;Access&amp;nbsp;denied&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;document://&amp;quot;&amp;nbsp;+&amp;nbsp;id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;doc.getMimeType(),&amp;nbsp;doc.getContent())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;analyze-document&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Analyze&amp;nbsp;document&amp;nbsp;content&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;analyzeDocument(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Document&amp;nbsp;ID&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;docId,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Analysis&amp;nbsp;type&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;String&amp;nbsp;type)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Document&amp;nbsp;doc&amp;nbsp;=&amp;nbsp;documents.get(docId);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(doc&amp;nbsp;==&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Document&amp;nbsp;not&amp;nbsp;found&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Access&amp;nbsp;progress&amp;nbsp;token&amp;nbsp;from&amp;nbsp;context
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;progressToken&amp;nbsp;=&amp;nbsp;context.request().progressToken();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.progress(p&amp;nbsp;-&amp;gt;&amp;nbsp;p.progress(0.0).total(1.0).message(&amp;quot;Starting&amp;nbsp;analysis&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Perform&amp;nbsp;analysis
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;analysisType&amp;nbsp;=&amp;nbsp;type&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;?&amp;nbsp;type&amp;nbsp;:&amp;nbsp;&amp;quot;summary&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;result&amp;nbsp;=&amp;nbsp;performAnalysis(doc,&amp;nbsp;analysisType);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.progress(p&amp;nbsp;-&amp;gt;&amp;nbsp;p.progress(1.0).total(1.0).message(&amp;quot;Analysis&amp;nbsp;complete&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpPrompt(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;document-summary&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Generate&amp;nbsp;document&amp;nbsp;summary&amp;nbsp;prompt&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;GetPromptResult&amp;nbsp;documentSummaryPrompt(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;docId&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;docId,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;length&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;String&amp;nbsp;length)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Document&amp;nbsp;doc&amp;nbsp;=&amp;nbsp;documents.get(docId);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(doc&amp;nbsp;==&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;GetPromptResult(&amp;quot;Error&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(new&amp;nbsp;PromptMessage(Role.SYSTEM,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextContent(&amp;quot;Document&amp;nbsp;not&amp;nbsp;found&amp;quot;))));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;promptText&amp;nbsp;=&amp;nbsp;String.format(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Please&amp;nbsp;summarize&amp;nbsp;the&amp;nbsp;following&amp;nbsp;document&amp;nbsp;in&amp;nbsp;%s:\n\n%s&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;length&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;?&amp;nbsp;length&amp;nbsp;:&amp;nbsp;&amp;quot;a&amp;nbsp;few&amp;nbsp;paragraphs&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;doc.getContent()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;GetPromptResult(&amp;quot;Document&amp;nbsp;Summary&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(new&amp;nbsp;PromptMessage(Role.USER,&amp;nbsp;new&amp;nbsp;TextContent(promptText))));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpComplete(prompt&amp;nbsp;=&amp;nbsp;&amp;quot;document-summary&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ListcompleteDocumentId(String&amp;nbsp;prefix)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;documents.keySet().stream()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(id&amp;nbsp;-&amp;gt;&amp;nbsp;id.startsWith(prefix))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sorted()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.limit(10)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.toList();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;带处理器的MCP客户端&lt;/h4&gt;&lt;p&gt;包含各类处理器的完整MCP客户端应用：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@SpringBootApplication
public&amp;nbsp;class&amp;nbsp;McpClientApplication&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(McpClientApplication.class,&amp;nbsp;args);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Component
public&amp;nbsp;class&amp;nbsp;ClientHandlers&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;Logger&amp;nbsp;logger&amp;nbsp;=&amp;nbsp;LoggerFactory.getLogger(ClientHandlers.class);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;ProgressTracker&amp;nbsp;progressTracker&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ProgressTracker();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;ChatModel&amp;nbsp;chatModel;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ClientHandlers(@Lazy&amp;nbsp;ChatModel&amp;nbsp;chatModel)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.chatModel&amp;nbsp;=&amp;nbsp;chatModel;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpLogging(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleLogging(LoggingMessageNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;switch&amp;nbsp;(notification.level())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;ERROR:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.error(&amp;quot;[MCP]&amp;nbsp;{}&amp;nbsp;-&amp;nbsp;{}&amp;quot;,&amp;nbsp;notification.logger(),&amp;nbsp;notification.data());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;WARNING:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.warn(&amp;quot;[MCP]&amp;nbsp;{}&amp;nbsp;-&amp;nbsp;{}&amp;quot;,&amp;nbsp;notification.logger(),&amp;nbsp;notification.data());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;INFO:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&amp;quot;[MCP]&amp;nbsp;{}&amp;nbsp;-&amp;nbsp;{}&amp;quot;,&amp;nbsp;notification.logger(),&amp;nbsp;notification.data());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.debug(&amp;quot;[MCP]&amp;nbsp;{}&amp;nbsp;-&amp;nbsp;{}&amp;quot;,&amp;nbsp;notification.logger(),&amp;nbsp;notification.data());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpSampling(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;CreateMessageResult&amp;nbsp;handleSampling(CreateMessageRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Use&amp;nbsp;Spring&amp;nbsp;AI&amp;nbsp;ChatModel&amp;nbsp;for&amp;nbsp;sampling
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Listmessages&amp;nbsp;=&amp;nbsp;request.messages().stream()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(msg&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(msg.role()&amp;nbsp;==&amp;nbsp;Role.USER)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;UserMessage(((TextContent)&amp;nbsp;msg.content()).text());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;AssistantMessage.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content(((TextContent)&amp;nbsp;msg.content()).text())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.toList();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ChatResponse&amp;nbsp;response&amp;nbsp;=&amp;nbsp;chatModel.call(new&amp;nbsp;Prompt(messages));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CreateMessageResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.role(Role.ASSISTANT)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content(new&amp;nbsp;TextContent(response.getResult().getOutput().getText()))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.model(request.modelPreferences().hints().get(0).name())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpElicitation(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ElicitResult&amp;nbsp;handleElicitation(ElicitRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;In&amp;nbsp;a&amp;nbsp;real&amp;nbsp;application,&amp;nbsp;this&amp;nbsp;would&amp;nbsp;show&amp;nbsp;a&amp;nbsp;UI&amp;nbsp;dialog
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MapuserData&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&amp;quot;Elicitation&amp;nbsp;requested:&amp;nbsp;{}&amp;quot;,&amp;nbsp;request.message());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Simulate&amp;nbsp;user&amp;nbsp;input&amp;nbsp;based&amp;nbsp;on&amp;nbsp;schema
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapschema&amp;nbsp;=&amp;nbsp;request.requestedSchema();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(schema&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;schema.containsKey(&amp;quot;properties&amp;quot;))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapproperties&amp;nbsp;=&amp;nbsp;(Map)&amp;nbsp;schema.get(&amp;quot;properties&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;properties.forEach((key,&amp;nbsp;value)&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;In&amp;nbsp;real&amp;nbsp;app,&amp;nbsp;prompt&amp;nbsp;user&amp;nbsp;for&amp;nbsp;each&amp;nbsp;field
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userData.put(key,&amp;nbsp;getDefaultValueForProperty(key,&amp;nbsp;value));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ElicitResult(ElicitResult.Action.ACCEPT,&amp;nbsp;userData);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgress(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleProgress(ProgressNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressTracker.update(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;notification.progressToken(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;notification.progress(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;notification.total(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;notification.message()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Update&amp;nbsp;UI&amp;nbsp;or&amp;nbsp;send&amp;nbsp;websocket&amp;nbsp;notification
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;broadcastProgress(notification);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleServer1ToolsChanged(Listtools)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&amp;quot;Server1&amp;nbsp;tools&amp;nbsp;updated:&amp;nbsp;{}&amp;nbsp;tools&amp;nbsp;available&amp;quot;,&amp;nbsp;tools.size());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Update&amp;nbsp;tool&amp;nbsp;registry
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;toolRegistry.updateServerTools(&amp;quot;server1&amp;quot;,&amp;nbsp;tools);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Notify&amp;nbsp;UI&amp;nbsp;to&amp;nbsp;refresh&amp;nbsp;tool&amp;nbsp;list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;eventBus.publish(new&amp;nbsp;ToolsUpdatedEvent(&amp;quot;server1&amp;quot;,&amp;nbsp;tools));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpResourceListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleServer1ResourcesChanged(Listresources)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&amp;quot;Server1&amp;nbsp;resources&amp;nbsp;updated:&amp;nbsp;{}&amp;nbsp;resources&amp;nbsp;available&amp;quot;,&amp;nbsp;resources.size());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Clear&amp;nbsp;resource&amp;nbsp;cache&amp;nbsp;for&amp;nbsp;this&amp;nbsp;server
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resourceCache.clearServer(&amp;quot;server1&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Register&amp;nbsp;new&amp;nbsp;resources
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resources.forEach(resource&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resourceCache.register(&amp;quot;server1&amp;quot;,&amp;nbsp;resource));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;配置：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;spring:
&amp;nbsp;&amp;nbsp;ai:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mcp:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;client:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;SYNC
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;initialized:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request-timeout:&amp;nbsp;30s
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;annotation-scanner:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sse:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connections:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server1:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url:&amp;nbsp;http://localhost:8080
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stdio:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connections:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local-tool:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command:&amp;nbsp;/usr/local/bin/mcp-tool
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;--mode=production&lt;/pre&gt;&lt;h3&gt;异步示例&lt;/h3&gt;&lt;h4&gt;异步工具服务端&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;AsyncDataProcessor&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;fetch-data&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Fetch&amp;nbsp;data&amp;nbsp;from&amp;nbsp;external&amp;nbsp;source&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonofetchData(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Data&amp;nbsp;source&amp;nbsp;URL&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;url,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Timeout&amp;nbsp;in&amp;nbsp;seconds&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;Integer&amp;nbsp;timeout)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Duration&amp;nbsp;timeoutDuration&amp;nbsp;=&amp;nbsp;Duration.ofSeconds(timeout&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;?&amp;nbsp;timeout&amp;nbsp;:&amp;nbsp;30);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;WebClient.create()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.get()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.uri(url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retrieve()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.bodyToMono(String.class)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(data&amp;nbsp;-&amp;gt;&amp;nbsp;new&amp;nbsp;DataResult(url,&amp;nbsp;data,&amp;nbsp;System.currentTimeMillis()))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.timeout(timeoutDuration)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onErrorReturn(new&amp;nbsp;DataResult(url,&amp;nbsp;&amp;quot;Error&amp;nbsp;fetching&amp;nbsp;data&amp;quot;,&amp;nbsp;0L));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;process-stream&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Process&amp;nbsp;data&amp;nbsp;stream&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;FluxprocessStream(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpAsyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Item&amp;nbsp;count&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;int&amp;nbsp;count)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Access&amp;nbsp;progress&amp;nbsp;token&amp;nbsp;from&amp;nbsp;context
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;progressToken&amp;nbsp;=&amp;nbsp;context.request().progressToken();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Flux.range(1,&amp;nbsp;count)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.delayElements(Duration.ofMillis(100))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.flatMap(i&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;double&amp;nbsp;progress&amp;nbsp;=&amp;nbsp;(double)&amp;nbsp;i&amp;nbsp;/&amp;nbsp;count;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;context.progress(p&amp;nbsp;-&amp;gt;&amp;nbsp;p.progress(progress).total(1.0).message(&amp;quot;Processing&amp;nbsp;item&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;i))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.thenReturn(&amp;quot;Processed&amp;nbsp;item&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;i);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.just(&amp;quot;Processed&amp;nbsp;item&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;i);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpResource(uri&amp;nbsp;=&amp;nbsp;&amp;quot;async-data://{id}&amp;quot;,&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Async&amp;nbsp;Data&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonogetAsyncData(String&amp;nbsp;id)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromCallable(()&amp;nbsp;-&amp;gt;&amp;nbsp;loadDataAsync(id))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.subscribeOn(Schedulers.boundedElastic())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(data&amp;nbsp;-&amp;gt;&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;async-data://&amp;quot;&amp;nbsp;+&amp;nbsp;id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;application/json&amp;quot;,&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;异步客户端处理器&lt;/h4&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;AsyncClientHandlers&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpSampling(clients&amp;nbsp;=&amp;nbsp;&amp;quot;async-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonohandleAsyncSampling(CreateMessageRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromCallable(()&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Prepare&amp;nbsp;request&amp;nbsp;for&amp;nbsp;LLM
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;prompt&amp;nbsp;=&amp;nbsp;extractPrompt(request);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;prompt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.flatMap(prompt&amp;nbsp;-&amp;gt;&amp;nbsp;callLLMAsync(prompt))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(response&amp;nbsp;-&amp;gt;&amp;nbsp;CreateMessageResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.role(Role.ASSISTANT)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content(new&amp;nbsp;TextContent(response))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.model(&amp;quot;gpt-4&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.timeout(Duration.ofSeconds(30));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgress(clients&amp;nbsp;=&amp;nbsp;&amp;quot;async-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonohandleAsyncProgress(ProgressNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromRunnable(()&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Update&amp;nbsp;progress&amp;nbsp;tracking
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;updateProgressAsync(notification);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.then(broadcastProgressAsync(notification))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.subscribeOn(Schedulers.parallel());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpElicitation(clients&amp;nbsp;=&amp;nbsp;&amp;quot;async-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonohandleAsyncElicitation(ElicitRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;showUserDialogAsync(request)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(userData&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(userData&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;!userData.isEmpty())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ElicitResult(ElicitResult.Action.ACCEPT,&amp;nbsp;userData);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ElicitResult(ElicitResult.Action.DECLINE,&amp;nbsp;null);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.timeout(Duration.ofMinutes(5))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onErrorReturn(new&amp;nbsp;ElicitResult(ElicitResult.Action.CANCEL,&amp;nbsp;null));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;无状态服务端示例&lt;/h3&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;StatelessTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Simple&amp;nbsp;stateless&amp;nbsp;tool
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;format-text&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Format&amp;nbsp;text&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;formatText(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Text&amp;nbsp;to&amp;nbsp;format&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;text,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Format&amp;nbsp;type&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;format)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;switch&amp;nbsp;(format.toLowerCase())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;&amp;quot;uppercase&amp;quot;&amp;nbsp;-&amp;gt;&amp;nbsp;text.toUpperCase();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;&amp;quot;lowercase&amp;quot;&amp;nbsp;-&amp;gt;&amp;nbsp;text.toLowerCase();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;&amp;quot;title&amp;quot;&amp;nbsp;-&amp;gt;&amp;nbsp;toTitleCase(text);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;&amp;quot;reverse&amp;quot;&amp;nbsp;-&amp;gt;&amp;nbsp;new&amp;nbsp;StringBuilder(text).reverse().toString();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;-&amp;gt;&amp;nbsp;text;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Stateless&amp;nbsp;with&amp;nbsp;transport&amp;nbsp;context
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;validate-json&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Validate&amp;nbsp;JSON&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;CallToolResult&amp;nbsp;validateJson(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpTransportContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;JSON&amp;nbsp;string&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;json)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ObjectMapper&amp;nbsp;mapper&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ObjectMapper();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mapper.readTree(json);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CallToolResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addTextContent(&amp;quot;Valid&amp;nbsp;JSON&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.structuredContent(Map.of(&amp;quot;valid&amp;quot;,&amp;nbsp;true))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(Exception&amp;nbsp;e)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CallToolResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addTextContent(&amp;quot;Invalid&amp;nbsp;JSON:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;e.getMessage())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.structuredContent(Map.of(&amp;quot;valid&amp;quot;,&amp;nbsp;false,&amp;nbsp;&amp;quot;error&amp;quot;,&amp;nbsp;e.getMessage()))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpResource(uri&amp;nbsp;=&amp;nbsp;&amp;quot;static://{path}&amp;quot;,&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Static&amp;nbsp;Resource&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;getStaticResource(String&amp;nbsp;path)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Simple&amp;nbsp;stateless&amp;nbsp;resource
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;loadStaticContent(path);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpPrompt(name&amp;nbsp;=&amp;nbsp;&amp;quot;template&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Template&amp;nbsp;prompt&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;GetPromptResult&amp;nbsp;templatePrompt(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;template&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;templateName,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;variables&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;String&amp;nbsp;variables)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;template&amp;nbsp;=&amp;nbsp;loadTemplate(templateName);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(variables&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template&amp;nbsp;=&amp;nbsp;substituteVariables(template,&amp;nbsp;variables);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;GetPromptResult(&amp;quot;Template:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;templateName,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(new&amp;nbsp;PromptMessage(Role.USER,&amp;nbsp;new&amp;nbsp;TextContent(template))));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;集成多LLM提供商的MCP采样&lt;/h3&gt;&lt;p&gt;本示例演示如何使用MCP采样从多个LLM提供商生成创意内容，展示基于注解的服务端和客户端实现方式。&lt;/p&gt;&lt;h4&gt;采样服务端实现&lt;/h4&gt;&lt;p&gt;服务端提供天气工具，通过MCP采样从不同LLM提供商生成诗歌：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Service
public&amp;nbsp;class&amp;nbsp;WeatherService&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;RestClient&amp;nbsp;restClient&amp;nbsp;=&amp;nbsp;RestClient.create();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;record&amp;nbsp;WeatherResponse(Current&amp;nbsp;current)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;record&amp;nbsp;Current(LocalDateTime&amp;nbsp;time,&amp;nbsp;int&amp;nbsp;interval,&amp;nbsp;double&amp;nbsp;temperature_2m)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(description&amp;nbsp;=&amp;nbsp;&amp;quot;Get&amp;nbsp;the&amp;nbsp;temperature&amp;nbsp;(in&amp;nbsp;celsius)&amp;nbsp;for&amp;nbsp;a&amp;nbsp;specific&amp;nbsp;location&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;getTemperature2(McpSyncServerExchange&amp;nbsp;exchange,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;The&amp;nbsp;location&amp;nbsp;latitude&amp;quot;)&amp;nbsp;double&amp;nbsp;latitude,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;The&amp;nbsp;location&amp;nbsp;longitude&amp;quot;)&amp;nbsp;double&amp;nbsp;longitude)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Fetch&amp;nbsp;weather&amp;nbsp;data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WeatherResponse&amp;nbsp;weatherResponse&amp;nbsp;=&amp;nbsp;restClient
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.get()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.uri(&amp;quot;https://api.open-meteo.com/v1/forecast?latitude={latitude}&amp;amp;longitude={longitude}&amp;amp;current=temperature_2m&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;latitude,&amp;nbsp;longitude)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retrieve()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(WeatherResponse.class);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;StringBuilder&amp;nbsp;openAiWeatherPoem&amp;nbsp;=&amp;nbsp;new&amp;nbsp;StringBuilder();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;StringBuilder&amp;nbsp;anthropicWeatherPoem&amp;nbsp;=&amp;nbsp;new&amp;nbsp;StringBuilder();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Send&amp;nbsp;logging&amp;nbsp;notification
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.loggingNotification(LoggingMessageNotification.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.level(LoggingLevel.INFO)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.data(&amp;quot;Start&amp;nbsp;sampling&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Check&amp;nbsp;if&amp;nbsp;client&amp;nbsp;supports&amp;nbsp;sampling
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(exchange.getClientCapabilities().sampling()&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;messageRequestBuilder&amp;nbsp;=&amp;nbsp;McpSchema.CreateMessageRequest.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.systemPrompt(&amp;quot;You&amp;nbsp;are&amp;nbsp;a&amp;nbsp;poet!&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.messages(List.of(new&amp;nbsp;McpSchema.SamplingMessage(McpSchema.Role.USER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;McpSchema.TextContent(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Please&amp;nbsp;write&amp;nbsp;a&amp;nbsp;poem&amp;nbsp;about&amp;nbsp;this&amp;nbsp;weather&amp;nbsp;forecast&amp;nbsp;(temperature&amp;nbsp;is&amp;nbsp;in&amp;nbsp;Celsius).&amp;nbsp;Use&amp;nbsp;markdown&amp;nbsp;format&amp;nbsp;:\n&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+&amp;nbsp;ModelOptionsUtils.toJsonStringPrettyPrinter(weatherResponse)))));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Request&amp;nbsp;poem&amp;nbsp;from&amp;nbsp;OpenAI
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;openAiLlmMessageRequest&amp;nbsp;=&amp;nbsp;messageRequestBuilder
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.modelPreferences(ModelPreferences.builder().addHint(&amp;quot;openai&amp;quot;).build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CreateMessageResult&amp;nbsp;openAiLlmResponse&amp;nbsp;=&amp;nbsp;exchange.createMessage(openAiLlmMessageRequest);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;openAiWeatherPoem.append(((McpSchema.TextContent)&amp;nbsp;openAiLlmResponse.content()).text());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Request&amp;nbsp;poem&amp;nbsp;from&amp;nbsp;Anthropic
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;anthropicLlmMessageRequest&amp;nbsp;=&amp;nbsp;messageRequestBuilder
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.modelPreferences(ModelPreferences.builder().addHint(&amp;quot;anthropic&amp;quot;).build())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CreateMessageResult&amp;nbsp;anthropicAiLlmResponse&amp;nbsp;=&amp;nbsp;exchange.createMessage(anthropicLlmMessageRequest);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;anthropicWeatherPoem.append(((McpSchema.TextContent)&amp;nbsp;anthropicAiLlmResponse.content()).text());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.loggingNotification(LoggingMessageNotification.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.level(LoggingLevel.INFO)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.data(&amp;quot;Finish&amp;nbsp;Sampling&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Combine&amp;nbsp;results
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;responseWithPoems&amp;nbsp;=&amp;nbsp;&amp;quot;OpenAI&amp;nbsp;poem&amp;nbsp;about&amp;nbsp;the&amp;nbsp;weather:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;openAiWeatherPoem.toString()&amp;nbsp;+&amp;nbsp;&amp;quot;\n\n&amp;quot;&amp;nbsp;+
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Anthropic&amp;nbsp;poem&amp;nbsp;about&amp;nbsp;the&amp;nbsp;weather:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;anthropicWeatherPoem.toString()&amp;nbsp;+&amp;nbsp;&amp;quot;\n&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+&amp;nbsp;ModelOptionsUtils.toJsonStringPrettyPrinter(weatherResponse);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;responseWithPoems;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;采样客户端实现&lt;/h4&gt;&lt;p&gt;客户端通过模型提示将采样请求路由至对应的LLM提供商：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Service
public&amp;nbsp;class&amp;nbsp;McpClientHandlers&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;static&amp;nbsp;final&amp;nbsp;Logger&amp;nbsp;logger&amp;nbsp;=&amp;nbsp;LoggerFactory.getLogger(McpClientHandlers.class);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Autowired
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MapchatClients;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgress(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;progressHandler(ProgressNotification&amp;nbsp;progressNotification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&amp;quot;MCP&amp;nbsp;PROGRESS:&amp;nbsp;[{}]&amp;nbsp;progress:&amp;nbsp;{}&amp;nbsp;total:&amp;nbsp;{}&amp;nbsp;message:&amp;nbsp;{}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressNotification.progressToken(),&amp;nbsp;progressNotification.progress(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressNotification.total(),&amp;nbsp;progressNotification.message());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpLogging(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;loggingHandler(LoggingMessageNotification&amp;nbsp;loggingMessage)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&amp;quot;MCP&amp;nbsp;LOGGING:&amp;nbsp;[{}]&amp;nbsp;{}&amp;quot;,&amp;nbsp;loggingMessage.level(),&amp;nbsp;loggingMessage.data());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpSampling(clients&amp;nbsp;=&amp;nbsp;&amp;quot;server1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;CreateMessageResult&amp;nbsp;samplingHandler(CreateMessageRequest&amp;nbsp;llmRequest)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&amp;quot;MCP&amp;nbsp;SAMPLING:&amp;nbsp;{}&amp;quot;,&amp;nbsp;llmRequest);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Extract&amp;nbsp;user&amp;nbsp;prompt&amp;nbsp;and&amp;nbsp;model&amp;nbsp;hint
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;userPrompt&amp;nbsp;=&amp;nbsp;((McpSchema.TextContent)&amp;nbsp;llmRequest.messages().get(0).content()).text();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;modelHint&amp;nbsp;=&amp;nbsp;llmRequest.modelPreferences().hints().get(0).name();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Find&amp;nbsp;appropriate&amp;nbsp;ChatClient&amp;nbsp;based&amp;nbsp;on&amp;nbsp;model&amp;nbsp;hint
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ChatClient&amp;nbsp;hintedChatClient&amp;nbsp;=&amp;nbsp;chatClients.entrySet().stream()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(e&amp;nbsp;-&amp;gt;&amp;nbsp;e.getKey().contains(modelHint))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.findFirst()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.orElseThrow()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.getValue();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Generate&amp;nbsp;response&amp;nbsp;using&amp;nbsp;the&amp;nbsp;selected&amp;nbsp;model
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;response&amp;nbsp;=&amp;nbsp;hintedChatClient.prompt()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.system(llmRequest.systemPrompt())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.user(userPrompt)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.call()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CreateMessageResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content(new&amp;nbsp;McpSchema.TextContent(response))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;客户端应用配置&lt;/h4&gt;&lt;p&gt;在客户端应用中注册MCP工具和处理器：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@SpringBootApplication
public&amp;nbsp;class&amp;nbsp;McpClientApplication&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(McpClientApplication.class,&amp;nbsp;args).close();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;CommandLineRunner&amp;nbsp;predefinedQuestions(OpenAiChatModel&amp;nbsp;openAiChatModel,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ToolCallbackProvider&amp;nbsp;mcpToolProvider)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;args&amp;nbsp;-&amp;gt;&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ChatClient&amp;nbsp;chatClient&amp;nbsp;=&amp;nbsp;ChatClient.builder(openAiChatModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.defaultToolCallbacks(mcpToolProvider)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;userQuestion&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;What&amp;nbsp;is&amp;nbsp;the&amp;nbsp;weather&amp;nbsp;in&amp;nbsp;Amsterdam&amp;nbsp;right&amp;nbsp;now?
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Please&amp;nbsp;incorporate&amp;nbsp;all&amp;nbsp;creative&amp;nbsp;responses&amp;nbsp;from&amp;nbsp;all&amp;nbsp;LLM&amp;nbsp;providers.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;After&amp;nbsp;the&amp;nbsp;other&amp;nbsp;providers&amp;nbsp;add&amp;nbsp;a&amp;nbsp;poem&amp;nbsp;that&amp;nbsp;synthesizes&amp;nbsp;the&amp;nbsp;poems&amp;nbsp;from&amp;nbsp;all&amp;nbsp;the&amp;nbsp;other&amp;nbsp;providers.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;&amp;gt;&amp;nbsp;USER:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;userQuestion);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;&amp;gt;&amp;nbsp;ASSISTANT:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;chatClient.prompt(userQuestion).call().content());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;配置&lt;/h4&gt;&lt;h5&gt;服务端配置&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;#&amp;nbsp;Server&amp;nbsp;application.properties
spring.ai.mcp.server.name=mcp-sampling-server-annotations
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.protocol=STREAMABLE
spring.main.banner-mode=off&lt;/pre&gt;&lt;h5&gt;客户端配置&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;#&amp;nbsp;Client&amp;nbsp;application.properties
spring.application.name=mcp
spring.main.web-application-type=none

#&amp;nbsp;Disable&amp;nbsp;default&amp;nbsp;chat&amp;nbsp;client&amp;nbsp;auto-configuration&amp;nbsp;for&amp;nbsp;multiple&amp;nbsp;models
spring.ai.chat.client.enabled=false

#&amp;nbsp;API&amp;nbsp;keys
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}

#&amp;nbsp;MCP&amp;nbsp;client&amp;nbsp;connection&amp;nbsp;using&amp;nbsp;stateless-http&amp;nbsp;transport
spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:8080

#&amp;nbsp;Disable&amp;nbsp;tool&amp;nbsp;callback&amp;nbsp;to&amp;nbsp;prevent&amp;nbsp;cyclic&amp;nbsp;dependencies
spring.ai.mcp.client.toolcallback.enabled=false&lt;/pre&gt;&lt;h4&gt;演示的核心功能&lt;/h4&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;多模型采样：服务端通过模型提示向多个LLM提供商请求内容&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;基于注解的处理器：客户端使用@McpSampling、@McpLogging和@McpProgress注解&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;无状态HTTP传输：采用可流式协议进行通信&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;创意内容生成：从不同模型生成天气相关诗歌&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;统一响应处理：将多个提供商的响应整合为单一结果&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;示例输出&lt;/h4&gt;&lt;p&gt;运行客户端时，输出如下：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;&amp;gt;&amp;nbsp;USER:&amp;nbsp;What&amp;nbsp;is&amp;nbsp;the&amp;nbsp;weather&amp;nbsp;in&amp;nbsp;Amsterdam&amp;nbsp;right&amp;nbsp;now?
Please&amp;nbsp;incorporate&amp;nbsp;all&amp;nbsp;creative&amp;nbsp;responses&amp;nbsp;from&amp;nbsp;all&amp;nbsp;LLM&amp;nbsp;providers.
After&amp;nbsp;the&amp;nbsp;other&amp;nbsp;providers&amp;nbsp;add&amp;nbsp;a&amp;nbsp;poem&amp;nbsp;that&amp;nbsp;synthesizes&amp;nbsp;the&amp;nbsp;poems&amp;nbsp;from&amp;nbsp;all&amp;nbsp;the&amp;nbsp;other&amp;nbsp;providers.

&amp;gt;&amp;nbsp;ASSISTANT:
OpenAI&amp;nbsp;poem&amp;nbsp;about&amp;nbsp;the&amp;nbsp;weather:
**Amsterdam&amp;#39;s&amp;nbsp;Winter&amp;nbsp;Whisper**
*Temperature:&amp;nbsp;4.2°C*

In&amp;nbsp;Amsterdam&amp;#39;s&amp;nbsp;embrace,&amp;nbsp;where&amp;nbsp;canals&amp;nbsp;reflect&amp;nbsp;the&amp;nbsp;sky,
A&amp;nbsp;gentle&amp;nbsp;chill&amp;nbsp;of&amp;nbsp;4.2&amp;nbsp;degrees&amp;nbsp;drifts&amp;nbsp;by...

Anthropic&amp;nbsp;poem&amp;nbsp;about&amp;nbsp;the&amp;nbsp;weather:
**Canal-Side&amp;nbsp;Contemplation**
*Current&amp;nbsp;conditions:&amp;nbsp;4.2°C*

Along&amp;nbsp;the&amp;nbsp;waterways&amp;nbsp;where&amp;nbsp;bicycles&amp;nbsp;rest,
The&amp;nbsp;winter&amp;nbsp;air&amp;nbsp;puts&amp;nbsp;Amsterdam&amp;nbsp;to&amp;nbsp;test...

Weather&amp;nbsp;Data:
{
&amp;nbsp;&amp;nbsp;&amp;quot;current&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;time&amp;quot;:&amp;nbsp;&amp;quot;2025-01-23T11:00&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;interval&amp;quot;:&amp;nbsp;900,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;temperature_2m&amp;quot;:&amp;nbsp;4.2
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;与Spring AI集成&lt;/h3&gt;&lt;p&gt;MCP工具与Spring AI函数调用集成示例：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@RestController
@RequestMapping(&amp;quot;/chat&amp;quot;)
public&amp;nbsp;class&amp;nbsp;ChatController&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;ChatModel&amp;nbsp;chatModel;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;final&amp;nbsp;SyncMcpToolCallbackProvider&amp;nbsp;toolCallbackProvider;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ChatController(ChatModel&amp;nbsp;chatModel,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SyncMcpToolCallbackProvider&amp;nbsp;toolCallbackProvider)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.chatModel&amp;nbsp;=&amp;nbsp;chatModel;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.toolCallbackProvider&amp;nbsp;=&amp;nbsp;toolCallbackProvider;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostMapping
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ChatResponse&amp;nbsp;chat(@RequestBody&amp;nbsp;ChatRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Get&amp;nbsp;MCP&amp;nbsp;tools&amp;nbsp;as&amp;nbsp;Spring&amp;nbsp;AI&amp;nbsp;function&amp;nbsp;callbacks
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ToolCallback[]&amp;nbsp;mcpTools&amp;nbsp;=&amp;nbsp;toolCallbackProvider.getToolCallbacks();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Create&amp;nbsp;prompt&amp;nbsp;with&amp;nbsp;MCP&amp;nbsp;tools
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Prompt&amp;nbsp;prompt&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Prompt(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request.getMessage(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ChatOptionsBuilder.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withTools(mcpTools)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Call&amp;nbsp;chat&amp;nbsp;model&amp;nbsp;with&amp;nbsp;MCP&amp;nbsp;tools&amp;nbsp;available
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;chatModel.call(prompt);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Component
public&amp;nbsp;class&amp;nbsp;WeatherTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;get-weather&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Get&amp;nbsp;current&amp;nbsp;weather&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;WeatherInfo&amp;nbsp;getWeather(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;City&amp;nbsp;name&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;city,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Units&amp;nbsp;(metric/imperial)&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;String&amp;nbsp;units)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;unit&amp;nbsp;=&amp;nbsp;units&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;?&amp;nbsp;units&amp;nbsp;:&amp;nbsp;&amp;quot;metric&amp;quot;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Call&amp;nbsp;weather&amp;nbsp;API
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;weatherService.getCurrentWeather(city,&amp;nbsp;unit);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;get-forecast&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Get&amp;nbsp;weather&amp;nbsp;forecast&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ForecastInfo&amp;nbsp;getForecast(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;City&amp;nbsp;name&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;city,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Days&amp;nbsp;(1-7)&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;Integer&amp;nbsp;days)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp;forecastDays&amp;nbsp;=&amp;nbsp;days&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;?&amp;nbsp;days&amp;nbsp;:&amp;nbsp;3;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;weatherService.getForecast(city,&amp;nbsp;forecastDays);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;</description><pubDate>Mon, 18 May 2026 13:46:41 +0800</pubDate></item><item><title>Spring AI MCP注解特殊参数</title><link>https://catnav.cn/post/82.html</link><description>&lt;p&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;MCP注解支持多种特殊参数类型，这些参数可为被注解的方法提供额外上下文和功能。它们由框架自动注入，且不会被纳入JSON模式生成。&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;特殊参数类型&lt;/h3&gt;&lt;h4&gt;McpMeta&lt;/h4&gt;&lt;p&gt;McpMeta类用于获取MCP请求、通知和结果中的元数据。&lt;/p&gt;&lt;h5&gt;概述&lt;/h5&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;作为方法参数使用时会被自动注入&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;不计入参数数量限制，且不参与JSON模式生成&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;通过get(String key)方法可便捷获取元数据&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若请求中无元数据，将注入空的McpMeta对象&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;在工具中的使用&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;contextual-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;metadata&amp;nbsp;access&amp;quot;)
public&amp;nbsp;String&amp;nbsp;processWithContext(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;nbsp;data&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;data,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpMeta&amp;nbsp;meta)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Access&amp;nbsp;metadata&amp;nbsp;from&amp;nbsp;the&amp;nbsp;request
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;userId&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;userId&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;sessionId&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;sessionId&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;userRole&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;userRole&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Use&amp;nbsp;metadata&amp;nbsp;to&amp;nbsp;customize&amp;nbsp;behavior
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(&amp;quot;admin&amp;quot;.equals(userRole))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;processAsAdmin(data,&amp;nbsp;userId);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;processAsUser(data,&amp;nbsp;userId);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h5&gt;在资源中的使用&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpResource(uri&amp;nbsp;=&amp;nbsp;&amp;quot;secure-data://{id}&amp;quot;,&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Secure&amp;nbsp;Data&amp;quot;)
public&amp;nbsp;ReadResourceResult&amp;nbsp;getSecureData(String&amp;nbsp;id,&amp;nbsp;McpMeta&amp;nbsp;meta)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;requestingUser&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;requestingUser&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;accessLevel&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;accessLevel&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Check&amp;nbsp;access&amp;nbsp;permissions&amp;nbsp;using&amp;nbsp;metadata
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!&amp;quot;admin&amp;quot;.equals(accessLevel))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;secure-data://&amp;quot;&amp;nbsp;+&amp;nbsp;id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;&amp;quot;Access&amp;nbsp;denied&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;data&amp;nbsp;=&amp;nbsp;loadSecureData(id);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;secure-data://&amp;quot;&amp;nbsp;+&amp;nbsp;id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
}&lt;/pre&gt;&lt;h5&gt;在提示词中的使用&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpPrompt(name&amp;nbsp;=&amp;nbsp;&amp;quot;localized-prompt&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Localized&amp;nbsp;prompt&amp;nbsp;generation&amp;quot;)
public&amp;nbsp;GetPromptResult&amp;nbsp;localizedPrompt(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;topic&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;topic,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpMeta&amp;nbsp;meta)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;language&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;language&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;region&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;region&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Generate&amp;nbsp;localized&amp;nbsp;content&amp;nbsp;based&amp;nbsp;on&amp;nbsp;metadata
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;message&amp;nbsp;=&amp;nbsp;generateLocalizedMessage(topic,&amp;nbsp;language,&amp;nbsp;region);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;GetPromptResult(&amp;quot;Localized&amp;nbsp;Prompt&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(new&amp;nbsp;PromptMessage(Role.ASSISTANT,&amp;nbsp;new&amp;nbsp;TextContent(message)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
}&lt;/pre&gt;&lt;h4&gt;@McpProgressToken&lt;/h4&gt;&lt;p&gt;@McpProgressToken注解用于标记接收MCP请求进度令牌的参数。&lt;/p&gt;&lt;h5&gt;概述&lt;/h5&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;参数类型必须为String&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;自动接收请求中的进度令牌值&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;不纳入生成的JSON模式&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若请求中无进度令牌，将注入null&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;用于追踪耗时较长的操作&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;在工具中的使用&lt;/h5&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;long-operation&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Long-running&amp;nbsp;operation&amp;nbsp;with&amp;nbsp;progress&amp;quot;)
public&amp;nbsp;String&amp;nbsp;performLongOperation(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgressToken&amp;nbsp;String&amp;nbsp;progressToken,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Operation&amp;nbsp;name&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;operation,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Duration&amp;nbsp;in&amp;nbsp;seconds&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;int&amp;nbsp;duration,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncServerExchange&amp;nbsp;exchange)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Send&amp;nbsp;initial&amp;nbsp;progress
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.progressNotification(new&amp;nbsp;ProgressNotification(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;0.0,&amp;nbsp;1.0,&amp;nbsp;&amp;quot;Starting&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;operation));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Simulate&amp;nbsp;work&amp;nbsp;with&amp;nbsp;progress&amp;nbsp;updates
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;(int&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1;&amp;nbsp;i&amp;nbsp;在资源中的使用@McpResource(uri&amp;nbsp;=&amp;nbsp;&amp;quot;large-file://{path}&amp;quot;,&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Large&amp;nbsp;File&amp;nbsp;Resource&amp;quot;)
public&amp;nbsp;ReadResourceResult&amp;nbsp;getLargeFile(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgressToken&amp;nbsp;String&amp;nbsp;progressToken,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;path,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncServerExchange&amp;nbsp;exchange)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;File&amp;nbsp;file&amp;nbsp;=&amp;nbsp;new&amp;nbsp;File(path);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;long&amp;nbsp;fileSize&amp;nbsp;=&amp;nbsp;file.length();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Track&amp;nbsp;file&amp;nbsp;reading&amp;nbsp;progress
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.progressNotification(new&amp;nbsp;ProgressNotification(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;0.0,&amp;nbsp;fileSize,&amp;nbsp;&amp;quot;Reading&amp;nbsp;file&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;content&amp;nbsp;=&amp;nbsp;readFileWithProgress(file,&amp;nbsp;progressToken,&amp;nbsp;exchange);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.progressNotification(new&amp;nbsp;ProgressNotification(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;fileSize,&amp;nbsp;fileSize,&amp;nbsp;&amp;quot;File&amp;nbsp;read&amp;nbsp;complete&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;large-file://&amp;quot;&amp;nbsp;+&amp;nbsp;path,&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
}McpSyncRequestContext&amp;nbsp;/&amp;nbsp;McpAsyncRequestContext请求上下文对象提供了对MCP请求信息和服务端操作的统一访问能力。概述为有状态和无状态操作提供统一接口作为参数使用时会被自动注入不参与JSON模式生成支持日志、进度通知、采样、信息获取等高级功能兼容有状态（服务端交换）和无状态（传输上下文）模式McpSyncRequestContext功能public&amp;nbsp;record&amp;nbsp;UserInfo(String&amp;nbsp;name,&amp;nbsp;String&amp;nbsp;email,&amp;nbsp;int&amp;nbsp;age)&amp;nbsp;{}

@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;advanced-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;full&amp;nbsp;server&amp;nbsp;capabilities&amp;quot;)
public&amp;nbsp;String&amp;nbsp;advancedTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Send&amp;nbsp;logging&amp;nbsp;notification
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.info(&amp;quot;Processing:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Ping&amp;nbsp;the&amp;nbsp;client
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.ping();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Send&amp;nbsp;progress&amp;nbsp;updates
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.progress(50);&amp;nbsp;//&amp;nbsp;50%&amp;nbsp;complete

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Check&amp;nbsp;if&amp;nbsp;elicitation&amp;nbsp;is&amp;nbsp;supported&amp;nbsp;before&amp;nbsp;using&amp;nbsp;it
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.elicitEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Request&amp;nbsp;additional&amp;nbsp;information&amp;nbsp;from&amp;nbsp;user
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;StructuredElicitResultelicitResult&amp;nbsp;=&amp;nbsp;context.elicit(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;e&amp;nbsp;-&amp;gt;&amp;nbsp;e.message(&amp;quot;Need&amp;nbsp;additional&amp;nbsp;information&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserInfo.class
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(elicitResult.action()&amp;nbsp;==&amp;nbsp;ElicitResult.Action.ACCEPT)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserInfo&amp;nbsp;userInfo&amp;nbsp;=&amp;nbsp;elicitResult.structuredContent();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Use&amp;nbsp;the&amp;nbsp;user&amp;nbsp;information
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Check&amp;nbsp;if&amp;nbsp;sampling&amp;nbsp;is&amp;nbsp;supported&amp;nbsp;before&amp;nbsp;using&amp;nbsp;it
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.sampleEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Request&amp;nbsp;LLM&amp;nbsp;sampling
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CreateMessageResult&amp;nbsp;samplingResult&amp;nbsp;=&amp;nbsp;context.sample(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s&amp;nbsp;-&amp;gt;&amp;nbsp;s.message(&amp;quot;Process:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.modelPreferences(pref&amp;nbsp;-&amp;gt;&amp;nbsp;pref.modelHints(&amp;quot;gpt-4&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed&amp;nbsp;with&amp;nbsp;advanced&amp;nbsp;features&amp;quot;;
}McpAsyncRequestContext功能public&amp;nbsp;record&amp;nbsp;UserInfo(String&amp;nbsp;name,&amp;nbsp;String&amp;nbsp;email,&amp;nbsp;int&amp;nbsp;age)&amp;nbsp;{}

@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;async-advanced-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Async&amp;nbsp;tool&amp;nbsp;with&amp;nbsp;server&amp;nbsp;capabilities&amp;quot;)
public&amp;nbsp;MonoasyncAdvancedTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpAsyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;context.info(&amp;quot;Async&amp;nbsp;processing:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.then(context.progress(25))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.then(context.ping())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.flatMap(v&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Perform&amp;nbsp;elicitation&amp;nbsp;if&amp;nbsp;supported
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.elicitEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;context.elicitation(UserInfo.class)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(userInfo&amp;nbsp;-&amp;gt;&amp;nbsp;&amp;quot;Processing&amp;nbsp;for&amp;nbsp;user:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;userInfo.name());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.just(&amp;quot;Processing...&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.flatMap(msg&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Perform&amp;nbsp;sampling&amp;nbsp;if&amp;nbsp;supported
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.sampleEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;context.sampling(&amp;quot;Process:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(result&amp;nbsp;-&amp;gt;&amp;nbsp;&amp;quot;Completed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;result);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.just(&amp;quot;Completed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;msg);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
}McpTransportContext用于无状态操作的轻量级上下文。概述提供最小化上下文，不包含完整的服务端交换能力用于无状态实现作为参数使用时会被自动注入不参与JSON模式生成使用示例@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;stateless-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Stateless&amp;nbsp;tool&amp;nbsp;with&amp;nbsp;context&amp;quot;)
public&amp;nbsp;String&amp;nbsp;statelessTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpTransportContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Limited&amp;nbsp;context&amp;nbsp;access
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Useful&amp;nbsp;for&amp;nbsp;transport-level&amp;nbsp;operations

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed&amp;nbsp;in&amp;nbsp;stateless&amp;nbsp;mode:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input;
}

@McpResource(uri&amp;nbsp;=&amp;nbsp;&amp;quot;stateless://{id}&amp;quot;,&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Stateless&amp;nbsp;Resource&amp;quot;)
public&amp;nbsp;ReadResourceResult&amp;nbsp;statelessResource(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpTransportContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;id)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Access&amp;nbsp;transport&amp;nbsp;context&amp;nbsp;if&amp;nbsp;needed
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;data&amp;nbsp;=&amp;nbsp;loadData(id);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;stateless://&amp;quot;&amp;nbsp;+&amp;nbsp;id,&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
}CallToolRequest适用于需要访问完整动态模式请求的工具的特殊参数。概述可访问完整的工具请求支持运行时动态模式处理自动注入且不参与模式生成适用于适配不同输入模式的灵活工具使用示例@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;dynamic-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;dynamic&amp;nbsp;schema&amp;nbsp;support&amp;quot;)
public&amp;nbsp;CallToolResult&amp;nbsp;processDynamicSchema(CallToolRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapargs&amp;nbsp;=&amp;nbsp;request.arguments();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Process&amp;nbsp;based&amp;nbsp;on&amp;nbsp;whatever&amp;nbsp;schema&amp;nbsp;was&amp;nbsp;provided&amp;nbsp;at&amp;nbsp;runtime
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;StringBuilder&amp;nbsp;result&amp;nbsp;=&amp;nbsp;new&amp;nbsp;StringBuilder(&amp;quot;Processed:\n&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;(Map.Entryentry&amp;nbsp;:&amp;nbsp;args.entrySet())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.append(&amp;quot;&amp;nbsp;&amp;nbsp;&amp;quot;).append(entry.getKey())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.append(&amp;quot;:&amp;nbsp;&amp;quot;).append(entry.getValue()).append(&amp;quot;\n&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CallToolResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addTextContent(result.toString())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
}混合参数@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;hybrid-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;typed&amp;nbsp;and&amp;nbsp;dynamic&amp;nbsp;parameters&amp;quot;)
public&amp;nbsp;String&amp;nbsp;processHybrid(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Operation&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;operation,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Priority&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;Integer&amp;nbsp;priority,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CallToolRequest&amp;nbsp;request)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Use&amp;nbsp;typed&amp;nbsp;parameters&amp;nbsp;for&amp;nbsp;known&amp;nbsp;fields
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;result&amp;nbsp;=&amp;nbsp;&amp;quot;Operation:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;operation;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(priority&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;+=&amp;nbsp;&amp;quot;&amp;nbsp;(Priority:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;priority&amp;nbsp;+&amp;nbsp;&amp;quot;)&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Access&amp;nbsp;additional&amp;nbsp;dynamic&amp;nbsp;arguments
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MapallArgs&amp;nbsp;=&amp;nbsp;request.arguments();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Remove&amp;nbsp;known&amp;nbsp;parameters&amp;nbsp;to&amp;nbsp;get&amp;nbsp;only&amp;nbsp;additional&amp;nbsp;ones
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MapadditionalArgs&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;(allArgs);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;additionalArgs.remove(&amp;quot;operation&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;additionalArgs.remove(&amp;quot;priority&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!additionalArgs.isEmpty())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;+=&amp;nbsp;&amp;quot;&amp;nbsp;with&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;additionalArgs.size()&amp;nbsp;+&amp;nbsp;&amp;quot;&amp;nbsp;additional&amp;nbsp;parameters&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result;
}携带进度令牌@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;flexible-with-progress&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Flexible&amp;nbsp;tool&amp;nbsp;with&amp;nbsp;progress&amp;quot;)
public&amp;nbsp;CallToolResult&amp;nbsp;flexibleWithProgress(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgressToken&amp;nbsp;String&amp;nbsp;progressToken,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CallToolRequest&amp;nbsp;request,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncServerExchange&amp;nbsp;exchange)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapargs&amp;nbsp;=&amp;nbsp;request.arguments();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.progressNotification(new&amp;nbsp;ProgressNotification(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;0.0,&amp;nbsp;1.0,&amp;nbsp;&amp;quot;Processing&amp;nbsp;dynamic&amp;nbsp;request&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Process&amp;nbsp;dynamic&amp;nbsp;arguments
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;result&amp;nbsp;=&amp;nbsp;processDynamicArgs(args);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.progressNotification(new&amp;nbsp;ProgressNotification(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;1.0,&amp;nbsp;1.0,&amp;nbsp;&amp;quot;Complete&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CallToolResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addTextContent(result)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
}参数注入规则自动注入以下参数由框架自动注入：McpMeta&amp;nbsp;-&amp;nbsp;请求中的元数据@McpProgressToken&amp;nbsp;String&amp;nbsp;-&amp;nbsp;可用的进度令牌McpSyncServerExchange&amp;nbsp;/&amp;nbsp;McpAsyncServerExchange&amp;nbsp;-&amp;nbsp;服务端交换上下文McpTransportContext&amp;nbsp;-&amp;nbsp;无状态操作的传输上下文CallToolRequest&amp;nbsp;-&amp;nbsp;动态模式的完整工具请求模式生成特殊参数不会被纳入JSON模式生成：不会出现在工具的输入模式中不计入参数数量限制对MCP客户端不可见空值处理McpMeta&amp;nbsp;-&amp;nbsp;永不为null，无元数据时为空对象@McpProgressToken&amp;nbsp;-&amp;nbsp;无令牌时可为null服务端交换&amp;nbsp;-&amp;nbsp;正确配置时永不为nullCallToolRequest&amp;nbsp;-&amp;nbsp;工具方法中永不为null最佳实践使用McpMeta获取上下文@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;context-aware&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Context-aware&amp;nbsp;tool&amp;quot;)
public&amp;nbsp;String&amp;nbsp;contextAware(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Data&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;data,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpMeta&amp;nbsp;meta)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Always&amp;nbsp;check&amp;nbsp;for&amp;nbsp;null&amp;nbsp;values&amp;nbsp;in&amp;nbsp;metadata
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;userId&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;meta.get(&amp;quot;userId&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(userId&amp;nbsp;==&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userId&amp;nbsp;=&amp;nbsp;&amp;quot;anonymous&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;processForUser(data,&amp;nbsp;userId);
}进度令牌空值检查@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;safe-progress&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Safe&amp;nbsp;progress&amp;nbsp;handling&amp;quot;)
public&amp;nbsp;String&amp;nbsp;safeProgress(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgressToken&amp;nbsp;String&amp;nbsp;progressToken,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Task&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;task,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncServerExchange&amp;nbsp;exchange)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Always&amp;nbsp;check&amp;nbsp;if&amp;nbsp;progress&amp;nbsp;token&amp;nbsp;is&amp;nbsp;available
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.progressNotification(new&amp;nbsp;ProgressNotification(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;0.0,&amp;nbsp;1.0,&amp;nbsp;&amp;quot;Starting&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Perform&amp;nbsp;work...

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exchange.progressNotification(new&amp;nbsp;ProgressNotification(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;1.0,&amp;nbsp;1.0,&amp;nbsp;&amp;quot;Complete&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Task&amp;nbsp;completed&amp;quot;;
}选择合适的上下文使用McpSyncRequestContext&amp;nbsp;/&amp;nbsp;McpAsyncRequestContext统一访问请求上下文，支持有状态和无状态操作，附带便捷的辅助方法仅需传输级上下文时，使用McpTransportContext处理简单无状态操作最简单的场景可完全省略上下文参数功能可用性检查使用客户端功能前务必检查支持性：@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;capability-aware&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;that&amp;nbsp;checks&amp;nbsp;capabilities&amp;quot;)
public&amp;nbsp;String&amp;nbsp;capabilityAware(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Data&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;data)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Check&amp;nbsp;if&amp;nbsp;elicitation&amp;nbsp;is&amp;nbsp;supported&amp;nbsp;before&amp;nbsp;using&amp;nbsp;it
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.elicitEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Safe&amp;nbsp;to&amp;nbsp;use&amp;nbsp;elicitation
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;result&amp;nbsp;=&amp;nbsp;context.elicit(UserInfo.class);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Process&amp;nbsp;result...
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Check&amp;nbsp;if&amp;nbsp;sampling&amp;nbsp;is&amp;nbsp;supported&amp;nbsp;before&amp;nbsp;using&amp;nbsp;it
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.sampleEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Safe&amp;nbsp;to&amp;nbsp;use&amp;nbsp;sampling
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;samplingResult&amp;nbsp;=&amp;nbsp;context.sample(&amp;quot;Process:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;data);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Process&amp;nbsp;result...
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Note:&amp;nbsp;Stateless&amp;nbsp;servers&amp;nbsp;do&amp;nbsp;not&amp;nbsp;support&amp;nbsp;bidirectional&amp;nbsp;operations
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;(roots,&amp;nbsp;elicitation,&amp;nbsp;sampling)&amp;nbsp;and&amp;nbsp;will&amp;nbsp;return&amp;nbsp;false&amp;nbsp;for&amp;nbsp;these&amp;nbsp;checks

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed&amp;nbsp;with&amp;nbsp;capability&amp;nbsp;awareness&amp;quot;;
}&lt;/pre&gt;</description><pubDate>Mon, 18 May 2026 13:39:25 +0800</pubDate></item><item><title>Spring AI ​MCP 服务端注解</title><link>https://catnav.cn/post/81.html</link><description>&lt;p&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;MCP 服务端注解提供了一种声明式的方式，通过 Java 注解实现 MCP 服务端功能。这些注解简化了工具、资源、提示词和补全处理器的创建。&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;服务端注解&lt;/h3&gt;&lt;h4&gt;@McpTool&lt;/h4&gt;&lt;p&gt;@McpTool 注解将方法标记为 MCP 工具实现，并支持自动生成 JSON 模式。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;CalculatorTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;add&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Add&amp;nbsp;two&amp;nbsp;numbers&amp;nbsp;together&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;add(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;First&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;int&amp;nbsp;a,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Second&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;int&amp;nbsp;b)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;a&amp;nbsp;+&amp;nbsp;b;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;高级特性&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;calculate-area&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Calculate&amp;nbsp;the&amp;nbsp;area&amp;nbsp;of&amp;nbsp;a&amp;nbsp;rectangle&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;annotations&amp;nbsp;=&amp;nbsp;McpTool.McpAnnotations(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;=&amp;nbsp;&amp;quot;Rectangle&amp;nbsp;Area&amp;nbsp;Calculator&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;readOnlyHint&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;destructiveHint&amp;nbsp;=&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;idempotentHint&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;))
public&amp;nbsp;AreaResult&amp;nbsp;calculateRectangleArea(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Width&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;width,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Height&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;double&amp;nbsp;height)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;AreaResult(width&amp;nbsp;*&amp;nbsp;height,&amp;nbsp;&amp;quot;square&amp;nbsp;units&amp;quot;);
}&lt;/pre&gt;&lt;p&gt;携带请求上下文&lt;/p&gt;&lt;p&gt;工具可访问请求上下文以执行高级操作：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;process-data&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Process&amp;nbsp;data&amp;nbsp;with&amp;nbsp;request&amp;nbsp;context&amp;quot;)
public&amp;nbsp;String&amp;nbsp;processData(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Data&amp;nbsp;to&amp;nbsp;process&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;data)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;发送日志通知
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.info(&amp;quot;Processing&amp;nbsp;data:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;data);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;发送进度通知（使用便捷方法）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.progress(p&amp;nbsp;-&amp;gt;&amp;nbsp;p.progress(0.5).total(1.0).message(&amp;quot;Processing...&amp;quot;));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;向客户端发送心跳
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.ping();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;data.toUpperCase();
}&lt;/pre&gt;&lt;p&gt;动态模式支持&lt;/p&gt;&lt;p&gt;工具可接收 CallToolRequest 以实现运行时模式处理：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;flexible-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Process&amp;nbsp;dynamic&amp;nbsp;schema&amp;quot;)
public&amp;nbsp;CallToolResult&amp;nbsp;processDynamic(CallToolRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapargs&amp;nbsp;=&amp;nbsp;request.arguments();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;基于运行时模式处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;result&amp;nbsp;=&amp;nbsp;&amp;quot;Processed&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;args.size()&amp;nbsp;+&amp;nbsp;&amp;quot;&amp;nbsp;arguments&amp;nbsp;dynamically&amp;quot;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CallToolResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addTextContent(result)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
}&lt;/pre&gt;&lt;p&gt;进度跟踪&lt;/p&gt;&lt;p&gt;工具可接收进度令牌以跟踪长时间运行的操作：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;long-task&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Long-running&amp;nbsp;task&amp;nbsp;with&amp;nbsp;progress&amp;quot;)
public&amp;nbsp;String&amp;nbsp;performLongTask(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Task&amp;nbsp;name&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;taskName)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;从上下文获取进度令牌
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;progressToken&amp;nbsp;=&amp;nbsp;context.request().progressToken();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(progressToken&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.progress(p&amp;nbsp;-&amp;gt;&amp;nbsp;p.progress(0.0).total(1.0).message(&amp;quot;Starting&amp;nbsp;task&amp;quot;));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;执行任务...

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.progress(p&amp;nbsp;-&amp;gt;&amp;nbsp;p.progress(1.0).total(1.0).message(&amp;quot;Task&amp;nbsp;completed&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Task&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;taskName&amp;nbsp;+&amp;nbsp;&amp;quot;&amp;nbsp;completed&amp;quot;;
}&lt;/pre&gt;&lt;h4&gt;@McpResource&lt;/h4&gt;&lt;p&gt;@McpResource 注解通过 URI 模板提供资源访问能力。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;ResourceProvider&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpResource(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uri&amp;nbsp;=&amp;nbsp;&amp;quot;config://{key}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Configuration&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Provides&amp;nbsp;configuration&amp;nbsp;data&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;getConfig(String&amp;nbsp;key)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;configData.get(key);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;携带 ReadResourceResult&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpResource(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uri&amp;nbsp;=&amp;nbsp;&amp;quot;user-profile://{username}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;User&amp;nbsp;Profile&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Provides&amp;nbsp;user&amp;nbsp;profile&amp;nbsp;information&amp;quot;)
public&amp;nbsp;ReadResourceResult&amp;nbsp;getUserProfile(String&amp;nbsp;username)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;profileData&amp;nbsp;=&amp;nbsp;loadUserProfile(username);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;user-profile://&amp;quot;&amp;nbsp;+&amp;nbsp;username,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;application/json&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;profileData)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
}&lt;/pre&gt;&lt;p&gt;携带请求上下文&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpResource(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uri&amp;nbsp;=&amp;nbsp;&amp;quot;data://{id}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Data&amp;nbsp;Resource&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Resource&amp;nbsp;with&amp;nbsp;request&amp;nbsp;context&amp;quot;)
public&amp;nbsp;ReadResourceResult&amp;nbsp;getData(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;id)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用便捷方法发送日志通知
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.info(&amp;quot;Accessing&amp;nbsp;resource:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;id);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;向客户端发送心跳
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.ping();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;data&amp;nbsp;=&amp;nbsp;fetchData(id);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;data://&amp;quot;&amp;nbsp;+&amp;nbsp;id,&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
}&lt;/pre&gt;&lt;h4&gt;@McpPrompt&lt;/h4&gt;&lt;p&gt;@McpPrompt 注解为 AI 交互生成提示词消息。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;PromptProvider&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpPrompt(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;greeting&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Generate&amp;nbsp;a&amp;nbsp;greeting&amp;nbsp;message&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;GetPromptResult&amp;nbsp;greeting(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;name&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;User&amp;#39;s&amp;nbsp;name&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;name)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;message&amp;nbsp;=&amp;nbsp;&amp;quot;Hello,&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;name&amp;nbsp;+&amp;nbsp;&amp;quot;!&amp;nbsp;How&amp;nbsp;can&amp;nbsp;I&amp;nbsp;help&amp;nbsp;you&amp;nbsp;today?&amp;quot;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;GetPromptResult(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Greeting&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(new&amp;nbsp;PromptMessage(Role.ASSISTANT,&amp;nbsp;new&amp;nbsp;TextContent(message)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;携带可选参数&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpPrompt(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;personalized-message&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Generate&amp;nbsp;a&amp;nbsp;personalized&amp;nbsp;message&amp;quot;)
public&amp;nbsp;GetPromptResult&amp;nbsp;personalizedMessage(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;name&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;age&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;Integer&amp;nbsp;age,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpArg(name&amp;nbsp;=&amp;nbsp;&amp;quot;interests&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;String&amp;nbsp;interests)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;StringBuilder&amp;nbsp;message&amp;nbsp;=&amp;nbsp;new&amp;nbsp;StringBuilder();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message.append(&amp;quot;Hello,&amp;nbsp;&amp;quot;).append(name).append(&amp;quot;!\n\n&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(age&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message.append(&amp;quot;At&amp;nbsp;&amp;quot;).append(age).append(&amp;quot;&amp;nbsp;years&amp;nbsp;old,&amp;nbsp;&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;添加年龄专属内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(interests&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;!interests.isEmpty())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message.append(&amp;quot;Your&amp;nbsp;interest&amp;nbsp;in&amp;nbsp;&amp;quot;).append(interests);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;添加兴趣专属内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;GetPromptResult(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Personalized&amp;nbsp;Message&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.of(new&amp;nbsp;PromptMessage(Role.ASSISTANT,&amp;nbsp;new&amp;nbsp;TextContent(message.toString())))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
}&lt;/pre&gt;&lt;h4&gt;@McpComplete&lt;/h4&gt;&lt;p&gt;@McpComplete 注解为提示词提供自动补全功能。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;CompletionProvider&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpComplete(prompt&amp;nbsp;=&amp;nbsp;&amp;quot;city-search&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ListcompleteCityName(String&amp;nbsp;prefix)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;cities.stream()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(city&amp;nbsp;-&amp;gt;&amp;nbsp;city.toLowerCase().startsWith(prefix.toLowerCase()))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.limit(10)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.toList();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;携带 CompleteRequest.CompleteArgument&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpComplete(prompt&amp;nbsp;=&amp;nbsp;&amp;quot;travel-planner&amp;quot;)
public&amp;nbsp;ListcompleteTravelDestination(CompleteRequest.CompleteArgument&amp;nbsp;argument)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;prefix&amp;nbsp;=&amp;nbsp;argument.value().toLowerCase();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;argumentName&amp;nbsp;=&amp;nbsp;argument.name();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;根据参数名称提供不同的补全内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(&amp;quot;city&amp;quot;.equals(argumentName))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;completeCities(prefix);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;if&amp;nbsp;(&amp;quot;country&amp;quot;.equals(argumentName))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;completeCountries(prefix);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;List.of();
}&lt;/pre&gt;&lt;p&gt;携带 CompleteResult&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpComplete(prompt&amp;nbsp;=&amp;nbsp;&amp;quot;code-completion&amp;quot;)
public&amp;nbsp;CompleteResult&amp;nbsp;completeCode(String&amp;nbsp;prefix)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Listcompletions&amp;nbsp;=&amp;nbsp;generateCodeCompletions(prefix);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;CompleteResult(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;CompleteResult.CompleteCompletion(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;completions,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;completions.size(),&amp;nbsp;&amp;nbsp;//&amp;nbsp;总数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hasMoreCompletions&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;是否有更多补全标识
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
}&lt;/pre&gt;&lt;h3&gt;无状态与有状态实现&lt;/h3&gt;&lt;p&gt;统一请求上下文（推荐）&lt;/p&gt;&lt;p&gt;使用 McpSyncRequestContext 或 McpAsyncRequestContext 可获得适用于有状态和无状态操作的统一接口：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;public&amp;nbsp;record&amp;nbsp;UserInfo(String&amp;nbsp;name,&amp;nbsp;String&amp;nbsp;email,&amp;nbsp;int&amp;nbsp;age)&amp;nbsp;{}

@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;unified-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;unified&amp;nbsp;request&amp;nbsp;context&amp;quot;)
public&amp;nbsp;String&amp;nbsp;unifiedTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;访问请求和元数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;progressToken&amp;nbsp;=&amp;nbsp;context.request().progressToken();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用便捷方法记录日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.info(&amp;quot;Processing:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;进度通知（注意：客户端需在请求中设置进度令牌才能接收进度更新）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.progress(50);&amp;nbsp;//&amp;nbsp;简单百分比

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;向客户端发送心跳
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.ping();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用前检查能力是否支持
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.elicitEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;请求用户输入（仅在有状态模式下）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;StructuredElicitResultelicitResult&amp;nbsp;=&amp;nbsp;context.elicit(UserInfo.class);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(elicitResult.action()&amp;nbsp;==&amp;nbsp;ElicitResult.Action.ACCEPT)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用诱导获取的数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.sampleEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;请求大语言模型采样（仅在有状态模式下）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CreateMessageResult&amp;nbsp;samplingResult&amp;nbsp;=&amp;nbsp;context.sample(&amp;quot;Generate&amp;nbsp;response&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;使用采样结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed&amp;nbsp;with&amp;nbsp;unified&amp;nbsp;context&amp;quot;;
}&lt;/pre&gt;&lt;p&gt;简单操作（无上下文）&lt;/p&gt;&lt;p&gt;对于简单操作，可完全省略上下文参数：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;simple-add&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Simple&amp;nbsp;addition&amp;quot;)
public&amp;nbsp;int&amp;nbsp;simpleAdd(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;First&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;int&amp;nbsp;a,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Second&amp;nbsp;number&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;int&amp;nbsp;b)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;a&amp;nbsp;+&amp;nbsp;b;
}&lt;/pre&gt;&lt;p&gt;轻量级无状态（携带 McpTransportContext）&lt;/p&gt;&lt;p&gt;适用于需要最小传输上下文的无状态操作：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;stateless-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Stateless&amp;nbsp;with&amp;nbsp;transport&amp;nbsp;context&amp;quot;)
public&amp;nbsp;String&amp;nbsp;statelessTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpTransportContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;仅访问传输级上下文
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;无双向操作（根目录、诱导、采样）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input;
}&lt;/pre&gt;&lt;p&gt;无状态服务端不支持双向操作：
因此在无状态模式下，使用 McpSyncRequestContext 或 McpAsyncRequestContext 的方法会被忽略。&lt;/p&gt;&lt;h3&gt;按服务端类型过滤方法&lt;/h3&gt;&lt;p&gt;MCP 注解框架会根据服务端类型和方法特征自动过滤注解方法。这确保每个服务端配置仅注册合适的方法。每个被过滤的方法都会记录一条警告日志，便于调试。&lt;/p&gt;&lt;h4&gt;同步与异步过滤&lt;/h4&gt;&lt;p&gt;同步服务端&lt;/p&gt;&lt;p&gt;同步服务端（配置为 spring.ai.mcp.server.type=SYNC）使用同步提供者，规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;接收非响应式返回类型的方法：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;基本类型（int、double、boolean）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;对象类型（String、Integer、自定义 POJO）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;MCP 类型（CallToolResult、ReadResourceResult、GetPromptResult、CompleteResult）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;集合（List&lt;/p&gt;&lt;string&gt;、Map&lt;string, object=&quot;&quot;&gt;）&lt;/string,&gt;&lt;/string&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;过滤响应式返回类型的方法：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;Mono&lt;/p&gt;&lt;t&gt;&lt;/t&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Flux&lt;/p&gt;&lt;t&gt;&lt;/t&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Publisher&lt;/p&gt;&lt;t&gt;&lt;/t&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;SyncTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;sync-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Synchronous&amp;nbsp;tool&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;syncTool(String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在同步服务端注册
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;async-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Async&amp;nbsp;tool&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonoasyncTool(String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在同步服务端被过滤
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;会记录警告日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.just(&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;异步服务端&lt;/p&gt;&lt;p&gt;异步服务端（配置为 spring.ai.mcp.server.type=ASYNC）使用异步提供者，规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;接收响应式返回类型的方法：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;Mono&lt;/p&gt;&lt;t&gt;（单个结果）&lt;/t&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Flux&lt;/p&gt;&lt;t&gt;（流式结果）&lt;/t&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Publisher&lt;/p&gt;&lt;t&gt;（通用响应式类型）&lt;/t&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;过滤非响应式返回类型的方法：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;基本类型&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;对象类型&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;集合&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;MCP 结果类型&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;AsyncTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;async-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Async&amp;nbsp;tool&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonoasyncTool(String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在异步服务端注册
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.just(&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;sync-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Sync&amp;nbsp;tool&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;syncTool(String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在异步服务端被过滤
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;会记录警告日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;有状态与无状态过滤&lt;/h4&gt;&lt;p&gt;有状态服务端&lt;/p&gt;&lt;p&gt;有状态服务端支持双向通信，接收包含以下内容的方法：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;双向上下文参数：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;McpSyncRequestContext（同步操作）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;McpAsyncRequestContext（异步操作）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;McpSyncServerExchange（旧版，同步操作）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;McpAsyncServerExchange（旧版，异步操作）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;支持双向操作：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;roots() - 访问根目录&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;elicit() - 请求用户输入&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;sample() - 请求大语言模型采样&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;StatefulTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;interactive-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;bidirectional&amp;nbsp;operations&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;interactiveTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在有状态服务端注册
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;可使用诱导、采样、根目录功能
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(context.sampleEnabled())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;samplingResult&amp;nbsp;=&amp;nbsp;context.sample(&amp;quot;Generate&amp;nbsp;response&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;处理采样结果...
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed&amp;nbsp;with&amp;nbsp;context&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;无状态服务端&lt;/p&gt;&lt;p&gt;无状态服务端针对简单请求-响应模式优化，规则如下：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;过滤包含双向上下文参数的方法：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;携带 McpSyncRequestContext 的方法会被跳过&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;携带 McpAsyncRequestContext 的方法会被跳过&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;携带 McpSyncServerExchange 的方法会被跳过&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;携带 McpAsyncServerExchange 的方法会被跳过&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;每个被过滤的方法都会记录警告日志&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;接收包含以下内容的方法：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;McpTransportContext（轻量级无状态上下文）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;无任何上下文参数&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;仅常规 @McpToolParam 参数&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;不支持双向操作：&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot; style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;p&gt;roots() - 不可用&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;elicit() - 不可用&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;sample() - 不可用&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;StatelessTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;simple-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Simple&amp;nbsp;stateless&amp;nbsp;tool&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;simpleTool(@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在无状态服务端注册
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;context-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;transport&amp;nbsp;context&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;contextTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpTransportContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在无状态服务端注册
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;input;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;bidirectional-tool&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Tool&amp;nbsp;with&amp;nbsp;bidirectional&amp;nbsp;context&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;String&amp;nbsp;bidirectionalTool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;McpSyncRequestContext&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;Input&amp;quot;)&amp;nbsp;String&amp;nbsp;input)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;该方法会在无状态服务端被过滤
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;会记录警告日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Processed&amp;nbsp;with&amp;nbsp;sampling&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;过滤总结&lt;/h4&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr class=&quot;firstRow&quot;&gt;&lt;th&gt;服务端类型&lt;/th&gt;&lt;th&gt;接收的方法&lt;/th&gt;&lt;th&gt;过滤的方法&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;同步有状态&lt;/td&gt;&lt;td&gt;非响应式返回 + 双向上下文&lt;/td&gt;&lt;td&gt;响应式返回（Mono/Flux）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;异步有状态&lt;/td&gt;&lt;td&gt;响应式返回（Mono/Flux）+ 双向上下文&lt;/td&gt;&lt;td&gt;非响应式返回&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;同步无状态&lt;/td&gt;&lt;td&gt;非响应式返回 + 无双向上下文&lt;/td&gt;&lt;td&gt;响应式返回 或 双向上下文参数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;异步无状态&lt;/td&gt;&lt;td&gt;响应式返回（Mono/Flux）+ 无双向上下文&lt;/td&gt;&lt;td&gt;非响应式返回 或 双向上下文参数&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;方法过滤最佳实践：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;方法与服务端类型保持一致——同步服务端使用同步方法，异步服务端使用异步方法&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将有状态和无状态实现分离到不同类中，提升代码清晰度&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;启动时检查日志，查看被过滤的方法警告&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;使用合适的上下文——有状态使用 McpSyncRequestContext/McpAsyncRequestContext，无状态使用 McpTransportContext&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;若同时支持有状态和无状态部署，需对两种模式都进行测试&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;异步支持&lt;/h3&gt;&lt;p&gt;所有服务端注解均支持使用 Reactor 实现异步：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;AsyncTools&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpTool(name&amp;nbsp;=&amp;nbsp;&amp;quot;async-fetch&amp;quot;,&amp;nbsp;description&amp;nbsp;=&amp;nbsp;&amp;quot;Fetch&amp;nbsp;data&amp;nbsp;asynchronously&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonoasyncFetch(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolParam(description&amp;nbsp;=&amp;nbsp;&amp;quot;URL&amp;quot;,&amp;nbsp;required&amp;nbsp;=&amp;nbsp;true)&amp;nbsp;String&amp;nbsp;url)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromCallable(()&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;模拟异步操作
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;fetchFromUrl(url);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}).subscribeOn(Schedulers.boundedElastic());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpResource(uri&amp;nbsp;=&amp;nbsp;&amp;quot;async-data://{id}&amp;quot;,&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Async&amp;nbsp;Data&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonoasyncResource(String&amp;nbsp;id)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromCallable(()&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;data&amp;nbsp;=&amp;nbsp;loadData(id);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ReadResourceResult(List.of(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new&amp;nbsp;TextResourceContents(&amp;quot;async-data://&amp;quot;&amp;nbsp;+&amp;nbsp;id,&amp;nbsp;&amp;quot;text/plain&amp;quot;,&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}).delayElements(Duration.ofMillis(100));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;Spring Boot 集成&lt;/h3&gt;&lt;p&gt;通过 Spring Boot 自动配置，带注解的 Bean 会被自动检测并注册：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@SpringBootApplication
public&amp;nbsp;class&amp;nbsp;McpServerApplication&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(McpServerApplication.class,&amp;nbsp;args);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Component
public&amp;nbsp;class&amp;nbsp;MyMcpTools&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;你的&amp;nbsp;@McpTool&amp;nbsp;注解方法
}

@Component
public&amp;nbsp;class&amp;nbsp;MyMcpResources&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;你的&amp;nbsp;@McpResource&amp;nbsp;注解方法
}&lt;/pre&gt;&lt;p&gt;自动配置会执行以下操作：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;扫描带有 MCP 注解的 Bean&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;创建对应的规范定义&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将其注册到 MCP 服务端&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;根据配置自动处理同步和异步实现&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;配置属性&lt;/h3&gt;&lt;p&gt;配置服务端注解扫描器：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;spring:
&amp;nbsp;&amp;nbsp;ai:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mcp:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;SYNC&amp;nbsp;&amp;nbsp;#&amp;nbsp;或&amp;nbsp;ASYNC
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;annotation-scanner:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;true&lt;/pre&gt;&lt;h3&gt;附加资源&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;MCP 注解概述&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;客户端注解&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;特殊参数&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;MCP 服务端启动器&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</description><pubDate>Sat, 16 May 2026 00:02:05 +0800</pubDate></item><item><title>Spring AI ​MCP 客户端注解</title><link>https://catnav.cn/post/80.html</link><description>&lt;p&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;MCP 客户端注解提供了一种声明式的方式，通过 Java 注解实现 MCP 客户端处理器。这些注解简化了服务端通知和客户端操作的处理逻辑。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;所有 MCP 客户端注解&lt;strong&gt;必须&lt;/strong&gt;包含一个 clients 参数，用于将处理器与指定的 MCP 客户端连接关联。该 clients 值必须与应用配置文件中配置的连接名称一致。&lt;/p&gt;&lt;h3&gt;客户端注解&lt;/h3&gt;&lt;h4&gt;@McpLogging&lt;/h4&gt;&lt;p&gt;@McpLogging 注解用于处理来自 MCP 服务端的日志消息通知。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;LoggingHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpLogging(clients&amp;nbsp;=&amp;nbsp;&amp;quot;my-mcp-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleLoggingMessage(LoggingMessageNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;Received&amp;nbsp;log:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;notification.level()&amp;nbsp;+
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;nbsp;-&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;notification.data());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;独立参数接收方式&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpLogging(clients&amp;nbsp;=&amp;nbsp;&amp;quot;my-mcp-server&amp;quot;)
public&amp;nbsp;void&amp;nbsp;handleLoggingWithParams(LoggingLevel&amp;nbsp;level,&amp;nbsp;String&amp;nbsp;logger,&amp;nbsp;String&amp;nbsp;data)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(String.format(&amp;quot;[%s]&amp;nbsp;%s:&amp;nbsp;%s&amp;quot;,&amp;nbsp;level,&amp;nbsp;logger,&amp;nbsp;data));
}&lt;/pre&gt;&lt;h4&gt;@McpSampling&lt;/h4&gt;&lt;p&gt;@McpSampling 注解用于处理 MCP 服务端发送的大语言模型补全采样请求。&lt;/p&gt;&lt;p&gt;同步实现&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;SamplingHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpSampling(clients&amp;nbsp;=&amp;nbsp;&amp;quot;llm-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;CreateMessageResult&amp;nbsp;handleSamplingRequest(CreateMessageRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;处理请求并生成响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;response&amp;nbsp;=&amp;nbsp;generateLLMResponse(request);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CreateMessageResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.role(Role.ASSISTANT)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content(new&amp;nbsp;TextContent(response))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.model(&amp;quot;gpt-4&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;异步实现&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;AsyncSamplingHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpSampling(clients&amp;nbsp;=&amp;nbsp;&amp;quot;llm-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;MonohandleAsyncSampling(CreateMessageRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromCallable(()&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;response&amp;nbsp;=&amp;nbsp;generateLLMResponse(request);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;CreateMessageResult.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.role(Role.ASSISTANT)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.content(new&amp;nbsp;TextContent(response))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.model(&amp;quot;gpt-4&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}).subscribeOn(Schedulers.boundedElastic());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;@McpElicitation&lt;/h4&gt;&lt;p&gt;@McpElicitation 注解用于处理信息诱导请求，以从用户处收集额外信息。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;ElicitationHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpElicitation(clients&amp;nbsp;=&amp;nbsp;&amp;quot;interactive-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;ElicitResult&amp;nbsp;handleElicitationRequest(ElicitRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;向用户展示请求并收集输入信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MapuserData&amp;nbsp;=&amp;nbsp;presentFormToUser(request.requestedSchema());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(userData&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ElicitResult(ElicitResult.Action.ACCEPT,&amp;nbsp;userData);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ElicitResult(ElicitResult.Action.DECLINE,&amp;nbsp;null);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;用户交互实现&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpElicitation(clients&amp;nbsp;=&amp;nbsp;&amp;quot;interactive-server&amp;quot;)
public&amp;nbsp;ElicitResult&amp;nbsp;handleInteractiveElicitation(ElicitRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapschema&amp;nbsp;=&amp;nbsp;request.requestedSchema();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MapuserData&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;检查需要请求的信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(schema&amp;nbsp;!=&amp;nbsp;null&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;schema.containsKey(&amp;quot;properties&amp;quot;))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Mapproperties&amp;nbsp;=&amp;nbsp;(Map)&amp;nbsp;schema.get(&amp;quot;properties&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;根据模式收集用户输入
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(properties.containsKey(&amp;quot;name&amp;quot;))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userData.put(&amp;quot;name&amp;quot;,&amp;nbsp;promptUser(&amp;quot;请输入你的姓名：&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(properties.containsKey(&amp;quot;email&amp;quot;))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userData.put(&amp;quot;email&amp;quot;,&amp;nbsp;promptUser(&amp;quot;请输入你的邮箱：&amp;quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(properties.containsKey(&amp;quot;preferences&amp;quot;))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userData.put(&amp;quot;preferences&amp;quot;,&amp;nbsp;gatherPreferences());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ElicitResult(ElicitResult.Action.ACCEPT,&amp;nbsp;userData);
}&lt;/pre&gt;&lt;p&gt;异步信息诱导&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpElicitation(clients&amp;nbsp;=&amp;nbsp;&amp;quot;interactive-server&amp;quot;)
public&amp;nbsp;MonohandleAsyncElicitation(ElicitRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromCallable(()&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;异步用户交互
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MapuserData&amp;nbsp;=&amp;nbsp;asyncGatherUserInput(request);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;ElicitResult(ElicitResult.Action.ACCEPT,&amp;nbsp;userData);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}).timeout(Duration.ofSeconds(30))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onErrorReturn(new&amp;nbsp;ElicitResult(ElicitResult.Action.CANCEL,&amp;nbsp;null));
}&lt;/pre&gt;&lt;h4&gt;@McpProgress&lt;/h4&gt;&lt;p&gt;@McpProgress 注解用于处理长时间运行操作的进度通知。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;ProgressHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgress(clients&amp;nbsp;=&amp;nbsp;&amp;quot;my-mcp-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleProgressNotification(ProgressNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;double&amp;nbsp;percentage&amp;nbsp;=&amp;nbsp;notification.progress()&amp;nbsp;*&amp;nbsp;100;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(String.format(&amp;quot;进度：%.2f%%&amp;nbsp;-&amp;nbsp;%s&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;percentage,&amp;nbsp;notification.message()));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;独立参数接收方式&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpProgress(clients&amp;nbsp;=&amp;nbsp;&amp;quot;my-mcp-server&amp;quot;)
public&amp;nbsp;void&amp;nbsp;handleProgressWithDetails(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;progressToken,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;double&amp;nbsp;progress,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Double&amp;nbsp;total,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;message)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(total&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(String.format(&amp;quot;[%s]&amp;nbsp;%.0f/%.0f&amp;nbsp;-&amp;nbsp;%s&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;progress,&amp;nbsp;total,&amp;nbsp;message));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(String.format(&amp;quot;[%s]&amp;nbsp;%.2f%%&amp;nbsp;-&amp;nbsp;%s&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressToken,&amp;nbsp;progress&amp;nbsp;*&amp;nbsp;100,&amp;nbsp;message));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;更新界面进度条
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;updateProgressBar(progressToken,&amp;nbsp;progress);
}&lt;/pre&gt;&lt;p&gt;客户端专属进度处理&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpProgress(clients&amp;nbsp;=&amp;nbsp;&amp;quot;long-running-server&amp;quot;)
public&amp;nbsp;void&amp;nbsp;handleLongRunningProgress(ProgressNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;跟踪指定服务端的进度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;progressTracker.update(&amp;quot;long-running-server&amp;quot;,&amp;nbsp;notification);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;按需发送通知
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(notification.progress()&amp;nbsp;&amp;gt;=&amp;nbsp;1.0)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;notifyCompletion(notification.progressToken());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h4&gt;@McpToolListChanged&lt;/h4&gt;&lt;p&gt;@McpToolListChanged 注解用于处理服务端工具列表发生变更时的通知。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;ToolListChangedHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpToolListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;tool-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleToolListChanged(ListupdatedTools)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;工具列表已更新：可用工具数量&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;updatedTools.size());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;更新本地工具注册表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;toolRegistry.updateTools(updatedTools);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;记录新工具
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;(McpSchema.Tool&amp;nbsp;tool&amp;nbsp;:&amp;nbsp;updatedTools)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;tool.name()&amp;nbsp;+&amp;nbsp;&amp;quot;:&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;tool.description());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;异步处理&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpToolListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;tool-server&amp;quot;)
public&amp;nbsp;MonohandleAsyncToolListChanged(ListupdatedTools)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Mono.fromRunnable(()&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;异步处理工具列表更新
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;processToolListUpdate(updatedTools);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;通知相关组件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;eventBus.publish(new&amp;nbsp;ToolListUpdatedEvent(updatedTools));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}).then();
}&lt;/pre&gt;&lt;p&gt;客户端专属工具更新&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpToolListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;dynamic-server&amp;quot;)
public&amp;nbsp;void&amp;nbsp;handleDynamicServerToolUpdate(ListupdatedTools)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;处理来自频繁变更工具的指定服务端的工具
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dynamicToolManager.updateServerTools(&amp;quot;dynamic-server&amp;quot;,&amp;nbsp;updatedTools);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;重新评估工具可用性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reevaluateToolCapabilities();
}&lt;/pre&gt;&lt;h4&gt;@McpResourceListChanged&lt;/h4&gt;&lt;p&gt;@McpResourceListChanged 注解用于处理服务端资源列表发生变更时的通知。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;ResourceListChangedHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpResourceListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;resource-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleResourceListChanged(ListupdatedResources)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;资源已更新：数量&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;updatedResources.size());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;更新资源缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resourceCache.clear();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;(McpSchema.Resource&amp;nbsp;resource&amp;nbsp;:&amp;nbsp;updatedResources)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resourceCache.register(resource);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;资源变更分析&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpResourceListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;resource-server&amp;quot;)
public&amp;nbsp;void&amp;nbsp;analyzeResourceChanges(ListupdatedResources)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;分析变更内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SetnewUris&amp;nbsp;=&amp;nbsp;updatedResources.stream()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(McpSchema.Resource::uri)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.collect(Collectors.toSet());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SetremovedUris&amp;nbsp;=&amp;nbsp;previousUris.stream()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(uri&amp;nbsp;-&amp;gt;&amp;nbsp;!newUris.contains(uri))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.collect(Collectors.toSet());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!removedUris.isEmpty())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handleRemovedResources(removedUris);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;更新跟踪记录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;previousUris&amp;nbsp;=&amp;nbsp;newUris;
}&lt;/pre&gt;&lt;h4&gt;@McpPromptListChanged&lt;/h4&gt;&lt;p&gt;@McpPromptListChanged 注解用于处理服务端提示词列表发生变更时的通知。&lt;/p&gt;&lt;p&gt;基础用法&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Component
public&amp;nbsp;class&amp;nbsp;PromptListChangedHandler&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpPromptListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;prompt-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handlePromptListChanged(ListupdatedPrompts)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.out.println(&amp;quot;提示词已更新：数量&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;updatedPrompts.size());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;更新提示词目录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;promptCatalog.updatePrompts(updatedPrompts);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;按需刷新界面
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(uiController&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uiController.refreshPromptList(updatedPrompts);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;异步处理&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@McpPromptListChanged(clients&amp;nbsp;=&amp;nbsp;&amp;quot;prompt-server&amp;quot;)
public&amp;nbsp;MonohandleAsyncPromptUpdate(ListupdatedPrompts)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Flux.fromIterable(updatedPrompts)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.flatMap(prompt&amp;nbsp;-&amp;gt;&amp;nbsp;validatePrompt(prompt))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.collectList()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.doOnNext(validPrompts&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;promptRepository.saveAll(validPrompts);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.then();
}&lt;/pre&gt;&lt;h3&gt;Spring Boot 集成&lt;/h3&gt;&lt;p&gt;通过 Spring Boot 自动配置，客户端处理器会被自动检测并注册：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@SpringBootApplication
public&amp;nbsp;class&amp;nbsp;McpClientApplication&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;void&amp;nbsp;main(String[]&amp;nbsp;args)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(McpClientApplication.class,&amp;nbsp;args);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Component
public&amp;nbsp;class&amp;nbsp;MyClientHandlers&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpLogging(clients&amp;nbsp;=&amp;nbsp;&amp;quot;my-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleLogs(LoggingMessageNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;处理日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpSampling(clients&amp;nbsp;=&amp;nbsp;&amp;quot;my-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;CreateMessageResult&amp;nbsp;handleSampling(CreateMessageRequest&amp;nbsp;request)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;处理采样
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@McpProgress(clients&amp;nbsp;=&amp;nbsp;&amp;quot;my-server&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;handleProgress(ProgressNotification&amp;nbsp;notification)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;处理进度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;自动配置会执行以下操作：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;扫描带有 MCP 客户端注解的 Bean&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;创建对应的规范定义&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将其注册到 MCP 客户端&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;支持同步和异步两种实现方式&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;通过客户端专属处理器支持多客户端场景&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;配置属性&lt;/h3&gt;&lt;p&gt;配置客户端注解扫描器和客户端连接：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;spring:
&amp;nbsp;&amp;nbsp;ai:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mcp:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;client:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;SYNC&amp;nbsp;&amp;nbsp;#&amp;nbsp;或&amp;nbsp;ASYNC
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;annotation-scanner:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;配置客户端连接&amp;nbsp;-&amp;nbsp;连接名称会作为注解中clients的值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sse:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connections:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;my-server:&amp;nbsp;&amp;nbsp;#&amp;nbsp;该名称作为注解的clients值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url:&amp;nbsp;http://localhost:8080
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tool-server:&amp;nbsp;&amp;nbsp;#&amp;nbsp;另一个clients值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url:&amp;nbsp;http://localhost:8081
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stdio:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connections:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local-server:&amp;nbsp;&amp;nbsp;#&amp;nbsp;该名称作为注解的clients值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command:&amp;nbsp;/path/to/mcp-server
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;--mode=production&lt;/pre&gt;&lt;p&gt;注解中的 clients 参数必须与配置中定义的连接名称匹配。在上述示例中，合法的 clients 值为：&amp;quot;my-server&amp;quot;、&amp;quot;tool-server&amp;quot; 和 &amp;quot;local-server&amp;quot;。&lt;/p&gt;&lt;h3&gt;MCP 客户端使用方式&lt;/h3&gt;&lt;p&gt;带注解的处理器会自动与 MCP 客户端集成：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-java&quot;&gt;@Autowired
private&amp;nbsp;ListmcpClients;

//&amp;nbsp;客户端会根据clients配置自动使用你定义的注解处理器
//&amp;nbsp;无需手动注册&amp;nbsp;-&amp;nbsp;处理器会通过名称与客户端匹配关联&lt;/pre&gt;&lt;p&gt;对于每个 MCP 客户端连接，带有匹配 clients 值的处理器会被自动注册，并在对应事件触发时自动调用。&lt;/p&gt;</description><pubDate>Sat, 16 May 2026 00:01:26 +0800</pubDate></item></channel></rss>