news 2026/9/28 4:04:34

我花 6 步,从零做了一个 AI Agent:TaoToken 统一 Key 接入 ReAct 与 MCP 配置骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我花 6 步,从零做了一个 AI Agent:TaoToken 统一 Key 接入 ReAct 与 MCP 配置骨架

1. 从零搭一个 AI Agent,为什么卡在“接入”这一步

AI Agent 这个词这两年出现频率很高,但真正动手从零写一个能跑的最小 Agent,很多人会卡在同一个地方:模型通道怎么接、工具怎么挂、ReAct 循环怎么转起来。我自己第一次写的时候,代码逻辑其实不复杂,难的是把“模型调用”和“工具调用”这两条链路拼成一个闭环,还要保证每一轮推理都能拿到上一轮工具执行的真实结果。

这篇要做的,是一个最小但完整的 AI Agent:用 ReAct 推理循环作为主干,用 MCP 作为工具扩展骨架,用 TaoToken 统一 Key 作为模型接入通道。适合已经会写 Python、想搞清楚 Agent 内部到底怎么转的人,也适合已经在用各种编程助手、想自己拆一遍原理的人。走完之后你会得到一个能跑通“推理 → 行动 → 观察 → 再推理”的最小 Agent,并且能完成一次真实的工具调用验证。

整个链路我拆成 6 步:对话历史、工具调用、MCP 接入、TODO 锚点、SubAgent 委派、Skills 按需加载。其中第 2 步和第 3 步是核心,也是接入配置最容易出错的地方。下面每一步都给可复制的配置骨架和验证动作,不堆概念。

2. TaoToken 前置:统一 Key 与通道准备

在写 Agent 之前,先把模型通道准备好。TaoToken 在这里的角色是统一 Key 和 API 通道:你不需要在代码里分别维护多个模型供应商的地址和密钥,Agent 的 LLM 客户端只认一个 base_url 和一个 key,后面换模型、加模型都在这一层解决。

官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台创建 API Key。API 地址是 https://taotoken.net/api ,注意这个地址不带任何查询参数,直接作为 OpenAI 兼容协议的 base_url 使用。

这里有个容易踩的坑:很多人把 base_url 写成带/v1或者带一堆参数的完整地址,结果 SDK 拼接路径时出现双斜杠或者路径错位。正确做法是 base_url 只写到域名加/api,具体路径交给 SDK 处理。如果你用的是 OpenAI 官方 SDK,它会自动补/chat/completions。

Key 的权限建议单独建一个,只给对话和工具调用需要的模型权限,不要用主账号的万能 Key。Agent 在调试阶段会频繁发请求,单独 Key 方便你随时吊销和轮换。

拿到 Key 之后,先别急着写 Agent,用一条 curl 验证通道是否通:

curl https://taotoken.net/api/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "只回复两个字:通了"}], "stream": false }'

返回里有choices[0].message.content就说明通道没问题。这一步别跳过,后面 Agent 报错时你能快速判断是通道问题还是代码问题。

3. 可复制配置:settings.json 与 config.toml 骨架

Agent 的配置分两块:一块是模型通道配置,一块是 MCP Server 配置。我习惯把模型通道放在settings.json,MCP 放在config.toml,两者分开管理,改一个不影响另一个。

3.1 settings.json:模型通道与 ReAct 参数

{ "llm": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "model": "claude-sonnet-4-20250514", "max_tokens": 4096, "temperature": 0.2, "stream": true }, "agent": { "max_iterations": 12, "tool_timeout_seconds": 30, "enable_todo": true, "enable_subagent": true, "enable_skills": true }, "skills_dir": "./skills", "mcp_config": "./config.toml" }

max_iterations是 ReAct 循环的硬上限,防止模型在工具调用里绕圈。我一开始设成 50,结果有一次模型反复读同一个文件,烧了不少 token。12 到 15 是比较稳的范围。temperature设 0.2,Agent 需要的是稳定决策,不是创意写作。

3.2 config.toml:MCP Server 骨架

[mcp_servers.filesystem] command = "npx" args = ["-y", "@modelcontextprotocol/server-filesystem", "./workspace"] transport = "stdio" [mcp_servers.fetch] command = "npx" args = ["-y", "@modelcontextprotocol/server-fetch"] transport = "stdio" env = { HTTP_PROXY = "" } [mcp_servers.custom_tools] url = "http://127.0.0.1:8765/mcp" transport = "http"

MCP 的 transport 有两种常见形态:本地 Server 走 stdio,远程 Server 走 http。stdio 模式下 Agent 启动时会拉起子进程,通过标准输入输出通信;http 模式下直接请求远端地址。filesystem这个 Server 给 Agent 提供读写文件能力,fetch提供网络请求能力,这两个是最小可用组合。

注意env里不要塞任何敏感信息,MCP Server 的密钥应该通过环境变量注入,不要写死在 toml 里。如果你在本地调试,custom_tools那个 http 地址可以指向你自己写的 MCP Server,用来验证工具注册链路。

3.3 工具注册表结构

Agent 启动后,需要把内置工具和 MCP 工具合并成一张扁平表。结构大概是这样:

TOOL_REGISTRY = {} def register_tool(name, schema, handler, source="builtin"): TOOL_REGISTRY[name] = { "schema": schema, "handler": handler, "source": source, } def load_mcp_tools(mcp_client): for tool in mcp_client.list_tools(): register_tool( name=tool["name"], schema=tool["inputSchema"], handler=lambda args, t=tool: mcp_client.call_tool(t["name"], args), source="mcp", )

模型看到的只有name和schema,它不关心工具来自内置还是 MCP。这个抽象层很关键,后面加工具不用改 ReAct 循环。

