ARTICLE DETAIL

资讯详情

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

MCP Agent 的 ReAct 循环,模型请求改走 TaoToken

MCP Agent 的 ReAct 循环,模型请求改走 TaoToken 写 MCP Agent 的 ReAct 循环时最容易漏掉的不是工具函数数量而是每一轮 Thought-Action-Observation 都在真实消耗 Token。TaoToken 管的是通道这一段先去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册并创建 API Key把它塞进环境变量再把项目里的 base_url 指向 https://taotoken.net/api。原文在「基于 MCP 的 Agent 开发」那一节把重点压在推理—行动循环、工具参数描述和调用失败后的自愈机制上读起来像一道纯逻辑题真上手才发现循环能转几轮、能不能自愈很大程度取决于模型通道稳不稳。任务规划、工具调度、错误重试这些逻辑必须自己写通道则交给统一 API出问题才分得清是哪一头在掉链子。1. 把 MCP Agent 跑起来之前先算清 ReAct 每一轮的账1.1 原文只讲循环没讲循环的成本从哪来原文那一节的写法很典型先给一张流程图Thought 决定下一步动作Action 去调 MCP 工具Observation 把工具返回塞回上下文然后模型再来一轮。逻辑本身没问题问题在于它默认「调用是免费的、每次都能成功」。如果你照着写第一版通常长这样一个while True模型不调用工具就退出调用工具就执行、追加结果、继续下一轮。这段代码在本机跑一次两次没感觉但只要工具描述写得含糊一点模型就可能在同一轮里反复猜参数或者把刚查到的结果再查一遍。每猜一次就是一次完整的推理请求输入里还带着前面所有轮次的 messages。上下文越堆越长单次请求的输入 Token 是递增的第四轮的成本远高于第一轮。所以「循环」这个词在代码里是个循环在账面上是一条上升曲线。原文没提这一点不代表它不重要。恰恰相反你在调试工具参数描述的时候往往是在最烧 Token 的阶段做最多次试验。这也是我建议先把通道单独理顺的原因——把 Key 和 Base URL 这一段固定下来你才能安心地反复跑循环而不是每改一次描述就担心配额或者被限流打断。1.2 一轮循环里Token 到底花在什么地方拆开看一次典型的 ReAct 步骤至少包含三块输入系统提示词含工具清单的 schema、历史对话包含之前所有轮次的 action 和 observation、以及本轮的用户或工具上下文。工具越多schema 越长系统提示词的开销就越固定轮数越多历史越长增量开销就越明显。输出侧则是模型的 Thought 文本加上 tool_calls 的结构化参数。这意味着两件事。第一工具数量不是越多越好一个只有两三个工具的 Agent系统提示词可能比一个塞了二十个工具的 Agent 短一个数量级。第二Observation 的内容要裁剪工具返回一整页 JSON 和返回三个关键字段对下一轮推理的影响完全不同。原文强调工具参数描述要写清楚其实还有一层隐含收益描述清楚模型一次就能填对参数循环轮数下降账也就下来了。1.3 通道先固定逻辑再慢慢磨搭建顺序建议反过来先写一个最小的模型调用确认 Key 能用、Base URL 能通、模型 ID 存在再去写工具 schema 和自愈分支。这样当循环出现异常时你可以快速排除「是不是通道问题」。如果一开始就把模型调用、工具实现、重试策略揉在一个文件里一旦报 401 或者 404你得翻半天才知道是环境变量没生效还是路径写错了。具体做法是模型调用封装成一个单独的函数只依赖两个环境变量Key 和模型 ID和一个常量 Base URL。MCP 工具层单独一个模块只负责「给定参数返回结果或抛出错误」。循环控制层再单独一层负责步数上限、重复检测、错误回灌。三层之间用普通函数调用连接不互相知道对方的实现细节。这个结构不新鲜但它能让后面所有的调试都变得可定位。2. 在项目里把 base_url 指向 https://taotoken.net/api2.1 先注册、建 Key再去模型广场确认模型 ID准备工作只有三步顺序别颠倒。打开 TaoToken 注册账号进控制台创建一把 API Key然后回到模型广场看当前可用的模型 ID。模型 ID 千万别凭记忆写也不要拿别处文档里的旧名字凑数以模型广场当时列表为准复制下来直接贴进环境变量。Key 只在创建时完整展示一次复制完就存进本地密码管理器或者.env文件。这里有个常见误区有人把官网地址当成接口地址填进 SDK。两者不是一回事。官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 用来注册、建 Key、看模型、查用量填进代码或工具里的 Base URL 是 https://taotoken.net/api末尾不要加/v1也不要带任何查询参数。OpenAI 兼容的 SDK 通常会自动补/v1/chat/completions这一段你再加一层就会变成/v1/v1/...直接 404。2.2 Python 侧的最小调用与 ReAct 骨架先写一个能跑通的最小请求确认通道没问题export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODEL_IDYOUR_MODEL_IDimport os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) resp client.chat.completions.create( modelos.environ[TAOTOKEN_MODEL_ID], messages[{role: user, content: 只回复 ok}], ) print(resp.choices[0].message.content)这条能通再往上叠工具。ReAct 的主循环可以写成下面这样注意max_steps和seen_calls这两个防线import json def run_react(task, tools, dispatch, max_steps8): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: task}, ] seen_calls set() for step in range(max_steps): resp client.chat.completions.create( modelos.environ[TAOTOKEN_MODEL_ID], messagesmessages, tools[t[schema] for t in tools], tool_choiceauto, ) msg resp.choices[0].message if not msg.tool_calls: return msg.content messages.append(msg) for call in msg.tool_calls: name call.function.name args json.loads(call.function.arguments or {}) fingerprint (name, json.dumps(args, sort_keysTrue)) if fingerprint in seen_calls: result f重复调用被拦截{name}请换参数或直接给结论。 else: seen_calls.add(fingerprint) try: result dispatch(name, args) except Exception as exc: result f工具执行失败{type(exc).__name__}: {exc} messages.append({ role: tool, tool_call_id: call.id, content: str(result)[:2000], }) return 达到步数上限未能收敛。str(result)[:2000]这个截断是故意的。工具返回一坨几万字的原始数据下一轮推理的输入成本会立刻失控而且模型很难从中抓重点。截断前最好在工具层就做一次摘要只留下关键字段。2.3 Key 只进环境变量别写进任何会被提交的文件os.environ[TAOTOKEN_API_KEY]这种写法比在代码里写死字符串安全得多。如果你用.env记得把.env加进.gitignore仓库里只留一份.env.example里面写TAOTOKEN_API_KEYYOUR_API_KEY。团队协作时每个人用自己那把 Key出了事能定位到人用量也能分开看。另外提醒一句不要把 Key 拼进 base_url也不要放进 URL 的 query 里。Key 走 Authorization 头Base URL 保持干净的 https://taotoken.net/api。有些第三方工具会在配置界面里给一个「端点」输入框那个框填 Base URL旁边另一个框才是 Key两者别混。3. MCP 工具描述与调用失败后的自愈怎么落地3.1 工具 schema 的 description直接决定循环拐不拐弯MCP 工具最终暴露给模型的是一段 JSON Schema。很多实现只写参数类型description 一句话带过结果模型不知道这个工具到底能干什么、边界在哪于是要么不调用要么乱填参数。原文把这一点列为考点是有道理的描述写得好模型第一轮就能选出正确的工具循环轮数下降。一个写得还算清楚的工具定义长这样{ type: function, function: { name: get_order_status, description: 按订单号读取本地测试目录 orders/ 下的订单快照返回状态与更新时间。只读不写任何数据。, parameters: { type: object, properties: { order_id: { type: string, description: 订单号形如 SO-2024-00001区分大小写 } }, required: [order_id] } } }注意几个细节用途说清楚读本地测试快照、副作用说清楚只读、参数格式给例子。这三条能挡掉相当一部分无效调用。如果你的工具需要访问真实业务库别让 Agent 自己连上去执行正确做法是让模型生成 SQL 或查询语句你自己在本地客户端跑再把结果贴回对话作为下一步输入。3.2 把工具报错原文塞回 Observation而不是自己翻译工具抛异常时两种处理方式差别很大。一种是把异常吞掉回一句「查询失败请重试」模型只能瞎猜重试另一种是把异常原文、工具名、参数一起塞回role: tool的消息里模型下一轮就能据此修正参数或者换工具。第二种才是有效自愈。实践里有几个小技巧。参数校验类错误把期望格式和实际收到的值都带上比如「order_id 期望形如 SO-2024-00001收到 202400001」。工具本身不可用文件不存在、接口超时明确告诉模型「该工具当前不可用请改用 X 或者直接说明无法完成」避免它原地打转。连续两次同一工具同一参数报同样的错就该触发熔断把seen_calls拦下来的那句话换成更明确的指令。自愈还有一个前提错误信息不要被上下文截断吃掉。如果你在 Observation 上做了 2000 字符的硬截断而报错信息恰好排在最末尾模型就看不到了。所以截断逻辑优先保留头部和尾部或者让工具层自己把错误放在返回体的最前面。3.3 步数上限、重复检测与上下文裁剪这三个机制是让循环不失控的最低配置。步数上限最简单max_steps8起步按任务复杂度调超过就返回一个「未收敛」的明确结论而不是继续空转烧 Token。重复检测按「工具名 规范化参数」做指纹命中就拦。上下文裁剪则要复杂一些常见做法是保留系统提示词、首轮用户任务、最近 N 轮完整消息中间的老 observation 替换成一行摘要。裁剪的代价是信息丢失所以顺序要慎先做重复检测和步数上限这两个是无损的只有在长任务里上下文确实撑不住时再上摘要裁剪。另外把每一步的轮次、工具名、参数、耗时、是否命中缓存都记进日志等你回头排查循环为什么跑了七轮这份日志比模型自己的 Thought 有用得多。4. 借 Claude Code / Codex / CC Switch 改这段 Agent 代码4.1 Claude Codesettings.json 里把三项 env 配对如果你打算用 Claude Code 来修改和调试这段 Agent 代码先把它的模型通道切过来编辑~/.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_MODEL_ID } }三项缺一不可ANTHROPIC_BASE_URL是接口地址末尾不要加/v1ANTHROPIC_AUTH_TOKEN填从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建的那把 KeyANTHROPIC_MODEL填模型广场里复制来的 ID。改完重启会话先让它解释一段你已有的工具 schema确认通道通了再让它动 Agent 主循环的代码。4.2 Codexconfig.toml 里配 provider不要套 ANTHROPIC 变量Codex 走的是另一套配置在~/.codex/config.toml里加一个自定义 providermodel YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY然后把TAOTOKEN_API_KEY导出到 shell 环境里。这里最容易犯的错是把上一节的ANTHROPIC_*变量照搬过来Codex 根本不读那几个名字配完只会一直报认证失败。provider 名、env_key里填的变量名、实际导出的变量名这三处必须完全一致。4.3 CC Switch自定义供应商三件套别填错用 CC Switch 管理多套配置时新建一个自定义供应商把三项填对就行Base URL 填 https://taotoken.net/api不带/v1也不要把官网落地页那串带参数的地址粘进来Key 填YOUR_API_KEY模型 ID 从模型广场复制。保存后切换到这个供应商再开一个会话发条测试消息。如果你同时在用好几家供应商切换前先确认当前会话已经断开重连否则旧配置可能还在内存里生效。5. 循环跑通后怎么确认轮数和调用对得上5.1 本地日志和 TaoToken 侧对账跑完一轮完整任务先看本地日志总共几轮推理、每轮调了哪些工具、有没有命中重复拦截、有没有走到max_steps。再打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 进控制台看调用记录核对次数和时间点是否对得上。如果本地记了 6 次推理、控制台只显示 3 次说明中间有请求在到达服务前就失败了通常是网络或者配置问题不是模型侧的锅。对账这件事在调试期比省下的那点钱重要得多它能告诉你循环到底转了几圈。很多人以为自己的 Agent 三轮就收敛一看记录跑了十一轮多出来的全花在参数反复猜上回来改工具描述立刻见效。5.2 本篇可能遇到的几个报错现象常见原因处理方式401 未授权Key 没进环境变量或变量名和配置里写的不一致打印环境变量确认检查env_key拼写404 找不到路径Base URL 末尾多了/v1或往官网落地页地址上打请求改成 https://taotoken.net/api去掉多余后缀模型不存在模型 ID 凭记忆写或沿用了别处文档的旧名回模型广场复制当前可用的 ID循环不收敛工具描述含糊模型反复猜参数补 description、加示例值、开重复拦截上下文超限observation 太长轮数堆太多工具层做摘要observation 头尾保留截断5.3 排查顺序别乱出问题时的排查顺序建议固定下来先确认单次调用能通再确认工具能单独跑通最后才看循环。顺序颠倒的话你会在一堆交织的错误里来回猜。单次调用用最朴素的 messages 测工具用固定参数直接调函数测两者都正常再跑循环并把每轮日志打出来。还有一点值得强调Agent 的报错跟普通程序的报错不一样模型的「思考」是文本不是确定性的控制流。同一份工具定义今天能收敛明天可能多绕两轮。所以别把偶然跑通当成稳定多跑几组不同参数的任务看看轮数分布再决定要不要继续加工具。6. 循环调稳之后把 Key 和用量一起管起来Agent 跑顺以后剩下的就是长期维护Key 别只有一把、模型 ID 别写死在代码里、用量要有地方看。测试和日常调试可以用同一把 Key但如果你把 Agent 部署到会持续跑的地方建议单独建一把方便从用量里把它和手工调试区分开。想先确认刚才那次 ReAct 循环的调用有没有记上账可以在 TaoToken 模型对话 里用同一把 Key 发一条测试消息对照控制台记录看时间点是否吻合。如果这个 Agent 要长期挂着跑循环去 Coding Plan 看套餐档位需要新 Key 或者给不同环境分流在 控制台 API Keys 里建用 Claude Code 改这段代码时的环境变量对照见 Claude Code 接入文档。通道这部分封好之后就可以回到原文那一节把精力放在工具参数描述和自愈分支上了——那才是 MCP Agent 真正的技术含量所在。
返回列表