Day-2 复盘 AI agent 的装配链路结论先摆出来TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_end替掉 AiApiNode 里那个指向 xfg 的 baseUrl。链路本身一行不用重写——AiApiNode 从 AiAgentConfigTableVO.Module.AiApi 读出 baseUrl、apiKey、completionsPath、embeddingsPath交给 OpenAiApi.builder() 组装成客户端ChatModelNode 再取出来包成 OpenAiChatModel。真正要动的只是配置表里那四行。开会的时候有人问为什么不能直接在 ChatModelNode 里把模型名换掉就完事。答案藏在动态上下文里ChatModelNode 拿到的不是一份散装配置而是 AiApiNode 已经建好的 OpenAiApi 实例。这个实例一旦带着旧的 baseUrl 生成后面无论怎么改模型名请求还是会打到老地方去。所以这次统一入口的改造点必须落在 AiApiNode 读配置的那一步而不是在调用处打补丁。把这件事拆清楚之后步骤其实很短注册、建 Key、改四件套、重新装配、看日志。麻烦的地方在于每一步都容易顺手写错一个字符比如 baseUrl 末尾多一个斜杠、Key 复制时带上了空格、把官网地址当成接口地址填进去。下面按复盘的真实顺序把整条链路走一遍能直接照抄的地方我都标出来了。1. 复盘起点AiApiNode 里那个指向 xfg 的 baseUrl1.1 RootNode 一路把配置递到 AiApiNode 手里先还原一下 Day-2 白板上画的链路。RootNode 负责整个 agent 的启动与上下文初始化它把配置表读出来转成 AiAgentConfigTableVO然后按 module 分发。走到 AiApi 这个 module 时AiApiNode 承担三件事校验配置完整性、组装客户端、把结果放进 dynamicContext。组装的写法在原项目里很标准基本是 OpenAiApi.builder() 链式调用把 baseUrl、apiKey、completionsPath、embeddingsPath 四个字段依次塞进去。这四个字段全部来自 AiAgentConfigTableVO.Module.AiApi也就是说谁改了配置表谁就决定了整个 agent 后面所有对话请求的去向。问题也就出在这个「谁都能改」上。当初为了快速跑通 demobaseUrl 填的是 xfg 那套第三方通道能出结果没人深究。等到项目里同时跑起来三四条 agent 链路每个人本地配置表还不一样的时候同一句提示词在不同机器上返回的东西开始出现差异排查成本一下子就上来了。1.2 统一入口的价值不在省事而在可对账有人会觉得换 baseUrl 只是换个地址能有多大区别。实际影响在账单和排障上。baseUrl 指向多个不同的第三方通道时一个请求打到哪儿、扣的是谁的额度、返回的 401 是 Key 过期还是通道限流全靠猜。统一入口之后所有 ChatModel 请求都经过同一条通道发出出问题只需要看一处的日志和用量。团队内部再讨论模型效果时至少能确认大家用的是同一批模型、同一套计费口径。这也是这次复盘把「接入配置」单独拎出来讲的原因它不是顺手改改而是后面所有对比实验的前提。2. AiAgentConfigTableVO.Module.AiApi 四件套怎么填2.1 先拿 Key注册和创建都在这一个入口配置表里的 apiKey 是这次唯一需要新申请的东西。打开 TaoToken注册登录后进控制台在 API Keys 页面创建一把新的 Key复制出来先存到本地的密码管理器里。这个页面同时能看到模型广场和用量统计后面验证环节还会回来。有一点提前说清楚免得来回折腾创建 Key 的这个地址是给人点的落地页它和待会儿要填进配置表的接口地址不是同一个东西。落地页负责注册、建 Key、看模型列表、看用量接口地址只有一个形态就是 https://taotoken.net/api。两边的用途别混。2.2 baseUrl、apiKey、completionsPath、embeddingsPath 对照配置表里 Module.AiApi 的四个字段改一个、保留三个对照着填最不容易出错字段填什么注意事项baseUrlhttps://taotoken.net/api末尾不要带 /v1也不要带任何查询参数apiKeyYOUR_API_KEY从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建后替换completionsPathv1/chat/completions保持原文默认值不要改动embeddingsPathv1/embeddings保持原文默认值不要改动最终拼接出来的对话请求地址是 https://taotoken.net/api/v1/chat/completions看着有点重复其实分工很清楚baseUrl 只负责「去哪条通道」路径部分由 completionsPath 补全。这样设计的好处是以后要新增其他 endpoint只改路径字段就够了baseUrl 保持稳定。配置表里写进去大概长这样{ module: ai_api, baseUrl: https://taotoken.net/api, apiKey: YOUR_API_KEY, completionsPath: v1/chat/completions, embeddingsPath: v1/embeddings }2.3 baseUrl 最容易写错的三种形态第一种是把落地页地址直接粘进去。落地页带了一串查询参数协议和主机虽然对但路径和参数都不属于接口请求会返回 404 或者干脆连不上。接口地址就是 https://taotoken.net/api干净到没有多余字符。第二种是在末尾补一个 /v1。很多人习惯了 baseUrl 自带版本号于是写成 https://taotoken.net/api/v1再叠上 completionsPath 里的 v1/chat/completions请求路径就变成了 /api/v1/v1/chat/completions同样打不通。记住一条版本号只在 completionsPath 里出现一次。第三种是把 Key 复制成了带换行或空格的形态。从控制台复制时末尾很容易多一个空格肉眼看不出来报错却会直接给你 401。填完之后建议在本地对 Key 做一次 trim 再写入或者至少在打印日志时把 Key 的前几位和后几位打出来核对。3. OpenAiApi.builder() 与 dynamicContext装配代码怎么改3.1 AiApiNode 里读配置、建客户端、落上下文配置表改完之后代码侧不需要改结构只是把字段来源换到新配置上。AiApiNode 里那段组装逻辑保持原样AiAgentConfigTableVO.Module.AiApi aiApi config.getModule().getAiApi(); OpenAiApi openAiApi OpenAiApi.builder() .baseUrl(aiApi.getBaseUrl()) .apiKey(aiApi.getApiKey()) .completionsPath(aiApi.getCompletionsPath()) .embeddingsPath(aiApi.getEmbeddingsPath()) .build(); dynamicContext.put(openAiApi, openAiApi);这里的顺序值得强调一下先读配置对象再 builder最后放上下文。三步的顺序换了后面的节点就会拿到半成品。特别是动态上下文这一步如果 aiApi 为 null 时仍然把 null 塞进去ChatModelNode 拿到之后会在构建阶段抛空指针报错信息离真正的原因很远排查起来很难受。建议在 builder 之前加一段简短校验baseUrl 非空且以 http 开头、apiKey 非空、两个 path 非空。校验失败直接中断并打印字段名比让异常在下一个节点爆出来友好得多。3.2 ChatModelNode 继续构建 OpenAiChatModelMCP 与 Skills 不动ChatModelNode 这一步几乎零改动它从 dynamicContext 里取出 OpenAiApi再构建对话模型OpenAiApi openAiApi (OpenAiApi) dynamicContext.get(openAiApi); OpenAiChatModel chatModel OpenAiChatModel.builder() .openAiApi(openAiApi) .defaultOptions(OpenAiChatOptions.builder() .model(modelId) .build()) .build();modelId 具体写什么以模型广场当时列表为准别凭记忆手敲。MCP 与 Skills 的装配流程完全不受影响因为它们挂在 ChatModel 之上拿到的是一个已经可用的对话模型实例至于这个实例底层走哪条通道对它们来说是透明的。换句话说这次改造真正影响的范围就是「一个配置项 一次重新装配」爆炸半径很小这也是为什么复盘时大家能很快达成一致。4. 验证这次装配日志、调用和那个 4014.1 先在 AiApiNode 日志里确认地址对了重启服务让 RootNode 重新走一遍装配。第一眼看 AiApiNode 打出来的日志确认三件事baseUrl 是 https://taotoken.net/api、completionsPath 是 v1/chat/completions、apiKey 是刚建的那把只打前后几位。日志里如果出现旧地址说明配置读取的还是缓存或者旧配置源。这种时候别急着改代码先确认配置表刷新的时机——很多项目启动时读一次然后常驻内存热更新要看具体实现。[AiApiNode] baseUrlhttps://taotoken.net/api [AiApiNode] completionsPathv1/chat/completions [AiApiNode] apiKeysk-****abcd [AiApiNode] OpenAiApi built, put into dynamicContext4.2 用同一把 Key 去模型对话里发一条消息日志过了之后再到 TaoToken 模型对话 用同一把 Key 发一条测试消息。这一步是在排除 Key 本身的问题如果这里都不通那就是 Key 建错了或者状态不对跟 AiApiNode 的代码没关系。两边都通了再回到 agent 里跑一次真实对话。观察请求是否成功返回同时回控制台看用量是否记上了。用量有记录说明请求确实经过了统一通道链路是通的。之前每次跑到这一步都会看到的 401正常情况下应该彻底消失了。如果还在直接看下一节。5. 改完还报错三种和本篇配置直接相关的排障5.1 依旧 401配置改了但客户端没重新装配最常见的原因不是 Key 错而是旧的 OpenAiApi 实例还活着。配置表改完只重启了部分服务或者项目里做了客户端缓存AiApiNode 没有重新执行dynamicContext 里放的还是上一轮的对象。排查方式很直接在 AiApiNode 里打一行日志输出 builder 之前读到的 apiKey 后四位。如果和你在控制台创建的那把对不上问题就在读取或缓存环节而不是 TaoToken 侧。这类问题的修复动作是触发一次完整的重新装配而不是反复重建 Key。5.2 路径拼成了 /v1/v1/chat/completions这个坑前面提过一次但实际复现率最高。baseUrl 写成 https://taotoken.net/api/v1completionsPath 又保留了默认的 v1/chat/completions两个版本号叠在一起请求自然打不中。判断方法也简单把最终拼接出来的 URL 打印出来看一眼。正确形态是 https://taotoken.net/api/v1/chat/completions只要中间出现连续两个 v1就是重复了。修的时候改 baseUrl别去动 completionsPath因为那个字段是原文约定好的默认值动它可能影响项目里其他调用点。5.3 embeddings 通、chat 不通模型 ID 对不上还有一种情况是向量化能跑对话跑不起来。这时候大概率不是网络问题而是模型 ID 写错了。ChatModelNode 构建时传进去的 modelId 必须是模型广场里存在的条目拼错一个字母或者自己加日期后缀都会失败。解决办法就是打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end在模型广场里把准确的 ID 复制出来替换掉代码或配置里的那一行然后重新走一遍装配。别靠记忆也别沿用其他平台上的模型名。6. 把统一入口固化下来顺手对一次账6.1 配置回写与后续新增节点的注意事项这次改完建议把四件套的取值写进项目的配置说明文档标注清楚哪几个字段不要动、baseUrl 为什么不能带 /v1。下次再有人加新的 agent 节点时直接照抄这份说明不用再翻代码猜。另外如果后面还要接入新的模型能力优先复用 AiApiNode 已经建好的那个 OpenAiApi 实例。重复 builder 会绕开 dynamicContext 的约定也会让用量统计变得难以对应。统一入口的价值就在于「一处配置、一处生效」破坏这个约定前面的改造就白做了。6.2 下一步去控制台把这次调用对上配置跑通之后回到 控制台 API Keys看一眼刚建的这把 Key 今天的调用量和刚才测试的次数对一对。数对得上说明 AiApiNode 装配出来的客户端确实走的是这条通道。如果这套 agent 之后要长期跑批量任务可以顺便在 Coding Plan 里看看套餐规模够不够避免跑到一半被额度拦住。团队里其他人要复现这套配置把 模型对话 这个页面发给他们让他们先用自己的 Key 验证一次再回来改配置表能省掉一大半「是不是地址填错了」的来回确认。