ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring AI MCP Sampling Client 完整案例:TaoToken 统一 Key 接入与验证

Spring AI MCP Sampling Client 完整案例:TaoToken 统一 Key 接入与验证 1. 从一次“反向请求”说起MCP Sampling Client 到底解决什么问题如果你已经用过 Spring AI 的 MCP 工具调用会发现一个固定套路客户端把工具清单交给模型模型决定调用哪个工具客户端执行工具再把结果回传。整个链路里LLM 是“大脑”MCP 服务器是“手脚”。但 Sampling 把这个方向反过来了。MCP 服务器在运行过程中可以主动向客户端发起一个“帮我生成一段文本”的请求客户端再去调用真正的 LLM把结果回传给服务器。也就是说服务器不再只是被动执行工具它也能借用客户端的模型能力。这个能力在实际项目里非常有用。比如你写了一个 MCP 天气服务器它拿到原始气象数据后想直接生成一段人类可读的天气描述甚至一首诗但服务器本身不持有任何模型 Key。这时候 Sampling 就派上用场了服务器发出 sampling 请求客户端负责路由到具体 LLM生成结果后返回。Spring AI MCP Sampling Client 就是这套机制的客户端实现。它适合谁适合正在用 Spring Boot 3.x 构建 AI 应用、已经接触过 MCP 工具调用、想进一步理解“服务端反向请求 LLM”这条链路的开发者。本文会给出可复制的application.yml、Sampling 回调实现、TaoToken 统一 Key 配置并用一次端到端调用验证整条链路是否打通。我试过把这套流程跑通中间踩过几个坑后面会逐个拆开讲。先明确一点Sampling 的核心不是“多模型路由”本身而是“服务器请求 → 客户端回调 → LLM 生成 → 结果回传”这个闭环。理解了闭环配置就只是填空题。2. TaoToken 前置准备统一 Key 与 API 通道配置在写 Sampling 回调之前得先解决“客户端拿什么去调 LLM”的问题。传统做法是给 OpenAI 配一个 Key、给 Anthropic 配一个 Key环境变量一堆切换模型还要改配置。这里我们用 TaoToken 做统一入口一个 Key 走通多个模型通道。TaoToken 的定位是统一的模型 API 通道官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。它的 API 基址是 https://taotoken.net/api 注意这个地址后面不加任何查询参数直接作为 Base URL 使用。你需要先拿到一个 API Key。登录后进入控制台在 API Keys 页面创建一个新 Key复制出来备用。这个 Key 就是后面application.yml里要填的值。控制台入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Keys 页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到 Key 之后先别急着写 Java 代码用一条 curl 验证通道是否可用。这一步很关键因为后面 Sampling 回调如果报 401你至少能确定是 Key 问题还是代码问题。curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key \ -d { model: gpt-4o-mini, messages: [ {role: user, content: 用一句话说明什么是 MCP Sampling} ] }如果返回里能看到choices数组和正常的content说明通道没问题。如果返回 401检查 Key 是否复制完整、有没有多余空格。如果返回model not found说明模型 ID 写错了换成通道支持的模型名再试。这里有个细节TaoToken 的 Base URL 是https://taotoken.net/api而 OpenAI 兼容接口的完整路径是/v1/chat/completions。所以在 Spring AI 配置里base-url填https://taotoken.net/apiSpring AI 会自动拼接/v1/chat/completions。不要手动把/v1写进 base-url否则会变成/api/v1/v1/...。另外如果你打算同时验证多个模型可以在控制台里确认哪些模型 ID 可用。常见的有gpt-4o-mini、claude-3-5-sonnet这类。模型 ID 要和后面 Sampling 回调里的modelHint对应上否则路由会找不到对应的 ChatClient。3. 可复制配置application.yml 与 Sampling 回调实现这一节是全文的核心所有配置都可以直接复制。先看pom.xml的依赖Spring Boot 版本用 3.4.5Spring AI 用 1.1.0 的 BOM。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.5/version /parent dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.1.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency /dependencies这里只保留 OpenAI 一个 starter因为 TaoToken 走的是 OpenAI 兼容协议一个 starter 就能覆盖多个模型。如果你确实要接 Anthropic 原生协议再加spring-ai-starter-model-anthropic但本文用统一通道不需要。接下来是application.yml。注意我用的是 YAML 而不是 properties因为嵌套结构更清晰。spring: application: name: mcp-sampling-client main: web-application-type: none ai: chat: client: enabled: false openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 mcp: client: toolcallback: enabled: false sse: connections: weather-server: url: http://localhost:8080 logging: level: io.modelcontextprotocol.client: WARN io.modelcontextprotocol.spec: WARN几个关键点。第一spring.ai.chat.client.enabledfalse必须关掉因为我们要手动管理多个 ChatClient自动配置会干扰。第二base-url填https://taotoken.net/apiapi-key用环境变量TAOTOKEN_API_KEY注入不要把 Key 硬编码进文件。第三toolcallback.enabledfalse是因为本文聚焦 Sampling不混入工具回调逻辑。第四SSE 连接指向本地 8080 的 MCP 服务器这个服务器后面要单独启动。然后是 Sampling 回调的实现。核心是注册一个McpSyncClientCustomizer在sampling方法里处理服务器发来的请求。Bean McpSyncClientCustomizer samplingCustomizer(MapString, ChatClient chatClients) { return (name, spec) - { spec.sampling(llmRequest - { var userPrompt ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text(); String modelHint llmRequest.modelPreferences().hints().get(0).name(); ChatClient hintedChatClient chatClients.entrySet().stream() .filter(e - e.getKey().contains(modelHint)) .findFirst() .orElseThrow(() - new IllegalStateException(no chat client for hint: modelHint)) .getValue(); String response hintedChatClient.prompt() .system(llmRequest.systemPrompt()) .user(userPrompt) .call() .content(); return McpSchema.CreateMessageResult.builder() .content(new McpSchema.TextContent(response)) .build(); }); }; }这段代码的逻辑是从请求里取出用户提示词和模型偏好提示根据提示找到对应的 ChatClient调用模型生成结果再包装成CreateMessageResult返回。modelHint是服务器指定的比如服务器说“这次用 gpt-4o-mini”客户端就路由到对应的客户端。ChatClient 的注册用下面这个 BeanBean public MapString, ChatClient chatClients(ListChatModel chatModels) { return chatModels.stream().collect(Collectors.toMap( model - model.getClass().getSimpleName().toLowerCase(), model - ChatClient.builder(model).build() )); }这里 key 是模型类名的小写形式比如openaichatmodel。所以modelHint里如果写openaicontains判断就能命中。如果你有多个模型key 会各自不同路由就靠这个匹配。主类里再加一个CommandLineRunner做端到端触发Bean public CommandLineRunner run(OpenAiChatModel chatModel, ListMcpSyncClient mcpClients) { return args - { var toolProvider new SyncMcpToolCallbackProvider(mcpClients); ChatClient chatClient ChatClient.builder(chatModel) .defaultToolCallbacks(toolProvider) .build(); String question What is the weather in Amsterdam right now?; System.out.println( USER: question); System.out.println( ASSISTANT: chatClient.prompt(question).call().content()); }; }注意这里defaultToolCallbacks把 MCP 工具挂上了所以模型在需要时会调用天气工具工具执行过程中服务器再发起 Sampling 请求形成完整闭环。4. 验证请求端到端跑通 Sampling 链路配置写完接下来验证。分三步启动 MCP 服务器、设置环境变量、运行客户端。第一步启动 MCP 天气服务器。假设你已经有一个基于 Spring Boot 的 MCP 服务器项目在它目录下执行./mvnw clean install -DskipTests java -jar target/mcp-weather-server-0.0.1-SNAPSHOT.jar服务器启动后会监听 8080提供 SSE 端点。你可以在浏览器或 curl 里访问http://localhost:8080/sse确认它活着。第二步设置环境变量。把 TaoToken 的 Key 注入进去export TAOTOKEN_API_KEYsk-你的Key如果你在 Windows 上用 PowerShell换成$env:TAOTOKEN_API_KEYsk-你的Key。第三步运行客户端./mvnw clean install java -jar target/mcp-sampling-client-0.0.1-SNAPSHOT.jar预期输出会先打印用户问题然后打印助手回答。回答里应该包含天气数据以及服务器通过 Sampling 生成的描述文本。如果你在服务器端加了日志还能看到 Sampling 请求的进出记录。判断链路是否真的通了看三个信号。第一客户端日志里出现MCP LOGGING开头的行说明 SSE 连接建立成功。第二服务器端日志里出现 sampling 请求记录说明服务器确实发起了反向请求。第三客户端返回的文本里包含模型生成的内容而不是空字符串或报错。如果只看到用户问题、没有助手回答大概率是 Sampling 回调没注册上或者modelHint匹配失败抛了异常。这时候把日志级别调到 DEBUG看io.modelcontextprotocol包下的输出。验证模型通道是否正常可以单独打开模型对话页面发一条消息确认 Key 和模型 ID 都对。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。如果那边能正常返回说明通道没问题问题就出在 Spring AI 配置或代码上。5. 常见报错排查401、local proxy failed 与 choices 为空这一节列几个真实会遇到的报错以及对应的排查路径。报错一401 Unauthorized。这是最常见的。原因通常是 Key 没注入、Key 复制不完整、或者base-url写错。先检查环境变量TAOTOKEN_API_KEY是否真的存在用echo $TAOTOKEN_API_KEY确认。然后检查application.yml里api-key的占位符拼写是否和变量名一致。最后确认base-url是https://taotoken.net/api没有多余斜杠或/v1后缀。报错二local proxy failed 或 connection refused。这个通常出现在 MCP 服务器没启动、或者 SSE 地址写错的时候。检查spring.ai.mcp.client.sse.connections.weather-server.url是否指向正确的http://localhost:8080。如果服务器换了端口这里要同步改。另外确认服务器确实暴露了 SSE 端点有些服务器默认只开 stdio不开 SSE。报错三reading choices 时返回空或 NPE。这说明请求发出去了但响应体里没有choices字段。常见原因是模型 ID 写错通道返回了错误信息而不是正常补全结果。把spring.ai.openai.chat.options.model换成通道确认支持的模型 ID比如gpt-4o-mini。如果还是不行用第 2 节的 curl 命令单独测一次对比返回结构。报错四OAuth 相关错误。如果你在配置里误加了 OAuth 相关参数或者用了需要 OAuth 的端点会看到这类报错。TaoToken 的 API 通道用 Bearer Token 即可不需要 OAuth 流程。检查配置里有没有多余的client-id、client-secret字段删掉即可。报错五Sampling 回调抛 IllegalStateException。错误信息是no chat client for hint: xxx。这说明服务器发来的modelHint和客户端注册的 ChatClient key 对不上。检查chatClients的 key 生成逻辑确认modelHint里包含的字符串能匹配到某个 key。比如 key 是openaichatmodelhint 写openai就能命中如果 hint 写gpt就匹配不上。排查时有个通用技巧把logging.level.io.modelcontextprotocol.client和logging.level.io.modelcontextprotocol.spec都设成 DEBUG能看到完整的请求和响应报文。另外Spring AI 的OpenAiChatModel在启动时会打印实际使用的 base-url确认它是不是https://taotoken.net/api。如果你在 Coding Plan 或 Agent 场景里长期跑这套链路建议把 Key 管理、模型路由、重试逻辑都收敛到配置层不要散落在代码里。Coding Plan 入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 适合需要稳定通道和统一计费的场景。6. 继续往下走接入文档与下一步动作整条链路跑通后你会发现 Sampling 的本质是“把模型调用能力从服务器侧转移到客户端侧”。服务器不需要持有 Key只需要声明“我要生成一段文本偏好某个模型”客户端负责落地。这种解耦在多租户、多模型、多环境的项目里特别有价值。下一步可以做的几件事。第一把modelHint的路由逻辑做得更细比如按任务类型、成本、延迟来选择模型而不是简单字符串匹配。第二给 Sampling 回调加缓存相同提示词直接返回缓存结果减少重复调用。第三加降级逻辑某个模型通道不可用时自动切到备用模型。如果你要接更多模型或者想把 Key 管理、用量统计、模型切换都统一起来可以看接入文档里面有完整的 Base URL、鉴权方式和模型列表说明。文档入口在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。最后提醒一个实操细节Spring AI 的版本迭代比较快McpSyncClientCustomizer的包路径和sampling方法签名在不同版本里可能有差异。如果你用的不是 1.1.0先确认对应版本的 API 签名再套用本文代码。跑通一次之后把配置和代码固化下来后面换模型只需要改model字段和modelHint不用动主逻辑。
返回列表