尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

[MCP系列3]做一个会调用MCP工具的智能体--完善架构长文详解版:用 TaoToken 统一 Key 打通 ReAct Agent 工具链

发布时间:2026/9/29 4:12:17

资讯中心
01
ARTICLE

[MCP系列3]做一个会调用MCP工具的智能体--完善架构长文详解版:用 TaoToken 统一 Key 打通 ReAct Agent 工具链

[MCP系列3]做一个会调用MCP工具的智能体--完善架构长文详解版:用 TaoToken 统一 Key 打通 ReAct Agent 工具链
1. 从 ReAct 循环到工具调用智能体架构到底卡在哪如果你已经跑通过一个基础 Agent大概率遇到过这几类问题模型明明该调工具却直接编答案、工具调用参数格式飘忽不定、多轮对话后上下文爆掉、换一个模型供应商就要重写一遍适配层。这些不是模型能力问题而是架构没完善。MCP 智能体的核心链路是 ReAct 循环加工具调用而工具调用的稳定性取决于三件事统一的模型接入通道、可预测的输出解析、以及上下文与工具注册的协同管理。这篇是 MCP 系列第三篇面向已经跑通基础 Agent 的开发者交付一套可复制的 config.toml 与 settings.json 骨架演示如何用 TaoToken 统一 Key 打通 ReAct Agent 的工具链并给出验证 Agent 是否正确调用工具的排查动作。适合谁手上有本地 MCP 工具、想让 Agent 稳定调用、又不想为每个模型供应商维护一套 Key 和适配代码的人。读完你能拿到一份能直接改参数就跑的配置以及一套定位“工具没被调用”的排查路径。2. TaoToken 前置统一 Key 与 API 通道在讲配置之前先把接入层说清楚。多模型场景下最烦的是每个供应商一套 api_key、一套 base_url、一套响应结构。TaoToken 的作用是把这些收敛成一个入口你只需要一个 Key通过统一的 API 通道访问不同模型Agent 侧只认一个 base_url 和一套 OpenAI 兼容的请求格式。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基地址https://taotoken.net/api你需要先拿到 Key再去配置 Agent。拿 Key 的入口在控制台的 API Keys 页面控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite拿到 Key 之后Agent 的模型调用层就只需要维护一个 provider 配置。这样做的好处很直接ReAct 循环里每次推理都走同一个通道工具调用的请求格式不会因为换模型而变排查问题时也只需要看一个出口。注意Key 只放在本地配置文件或环境变量里不要提交到代码仓库。下面配置里我用${TAOTOKEN_API_KEY}占位实际运行时从环境变量注入。3. 可复制配置config.toml 与 settings.json 骨架这一节是全文的核心交付。我把它拆成三块模型接入配置、Agent 运行配置、MCP 工具注册配置。三块拼起来就是一个能跑的最小骨架。3.1 config.toml模型与 Agent 运行参数# config.toml [llm] # 统一走 TaoToken 通道Agent 侧只认这一个 base_url provider taotoken base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} model claude-sonnet-4-20250514 timeout 120 max_retries 3 retry_delay 1.5 [llm.params] # ReAct 需要格式稳定温度压低 temperature 0.1 top_p 0.9 max_tokens 2048 [agent] name mcp-react-agent max_steps 6 # 单轮推理最多允许的工具调用次数防止死循环 max_tool_calls_per_step 3 # 上下文 token 上限超过触发裁剪 context_max_tokens 8192 # 保留最近 N 轮完整对话 keep_recent_turns 6 [agent.react] # ReAct 输出格式约束解析器按这个格式提取 action_format json # 是否在 Observation 后强制重新推理 force_reobserve true [logging] level INFO # 记录每次工具调用的入参和返回排查必备 log_tool_calls true log_llm_raw true这里几个参数值得单独说。max_steps控制 ReAct 循环上限太小会导致工具还没调完就退出太大会让一次请求烧掉大量 token。max_tool_calls_per_step是防止模型在一步里反复调同一个工具。context_max_tokens要和模型的真实上下文窗口对齐留出工具返回结果的余量。3.2 settings.jsonMCP 工具注册与权限{ mcpServers: { local-db: { transport: stdio, command: python, args: [-m, mcp_server_db, --config, ./db_config.json], env: { DB_READONLY: true }, enabled: true, tools: { query_database: { enabled: true, require_confirm: false, timeout: 30 } } }, file-system: { transport: stdio, command: npx, args: [-y, modelcontextprotocol/server-filesystem, ./workspace], enabled: true, tools: { read_file: { enabled: true }, write_file: { enabled: false } } } }, toolPolicy: { defaultTimeout: 30, maxResultBytes: 65536, denyByDefault: true } }denyByDefault: true是个关键安全开关没在tools里显式开启的工具一律不可调用。写操作类工具比如write_file默认关掉需要时再单独打开。maxResultBytes限制工具返回体积避免一次查询把上下文撑爆。3.3 把两者接起来Agent 初始化代码import os import json import tomllib from pathlib import Path def load_config(config_pathconfig.toml, settings_pathsettings.json): with open(config_path, rb) as f: config tomllib.load(f) # 注入环境变量里的 Key api_key os.environ.get(TAOTOKEN_API_KEY) if not api_key: raise RuntimeError(缺少 TAOTOKEN_API_KEY 环境变量) config[llm][api_key] api_key with open(settings_path, r, encodingutf-8) as f: settings json.load(f) return config, settings def build_llm_client(config): from openai import OpenAI llm config[llm] return OpenAI( api_keyllm[api_key], base_urlllm[base_url], timeoutllm[timeout], ) if __name__ __main__: config, settings load_config() client build_llm_client(config) print(模型通道:, config[llm][base_url]) print(已注册 MCP 服务:, list(settings[mcpServers].keys()))跑这段代码如果打印出模型通道和已注册服务列表说明配置层已经通了。接下来才是 ReAct 循环和工具调用的对接。4. 验证请求确认 Agent 真的调用了工具配置写完不代表工具会被调用。这一节给一套验证动作从“模型能回话”到“模型正确调工具”逐层确认。4.1 第一步确认模型通道可用resp client.chat.completions.create( modelconfig[llm][model], messages[{role: user, content: 只回复两个字收到}], temperature0.1, ) print(resp.choices[0].message.content)如果这一步报 401检查 Key 是否注入成功报 404检查base_url是否写成了https://taotoken.net/api不要带多余路径。模型对话入口在这里可以对照测试https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite4.2 第二步确认工具列表被正确注入提示词ReAct 的关键是把可用工具的描述塞进 system prompt。验证方法是打印实际发给模型的 messagesdef build_react_messages(user_input, tools_desc, historyNone): system_prompt f你是一个可以调用工具的智能体。 可用工具 {tools_desc} 严格按以下格式输出 Thought: 你的推理 Action: {{method:tools/call,params:{{name:工具名,arguments:{{...}}}}}} Observation: 留空等待工具返回 Answer: 最终回答或留空等待工具结果 messages [{role: system, content: system_prompt}] if history: messages.extend(history) messages.append({role: user, content: user_input}) return messages tools_desc \n.join( f- {name}: {info.get(description, )} for name, info in settings[mcpServers][local-db][tools].items() ) msgs build_react_messages(查一下用户表里有多少条记录, tools_desc) print(msgs[0][content])确认工具名和描述都出现在 system prompt 里。如果工具描述为空模型就不知道这个工具能干什么自然不会调用。4.3 第三步跑一轮完整 ReAct 并检查工具调用import re def parse_action(text): match re.search(rAction:\s*(\{.*?\})\s*(?:\n|$), text, re.DOTALL) if not match: return None try: action json.loads(match.group(1)) params action.get(params, {}) return params.get(name), params.get(arguments, {}) except json.JSONDecodeError: return None def run_react_once(user_input, tools_desc, tool_executor): messages build_react_messages(user_input, tools_desc) for step in range(config[agent][max_steps]): resp client.chat.completions.create( modelconfig[llm][model], messagesmessages, temperatureconfig[llm][params][temperature], ) reply resp.choices[0].message.content print(f--- step {step} ---\n{reply}\n) parsed parse_action(reply) if parsed: name, args parsed result tool_executor(name, args) messages.append({role: assistant, content: reply}) messages.append({ role: user, content: fObservation: {json.dumps(result, ensure_asciiFalse)} }) continue if Answer: in reply: return reply.split(Answer:, 1)[1].strip() return 达到最大步数未得到最终答案跑起来后观察日志如果 step 0 就出现Action:且工具名正确说明工具调用链路通了。如果模型一直输出Answer:而不调工具问题在提示词或工具描述不在模型。4.4 成功结果长什么样一次正常的工具调用日志应该包含这几个特征Thought 里提到要查数据库、Action 是合法 JSON、工具名和注册名完全一致、Observation 里是工具真实返回、下一轮推理基于 Observation 给出 Answer。任何一环缺失都对应下面排查表里的某一项。5. 本篇常见错排查5.1 模型不调工具直接编答案最常见。原因通常是工具描述太弱或者 system prompt 没强调“需要外部数据时必须调工具”。改法在工具描述里写清楚适用场景比如“当用户询问数据库中的记录时使用此工具”。另外把temperature压到 0.1 以下减少模型自由发挥。5.2 Action 不是合法 JSON模型输出了 markdown 代码块包裹的 JSON或者用了单引号。解析器要能容错先剥离 json 标记再用json.loads失败时尝试ast.literal_eval。更稳的做法是在 system prompt 里明确写“Action 只输出纯 JSON不要加代码块标记”。5.3 工具名对不上注册名是query_database模型输出queryDatabase或query-db。这是命名风格不一致导致的。统一用下划线命名并在工具描述里把准确名称再写一遍。解析到未知工具名时不要静默失败把可用工具名列表作为 Observation 返回给模型让它自我纠正。5.4 上下文超限导致工具结果被截断工具返回大 JSON 时如果maxResultBytes没限制一次就能把上下文撑爆模型看不到完整结果就开始编。改法在工具执行层截断返回值只保留关键字段同时把context_max_tokens设成模型窗口的 70% 左右留出余量。5.5 多轮后工具调用失效对话轮次多了以后早期的 system prompt 被裁剪掉模型忘了 ReAct 格式。改法裁剪时始终保留 system prompt只裁中间的历史消息。keep_recent_turns控制保留最近几轮system prompt 单独标记为不可裁剪。5.6 换模型后行为突变不同模型对 ReAct 格式的遵循度不一样。统一走 TaoToken 通道的好处是切换模型只改model字段但提示词可能需要微调。建议在切换后先跑 4.3 的验证脚本确认新模型能正确输出 Action 再上生产。6. 长期编码与 Agent 场景的接入建议如果你打算把这套 Agent 用在长期编码、批量任务或常驻服务里按量计费的模型通道在成本上不好控。这种场景更适合用 Coding Plan 这类包周期方案把模型调用成本固定下来Agent 可以放心跑多轮 ReAct 而不用每次担心 token 账单。Coding Plan 入口https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite接入文档在这里配置字段和错误码都有说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你用的是 Claude Code 这类编码 AgentAnthropic 兼容通道的配置方式单独有一页https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite最后给一个实操建议把 4.3 的验证脚本存成一个verify_agent.py每次改完 config.toml 或 settings.json 就跑一遍。工具调用这类问题靠读代码很难发现靠跑一遍日志一眼就能定位。我试过在改完工具注册后忘了同步描述结果模型三天没调过那个工具直到跑验证脚本才暴露出来。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。