1. 智能办公 Agent 的真实痛点工具越多Key 越乱做 Spring AI 智能办公 Agent 的同学大概率都经历过这个阶段一开始只挂一个日历工具application.yml里塞一个 API Key 就完事后来加了邮件工具、文档检索工具、RAG 知识库工具每个 MCP Server 背后可能对接不同厂商的模型服务于是配置文件里出现了openai.api-key、claude.api-key、embedding.api-key、rerank.api-key一大堆字段。改一个 Key 要翻三个文件测试环境和生产环境还不一样稍不留神就把测试 Key 提交到了 Git。更麻烦的是 ReAct 循环。Agent 在一次任务里可能先调日历工具查空闲时间再调邮件工具发通知最后调 RAG 工具检索会议背景资料。如果每个工具背后的模型调用走的是不同的 Key 和不同的 Base URL那么一旦某个 Key 额度耗尽或者配置写错整个 ReAct 链条就会在中间某一步断掉报错信息还往往只告诉你“401 Unauthorized”根本定位不到是哪个工具出的问题。这篇要解决的就是这件事用 TaoToken 统一 Key 接入智能办公 Agent 的 MCP 工具链让日历、邮件、文档检索这些工具背后的模型调用都走同一个入口application.yml里只维护一份配置ReAct 循环一次跑通多工具。适合已经写过基础 Spring AI Agent、正在往多工具方向扩展的开发者。2. TaoToken 前置准备一个 Key 管住整条工具链TaoToken 在这里扮演的角色是统一的模型服务入口。你可以把它理解成一个“模型调用的总闸”不管你的 MCP 工具背后要调对话模型、Embedding 模型还是 Rerank 模型都通过同一个 API Key 和同一个 Base URL 发出请求。这样 Spring AI 的OpenAiChatModel、OpenAiEmbeddingModel只需要配置一份凭证工具层就不用各自维护 Key 了。先拿到 Key。访问控制台创建 API Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite创建时建议按用途命名比如spring-ai-office-agent-dev方便后续区分环境。Key 只在创建时完整显示一次复制后先存到本地环境变量里不要直接写进代码。export TAOTOKEN_API_KEYsk-你的实际KeyBase URL 统一用https://taotoken.net/api注意这个地址后面不加 UTM 参数直接作为 Spring AI 的base-url使用。如果你需要确认当前可用的模型名称可以打开模型对话页面手动发一条消息验证https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite在对话页里选一个对话模型发一句“你好”能正常返回就说明 Key 和网络都没问题。这一步看起来简单但能帮你排除掉后面 80% 的“配置写了但调不通”的问题。3. application.yml 统一 Key 与 MCP 客户端配置骨架下面这份配置是整篇文章的核心。思路是把 TaoToken 的 Key 和 Base URL 抽成公共变量对话模型、Embedding 模型、MCP 客户端都引用同一份避免重复。spring: ai: openai: # 统一入口所有模型调用都走 TaoToken base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.3 embedding: options: model: text-embedding-3-small mcp: client: enabled: true name: office-agent-mcp-client version: 1.0.0 # 请求超时办公工具里文档检索可能较慢 request-timeout: 30s # 按类型初始化避免启动时阻塞 type: SYNC # 多个 MCP Server 统一在这里声明 sse: connections: calendar: url: http://localhost:8081 email: url: http://localhost:8082 document: url: http://localhost:8083 knowledge: url: http://localhost:8084 # Agent 自身的 ReAct 参数 office: agent: max-iterations: 15 confirm-dangerous-actions: true几个关键点解释一下。base-url和api-key写在spring.ai.openai下Spring AI 的自动配置会把它注入到OpenAiChatModel和OpenAiEmbeddingModel里MCP 工具内部如果用到ChatClient拿到的也是这份配置。mcp.client.sse.connections下面每个子项就是一个 MCP Server名字对应工具来源URL 指向你本地或远程启动的 MCP Server 进程。如果你用的是 stdio 类型的 MCP Server把sse换成stdio并配置command和args即可Key 依然走上面那份统一配置不需要在 stdio 的启动参数里再传一遍。对应的 Java 配置类可以这样写把 MCP 客户端注入到 Agent 的ToolCallbackProviderConfiguration public class OfficeAgentConfig { Bean public ChatClient chatClient(OpenAiChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem( 你是智能办公助手可以调用日历、邮件、文档检索工具。 调用工具前先说明你的计划危险操作发邮件、删文件必须先确认。 ) .build(); } Bean public ToolCallbackProvider officeTools( SyncMcpToolCallbackProvider mcpToolCallbackProvider) { // 自动聚合所有已连接的 MCP Server 暴露的工具 return mcpToolCallbackProvider; } }SyncMcpToolCallbackProvider会把application.yml里声明的四个 MCP Server 的工具全部注册进来Agent 在 ReAct 循环里就能看到calendar_query、email_send、document_search、knowledge_query这些工具名。4. ReAct 循环调用工具的验证步骤配置写完接下来验证 Agent 能不能在一次对话里串起多个工具。先写一个最小的 ControllerRestController RequestMapping(/agent) public class OfficeAgentController { private final ChatClient chatClient; public OfficeAgentController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(/chat) public String chat(RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }启动应用观察日志里 MCP 客户端的连接情况。正常情况下会看到类似MCP client connected to calendar、MCP client connected to email的输出说明四个工具源都挂上了。然后发一个需要多工具协作的请求curl http://localhost:8080/agent/chat?message帮我查一下明天下午3点有没有空如果没有会议就发邮件通知teamexample.com主题是项目同步会预期 Agent 的 ReAct 过程是这样的第一步Thought 判断需要先查日历Action 调用calendar_query参数是明天下午 3 点Observation 返回“该时段空闲”。第二步Thought 判断需要发邮件Action 调用email_send参数包含收件人、主题、正文Observation 返回“邮件已发送”。第三步Thought 判断任务完成输出最终回答。如果你在日志里看到Tool execution: calendar_query和Tool execution: email_send两条记录并且最终返回了自然语言总结说明统一 Key 配置生效了ReAct 循环成功串起了两个工具。再测一个带 RAG 的场景curl http://localhost:8080/agent/chat?message检索知识库里关于报销流程的文档总结成三点发给我这个请求会触发knowledge_query工具而知识库工具内部通常要调 Embedding 模型做向量检索。因为 Embedding 也走 TaoToken 的统一配置所以不需要额外配 Key直接就能跑。5. 本篇常见错排查5.1 启动时报 401 或 invalid api key先确认环境变量有没有真正传进 JVM。用System.getenv(TAOTOKEN_API_KEY)打印一下如果是 null说明 IDE 的 Run Configuration 里没配环境变量。IDEA 里在 Run/Debug Configurations 的 Environment variables 一栏加上即可。另外检查api-key有没有多写空格YAML 里${TAOTOKEN_API_KEY}前后不要加引号以外的字符。5.2 MCP 工具注册了但 Agent 不调用常见原因是系统提示词里没有明确告诉 Agent 有哪些工具可用。Spring AI 会把工具描述传给模型但如果系统提示词过于简单模型可能倾向于直接回答而不调工具。在defaultSystem里把工具用途写清楚比如“查日程用 calendar_query发邮件用 email_send”命中率会明显提升。5.3 ReAct 循环超过 max-iterations 还没结束办公场景里文档检索和 RAG 查询比较慢如果request-timeout设得太短工具调用会超时Agent 收到超时 Observation 后可能反复重试把迭代次数耗尽。把request-timeout调到 30s 以上同时在系统提示词里加一句“工具调用失败时不要重复调用超过两次直接告知用户”。5.4 多个 MCP Server 工具名冲突如果两个 Server 都暴露了叫search的工具Spring AI 注册时会冲突。解决办法是在 MCP Server 端给工具名加前缀比如calendar_search、document_search或者在客户端配置里用tool-name-prefix区分。命名规范建议从一开始就定好后面加工具才不会乱。5.5 Embedding 维度不匹配导致 RAG 检索报错知识库工具如果之前用的是别的 Embedding 模型换到 TaoToken 的text-embedding-3-small后维度可能对不上向量库会报维度错误。这种情况需要重新灌一遍知识库数据或者确认向量库的维度配置和当前 Embedding 模型一致。6. 下一步把统一 Key 用到长期编码和 Agent 任务里一次配置跑通多工具之后你会发现这套模式可以复用到更多场景。如果你打算把智能办公 Agent 做成长期运行的服务或者接入 Coding Agent 做自动化开发任务可以了解一下 Coding Plan它适合需要持续调用模型、对额度有稳定预期的场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite接入文档里有 Spring AI 和其他框架的完整配置示例遇到 MCP 客户端参数不确定的地方可以直接对照https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你还没创建 Key回到 API Keys 页面建一个专门给办公 Agent 用的https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite我自己的习惯是给每个 Agent 项目单独建一个 Key命名带上项目名和环境这样月底看用量的时候一眼就能分清是哪个服务在消耗额度。统一 Key 最大的好处不是省事而是排障时只需要检查一个地方——Key 没问题那问题一定在工具实现或提示词上定位范围直接缩小一半。
