
1. 从零搭一个能跑的多 Agent Demo为什么先要解决 Key 和模型路由多 Agent 协作听起来很酷但真正动手时第一个卡住大多数人的不是架构设计而是模型接入。你要让 Codex 当主控做任务拆解和 Function Call 调度让 Deepseek 当子 Agent 处理具体子任务结果发现两家模型各有各的 API 地址、各有各的鉴权方式、各有各的返回格式。光是让两个模型能同时被调用就得写两套适配代码。我试过最笨的办法给每个模型单独维护一份配置Codex 用一套 base_url 和 keyDeepseek 用另一套。代码里到处是 if model codex 的分支判断改一个模型参数要翻三个文件。后来换成 TaoToken 统一 Key 之后模型抽象层只需要维护一份配置切换模型只改一个 model 字段业务代码零改动。这篇要做的 Demo 目标很明确用 Codex 作为主控 Agent负责理解用户意图、拆解任务、通过 Function Call 调用工具用 Deepseek 作为子 Agent负责执行具体的子任务并返回结果。两个 Agent 通过统一的消息协议流转最终完成一个端到端的协作流程。适合谁适合已经了解 Function Call 基本概念、想动手搭一个多 Agent 协作原型的开发者。不需要你之前用过 TaoToken但需要你有 Python 基础能跑通 HTTP 请求。整个 Demo 的核心链路是这样的用户输入 → Codex 主控解析意图 → Codex 决定调用哪个工具 → 工具内部转发给 Deepseek 子 Agent → Deepseek 返回结果 → Codex 汇总输出。这里面最关键的是 Function Call 的标准化以及两个模型之间的消息格式对齐。下面我会给出完整的配置片段、Agent 注册代码、路由逻辑以及一轮可复现的验证步骤。2. TaoToken 统一 Key 的前置准备一个 Key 管多个模型在开始写 Agent 代码之前先把模型接入层搞定。TaoToken 的核心价值在于你只需要一个 API Key就能调用 Codex、Deepseek 等多个模型不用分别去各家平台注册、充值、管理密钥。对于多 Agent Demo 来说这意味着模型抽象层可以做得非常薄。先拿到 Key。访问 https://taotoken.net/api-keys 创建一个 API Key复制保存好。这个 Key 后面会用在所有模型的请求头里。注意不要把它硬编码到代码里提交到仓库建议用环境变量管理。TaoToken 的 API 入口是 https://taotoken.net/api兼容 OpenAI 的接口格式。也就是说你原来用 openai 库写的代码只需要改 base_url 和 api_key 两个参数就能切换到 TaoToken 上调用不同模型。模型 ID 方面Codex 系列可以用 gpt-4o 或 gpt-4o-mini 这类标识Deepseek 系列用 deepseek-chat 或 deepseek-v4-flash。具体可用模型列表可以在模型对话页面查看https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat这里有一个关键设计决策多 Agent 系统里主控 Agent 和子 Agent 用不同的模型但走同一个 API 入口。这样做的好处是模型抽象层只需要维护一份 base_url 和一份 api_key模型差异通过 model 参数区分。下面是一个最小化的配置示例用 Python 的 dataclass 定义模型配置from dataclasses import dataclass, field from typing import Optional dataclass class ModelConfig: name: str model_id: str base_url: str https://taotoken.net/api api_key: str temperature: float 0.7 max_tokens: int 4096 # 从环境变量读取 Key import os TAOTOKEN_API_KEY os.environ.get(TAOTOKEN_API_KEY, ) # 主控 AgentCodex codex_config ModelConfig( namecodex_controller, model_idgpt-4o, api_keyTAOTOKEN_API_KEY, temperature0.3, # 主控需要稳定温度调低 ) # 子 AgentDeepseek deepseek_config ModelConfig( namedeepseek_worker, model_iddeepseek-chat, api_keyTAOTOKEN_API_KEY, temperature0.7, # 子任务可以稍微灵活 )如果你更习惯用配置文件的方式也可以用 JSON 或 TOML。比如用 TOML 写一个config.toml[taotoken] base_url https://taotoken.net/api api_key sk-你的Key [agents.codex] model_id gpt-4o temperature 0.3 max_tokens 4096 [agents.deepseek] model_id deepseek-chat temperature 0.7 max_tokens 4096然后在代码里用tomllibPython 3.11或tomli读取。这样配置和代码分离切换模型只改配置文件不用动业务逻辑。有一点需要注意TaoToken 的 API 兼容 OpenAI 格式所以你可以直接用openai这个 Python 库只需要在初始化 client 的时候传入base_url和api_key。不需要额外安装什么 SDK。如果你用的是其他语言的 OpenAI SDK原理一样改 base_url 即可。拿到 Key 之后建议先做一个最简单的连通性测试确认 Key 有效、模型可调用。这一步不要跳过否则后面 Agent 跑不通的时候你分不清是 Key 的问题还是代码的问题。测试代码很简单from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyTAOTOKEN_API_KEY, ) resp client.chat.completions.create( modelgpt-4o, messages[{role: user, content: 回复 OK 两个字母}], ) print(resp.choices[0].message.content)如果输出包含 OK说明 Key 和网络都没问题。如果报 401检查 Key 是否复制完整如果报 model not found检查模型 ID 是否正确。这一步跑通之后再进入 Agent 代码的编写。3. 可复制的 Agent 注册与 Function Call 路由配置现在进入核心部分定义工具、注册 Agent、实现 Function Call 路由。整个设计思路是Codex 主控 Agent 拥有一个工具列表每个工具对应一个子 Agent 的能力。当 Codex 决定调用某个工具时路由层把请求转发给对应的子 AgentDeepseek子 Agent 执行完返回结果再由 Codex 汇总。先定义工具 Schema。Function Call 的关键是工具描述要清晰模型才能正确选择。这里定义两个工具一个是analyze_sentiment情感分析一个是summarize_text文本摘要。这两个工具实际由 Deepseek 子 Agent 执行。import json from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyTAOTOKEN_API_KEY, ) # 工具定义遵循 JSON Schema 标准 TOOLS [ { type: function, function: { name: analyze_sentiment, description: 分析一段文本的情感倾向返回 positive/negative/neutral 及置信度。当用户需要判断评论、反馈的情感时调用。, parameters: { type: object, properties: { text: { type: string, description: 待分析的文本内容, } }, required: [text], }, }, }, { type: function, function: { name: summarize_text, description: 对长文本进行摘要返回不超过 100 字的精简摘要。当用户需要提炼文章、报告的核心内容时调用。, parameters: { type: object, properties: { text: { type: string, description: 待摘要的长文本, }, max_length: { type: integer, description: 摘要最大字数默认 100, default: 100, }, }, required: [text], }, }, }, ]接下来是子 Agent 的执行函数。每个工具对应一个函数函数内部调用 Deepseek 模型完成实际任务。注意这里用的是同一个 client只是 model 参数不同。def run_deepseek_subtask(task_prompt: str) - str: 子 Agent 执行器用 Deepseek 完成具体子任务 resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是一个执行子任务的助手请直接返回结果不要额外解释。}, {role: user, content: task_prompt}, ], temperature0.7, ) return resp.choices[0].message.content def handle_tool_call(tool_name: str, arguments: dict) - str: 工具路由根据工具名分发到对应的子 Agent 执行 if tool_name analyze_sentiment: text arguments.get(text, ) prompt f请分析以下文本的情感倾向只返回 positive、negative 或 neutral 其中一个词\n{text} return run_deepseek_subtask(prompt) elif tool_name summarize_text: text arguments.get(text, ) max_len arguments.get(max_length, 100) prompt f请将以下文本摘要为不超过 {max_len} 字\n{text} return run_deepseek_subtask(prompt) else: return json.dumps({error: f未知工具: {tool_name}}, ensure_asciiFalse)然后是主控 Agent 的循环逻辑。Codex 接收用户输入判断是否需要调用工具。如果需要解析 tool_calls执行工具把结果回传给 CodexCodex 再生成最终回复。def run_controller_agent(user_input: str) - str: 主控 AgentCodex 负责意图理解和任务调度 messages [ {role: system, content: 你是一个任务调度主控。根据用户需求选择合适的工具来完成任务。如果不需要工具直接回答。}, {role: user, content: user_input}, ] # 第一轮Codex 决定是否调用工具 resp client.chat.completions.create( modelgpt-4o, messagesmessages, toolsTOOLS, tool_choiceauto, temperature0.3, ) msg resp.choices[0].message # 如果没有工具调用直接返回 if not msg.tool_calls: return msg.content # 有工具调用执行每个工具收集结果 messages.append(msg) # 把 assistant 的 tool_calls 消息加入历史 for tool_call in msg.tool_calls: fn_name tool_call.function.name fn_args json.loads(tool_call.function.arguments) print(f[主控] 调用工具: {fn_name}, 参数: {fn_args}) # 路由到子 Agent 执行 result handle_tool_call(fn_name, fn_args) print(f[子Agent-Deepseek] 返回: {result}) # 把工具结果加入消息历史 messages.append({ role: tool, tool_call_id: tool_call.id, content: result, }) # 第二轮Codex 根据工具结果生成最终回复 final_resp client.chat.completions.create( modelgpt-4o, messagesmessages, temperature0.3, ) return final_resp.choices[0].message.content这段代码的核心在于消息流转Codex 发出 tool_calls → 路由层执行工具实际调用 Deepseek→ 工具结果以role: tool的消息回传 → Codex 生成最终答案。整个过程中Codex 和 Deepseek 走的是同一个 API 入口只是 model 参数不同。这就是统一 Key 带来的便利你不需要为两个模型分别维护 client 实例。如果你用的是 Claude Code 或者 Cline 这类工具做开发辅助配置方式类似。以 Cline 的 MCP 配置为例在 settings.json 里加上{ mcpServers: { taotoken: { command: npx, args: [-y, taotoken/mcp-server], env: { TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_BASE_URL: https://taotoken.net/api } } } }三件套记住Base URL 是https://taotoken.net/apiKey 从 API Keys 页面获取Model ID 根据你要用的模型填Codex 用 gpt-4oDeepseek 用 deepseek-chat。这三样配对了接入就不会出大问题。4. 端到端验证一轮完整的请求与预期输出代码写完了现在跑一轮完整的验证。准备一段测试文本包含情感倾向和需要摘要的内容。比如这样一段用户反馈这个产品我用了一个月整体感觉还不错界面简洁操作流畅。但是最近更新之后偶尔会出现卡顿的情况希望官方能优化一下性能。另外客服响应速度挺快的上次提的问题当天就解决了。把这段文本作为用户输入传给主控 Agent。预期流程是Codex 判断需要调用analyze_sentiment和summarize_text两个工具分别转发给 Deepseek 执行最后汇总结果。调用代码if __name__ __main__: user_text 这个产品我用了一个月整体感觉还不错界面简洁操作流畅。 但是最近更新之后偶尔会出现卡顿的情况希望官方能优化一下性能。 另外客服响应速度挺快的上次提的问题当天就解决了。 result run_controller_agent(f请分析以下用户反馈的情感倾向并给出摘要\n{user_text}) print(\n 最终输出 ) print(result)预期输出实际内容会因模型版本略有差异但结构一致[主控] 调用工具: analyze_sentiment, 参数: {text: 这个产品我用了一个月...} [子Agent-Deepseek] 返回: positive [主控] 调用工具: summarize_text, 参数: {text: 这个产品我用了一个月..., max_length: 100} [子Agent-Deepseek] 返回: 用户使用产品一个月整体满意界面简洁流畅但更新后偶有卡顿希望优化性能客服响应快。 最终输出 根据分析这段用户反馈的情感倾向为正面positive。摘要如下用户使用产品一个月整体满意界面简洁流畅但更新后偶有卡顿希望优化性能客服响应快。如果你看到类似输出说明多 Agent 协作链路跑通了。Codex 负责调度Deepseek 负责执行Function Call 完成了任务分发和结果回传。整个过程只用了 TaoToken 的一个 Key没有为两个模型分别配置。再验证一个边界情况用户输入不需要工具的场景。比如输入「你好介绍一下你自己」Codex 应该直接回复不触发任何工具调用。预期输出是 Codex 的直接回答控制台不会打印[主控] 调用工具的日志。这验证了tool_choiceauto的逻辑是正确的模型自己判断是否需要工具。还有一个值得测试的场景多轮工具调用。比如用户输入「先分析情感如果正面就摘要否则不摘要」。这需要 Codex 先调用情感分析根据结果决定是否调用摘要工具。当前代码只做了一轮工具调用循环如果要支持多轮需要把工具执行逻辑包在一个 while 循环里直到模型不再返回 tool_calls。这个扩展留给你自己实现思路是一样的。验证通过后你可以把这段代码保存为multi_agent_demo.py后续在此基础上增加更多工具和子 Agent。比如加一个translate_text工具路由到另一个模型或者加一个search_web工具接入外部 API。核心路由逻辑不变只需要在TOOLS列表和handle_tool_call函数里增加分支。5. 常见报错排查401、local proxy failed、reading choices 怎么处理多 Agent Demo 跑不起来大概率是下面几类错误。我按实际遇到的频率排个序逐个说排查方法。401 Unauthorized。这是最常见的错误原因通常是 Key 不对。检查三件事第一Key 是否从 https://taotoken.net/api-keys 正确复制有没有多余空格第二环境变量TAOTOKEN_API_KEY是否真的被读取到了可以在代码里 print 一下长度确认第三请求头里的 Authorization 格式是否是Bearer sk-xxx。如果用的是 openai 库它会自动加 Bearer 前缀你只需要传 api_key 参数。如果手动构造 HTTP 请求记得加Authorization: Bearer key。local proxy failed / connection error。这个报错通常和网络环境有关。先确认你的机器能正常访问https://taotoken.net/api可以用 curl 测试curl -I https://taotoken.net/api。如果返回 200 或 401说明网络通如果超时检查 DNS 或本地网络设置。另外注意有些公司内网会拦截外部 API 请求这种情况需要联系网络管理员。不要尝试用任何非正规的网络工具合规接入即可。reading choices 报错 / KeyError: choices。这个错误说明 API 返回的 JSON 结构里没有choices字段。常见原因有两个一是模型 ID 写错了API 返回了错误信息而不是正常的 completion 结果二是请求参数不合法比如tools格式不对。排查方法把原始响应 print 出来看。在代码里加一行print(resp)或者捕获异常后打印e.response.text。如果是模型 ID 问题对照模型列表确认正确的 ID如果是 tools 格式问题检查 JSON Schema 是否符合规范特别是required字段和properties的对应关系。OAuth / authentication 相关报错。如果你用的是 Claude Code 或 Codex CLI 这类工具可能会遇到 OAuth 认证问题。这类工具通常有自己的认证流程但你可以通过配置 API Key 的方式绕过。以 Codex 为例在~/.codex/auth.json里配置{ openai_api_key: sk-你的TaoToken Key, base_url: https://taotoken.net/api }配置完之后重启工具它就会用这个 Key 去请求。注意auth.json的路径和字段名可能因版本而异以你本地工具的文档为准。核心是三件套Base URL、Key、Model ID三者对齐就不会有认证问题。Function Call 不触发 / 模型不调用工具。这个不是报错但很常见。原因是工具描述不够清晰模型不知道什么时候该调用。改进方法在description里写清楚调用时机和边界条件。比如不要只写「分析情感」要写「当用户需要判断评论、反馈的情感倾向时调用返回 positive/negative/neutral」。另外tool_choice参数设为auto让模型自己判断如果设为none就永远不会调用工具。如果希望强制调用某个工具可以设为{type: function, function: {name: analyze_sentiment}}。子 Agent 返回结果为空。检查 Deepseek 的调用是否成功。可以在run_deepseek_subtask函数里加日志打印resp.choices[0].message.content的长度。如果为空可能是 prompt 太长超出了 max_tokens或者模型返回了空字符串。调整max_tokens参数或者在 prompt 里明确要求「必须返回结果」。排查问题的通用思路先确认单模型调用能通再确认工具定义格式正确最后确认消息流转逻辑没有丢消息。把每一步的输入输出都打日志问题定位会快很多。6. 把 Demo 跑通之后下一步可以怎么扩展Demo 跑通只是起点。实际的多 Agent 系统里还有几个方向值得继续深入。第一个方向是增加重试和降级机制。当前代码里如果 Deepseek 调用失败整个流程就断了。可以在handle_tool_call里加 try-except失败时返回一个结构化的错误信息给 Codex让 Codex 决定是重试还是换一个工具。这就是监督者模式的雏形主控 Agent 不仅负责调度还负责容错决策。第二个方向是上下文管理。当前 Demo 每次请求都是独立的没有记忆。如果要支持多轮对话需要维护一个消息历史列表并且做滑动窗口截断避免 token 超限。更进一步的可以加一个摘要 Agent当历史消息太长时自动压缩成摘要再传给主控。第三个方向是工具生态扩展。当前只有两个工具实际系统里可能有几十个。工具多了之后Codex 的选择准确率会下降。这时候可以引入路由模式先用一个轻量级分类器判断意图类别再把对应类别的工具列表传给 Codex缩小选择范围。如果你想把 Demo 变成长期可用的编码助手可以考虑用 Coding Plan 来管理模型调用额度https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan 对于需要频繁调用多个模型的场景统一管理比分别充值更方便。代码层面的扩展建议先把当前 Demo 拆成模块config.py管配置tools.py管工具定义agents.py管 Agent 执行router.py管路由。这样后续加功能不会把代码写成一团。另外把 API Key 从代码里彻底剥离用环境变量或密钥管理服务避免泄露。最后说一个实际踩过的坑多 Agent 系统里消息格式的对齐比模型选择更重要。Codex 返回的 tool_calls 格式和 Deepseek 期望的输入格式可能不一样中间需要一层转换。当前 Demo 里这层转换是隐式的都走 OpenAI 兼容格式但如果接入非 OpenAI 格式的模型就需要显式写适配器。这也是为什么统一 API 入口能省事格式对齐的工作由网关层做了你只需要关注业务逻辑。把 Demo 跑通然后按上面的方向逐步扩展你就能得到一个可用的多 Agent 协作原型。核心思路不变主控负责决策子 Agent 负责执行Function Call 负责连接统一 Key 负责简化接入。