4. ReAct 循环与验证请求

配置就绪后,核心就是那个循环。ReAct 的本质是:模型输出工具调用 → 执行工具 → 把结果塞回对话历史 → 再让模型推理。循环终止条件是模型不再请求工具,直接给出文本回复。

4.1 最小 ReAct 循环实现

import json from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key=os.environ["TAOTOKEN_API_KEY"], ) def agent_loop(user_message, history, tools): history.append({"role": "user", "content": user_message}) for step in range(MAX_ITERATIONS): response = client.chat.completions.create( model=MODEL, messages=history, tools=tools, tool_choice="auto", ) msg = response.choices[0].message if not msg.tool_calls: history.append({"role": "assistant", "content": msg.content}) return msg.content history.append(msg) for call in msg.tool_calls: fn_name = call.function.name fn_args = json.loads(call.function.arguments) result = TOOL_REGISTRY[fn_name]["handler"](fn_args) history.append({ "role": "tool", "tool_call_id": call.id, "content": str(result), }) return "达到最大迭代次数,任务未完成"

这段代码里有两个细节值得说。第一,history.append(msg)必须把 assistant 的 tool_calls 消息原样存进去,否则下一轮模型看不到自己请求过什么工具。第二,tool 消息必须带tool_call_id,这是 OpenAI 协议的要求,缺了会报 400。

4.2 验证一次真实工具调用

准备一个最简单的工具,比如读文件:

def read_file(args): with open(args["path"], "r", encoding="utf-8") as f: return f.read()[:2000] register_tool( name="read_file", schema={ "type": "function", "function": { "name": "read_file", "description": "读取指定路径的文件内容", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "文件路径"} }, "required": ["path"], }, }, }, handler=read_file, )

然后跑:

history = [{"role": "system", "content": "你是一个会使用工具的助手。"}] result = agent_loop("读一下 ./README.md 的前几行,告诉我这个项目是做什么的", history, list_tool_schemas()) print(result)

如果 Agent 先输出一个read_file的工具调用,拿到内容后再给出总结,说明 ReAct 循环通了。这一步是整个 Agent 的“第一次呼吸”,跑通之后后面都是在这个骨架上加东西。

4.3 MCP 工具接入验证

MCP 工具接入后,验证方式和内置工具一样,只是工具来源不同。启动时扫描config.toml,连接所有 Server,拉取工具列表注册进TOOL_REGISTRY。你可以让 Agent 执行一个需要 MCP 工具的任务,比如“列出 workspace 目录下的所有文件”,观察它是否调用了 filesystem Server 暴露的工具。

如果工具没被调用,先检查TOOL_REGISTRY里有没有 MCP 工具,再检查 schema 格式是否和内置工具一致。MCP 返回的inputSchema有时候字段名和 OpenAI 要求的不完全对齐,需要做一层转换。

5. 本篇常见错排查

接入阶段报错集中在几个地方,我按出现频率排一下。

401 或 403:Key 没读到或者权限不对。先确认环境变量TAOTOKEN_API_KEY在当前 shell 里能echo出来,再确认 Key 没有多余空格。如果 Key 是从文件读的,注意换行符。

404 或路径错误:base_url 写错了。正确写法是https://taotoken.net/api,不要加/v1,不要加尾部斜杠。SDK 会自己拼/chat/completions。

400 tool_call_id 缺失:tool 消息没带tool_call_id,或者 assistant 的 tool_calls 消息没存进历史。检查history.append(msg)那行有没有执行。

模型不调用工具:schema 的description写得太模糊,或者tool_choice设成了none。把工具描述写清楚“什么时候用”,tool_choice保持auto。

MCP Server 启动失败:stdio 模式下command找不到,通常是npx不在 PATH 里。用绝对路径,或者先手动跑一遍npx -y @modelcontextprotocol/server-filesystem ./workspace确认能启动。

循环停不下来:max_iterations设太大,或者工具返回结果太长把上下文撑爆。给工具结果加截断,比如只返回前 2000 字符。

流式输出和工具调用冲突:流式模式下 tool_calls 是分片到达的,需要自己拼接。调试阶段建议先关流式,跑通非流式再开。

6. 后续扩展与接入入口

最小 Agent 跑通之后,第 4 到第 6 步是自然延伸。TODO 管理本质上是给 Agent 一个外部状态锚点,把模型脑子里的计划变成可更新的列表,防止多步任务中途跑偏。SubAgent 是把复杂任务拆成独立上下文单元,主 Agent 只负责编排,子 Agent 各自带着干净的历史执行。Skills 则是把领域知识从系统提示词里拆出来,按需加载,平时不占上下文。

这三块都不需要改 ReAct 循环,它们只是往TOOL_REGISTRY里加新工具,然后在系统提示词里告诉模型什么时候用。这也是这套骨架的好处:核心循环稳定,能力通过工具扩展。

如果你在接入阶段卡住,优先看 API Keys 和接入文档,把通道和 Key 的问题先排掉:

  • API Keys 管理:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

想先验证模型通道和工具调用格式,可以直接在模型对话里试一轮带 tools 参数的请求:

  • 模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

如果你打算把 Agent 长期跑在编码或自动化任务上,Coding Plan 那条通道更适合持续调用:

  • Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

我自己的习惯是:调试阶段用模型对话快速验证 schema 和返回格式,跑通之后再切到 Agent 代码里。这样能把“协议问题”和“代码问题”分开,排查起来快很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 4:03:25

LVGL lv_menu实战:构建动态层级菜单与交互界面

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 4:03:00

Java+MySQL病房管理系统数据库课设:从建表到实验报告避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华