
1. 为什么概念都懂代码却跑不起来刚接触 AI Agent 开发的人几乎都会经历同一个阶段刷完一堆科普文章Token、Context、RAG、CoT、MCP、ReAct 这些词单独拿出来都能说两句可一旦打开编辑器准备写第一个 Agent就卡住了——不知道该先调哪个接口不知道 Base URL 填什么不知道工具调用返回的 JSON 怎么接回循环里。问题不在概念本身而在于这些概念没有被串成一条能跑的链路。LLM 是纯函数RAG 是给函数外挂知识CoT 是让函数先想再答MCP 是给函数接上手脚Agent 是给函数套上循环——这些说法都对但落到代码上它们对应的是不同的 API 调用、不同的配置项、不同的返回结构。你需要的不是再读一遍定义而是一套能复制粘贴、跑通、看到日志的最小工程骨架。这篇就按这条思路来先把环境配好用统一的 Key 和 Base URL 打通模型调用然后从单轮 LLM 调用开始一步步加上结构化输出、工具调用、ReAct 循环最后接上 MCP 工具。每一步都有可复制的配置和可验证的结果跑完你手里就有一个能观测、能扩展的 Agent 雏形。适合刚入门、想把概念映射到代码的工程师也适合已经会调 API 但没搭过完整 Agent 循环的人。核心检索词先钉一下AI Agent 入门、LLM 调用、RAG、CoT、MCP、ReAct、TaoToken 统一 Key。下面所有配置都围绕这几个词展开。2. TaoToken 前置一个 Key 打通 30 概念的调用层2.1 为什么 Agent 入门需要一个统一入口Agent 开发最烦的不是写循环是模型和工具的接入层太碎。今天试 Claude明天试 GPT后天想对比 Gemini每换一个模型就要改 Base URL、改 Key、改 SDK 参数、改返回解析。概念还没串起来光配置就耗掉一半耐心。TaoToken 在这里的角色是统一调用层一个 API Key一个 Base URL兼容主流模型的调用格式。你不需要为每个模型单独申请、单独配环境变量切换模型只改一个 model 字段。对入门阶段来说这能让你把精力放在 Agent 循环本身而不是接入细节上。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数配置时别把查询串带进去。2.2 环境变量与 Base URL 的标准写法先把环境变量配好后面所有代码都读这套变量换机器、换项目都不用改代码。# ~/.bashrc 或 ~/.zshrcWindows 用系统环境变量 export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODELclaude-sonnet-4-20250514如果你用 Python装官方 SDK 就行OpenAI 兼容格式可以直接复用pip install openai python-dotenv然后在项目根目录放一个.envTAOTOKEN_API_KEYsk-你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELclaude-sonnet-4-20250514注意Base URL 结尾不要带/v1SDK 会自己拼路径。带了会变成/v1/v1/chat/completions直接 404。2.3 拿 Key 与验证连通性Key 在控制台的 API Keys 页面创建地址是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。创建后先别急着写 Agent用一段最小代码验证连通import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() client OpenAI( api_keyos.getenv(TAOTOKEN_API_KEY), base_urlos.getenv(TAOTOKEN_BASE_URL), ) resp client.chat.completions.create( modelos.getenv(TAOTOKEN_MODEL), messages[{role: user, content: 只回复两个字通了}], ) print(resp.choices[0].message.content)跑出来打印「通了」说明 Key、Base URL、模型 ID 三件套都对。这一步是整个 Agent 的地基地基不稳后面全是玄学报错。2.4 模型 ID 怎么选入门阶段不用纠结先固定一个能稳定返回结构化输出的模型。Claude 系列在工具调用和长上下文上表现稳GPT 系列生态文档多Gemini 系列上下文窗口大。你可以在模型对话页面先手动试几轮确认模型 ID 拼写正确再写进环境变量。模型对话入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你打算长期跑编码类 Agent比如后面要接 Claude Code 或 Cline建议直接看 Coding Plan额度模型和调用方式都更省心 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。3. 可复制配置从单轮调用到带工具的 Agent3.1 单轮 LLM 调用先理解纯函数所有 Agent 的地基都是这一句LLM 是接收文字、输出文字的函数。先写最朴素的调用把 messages 结构搞清楚。def llm_call(messages, modelNone): resp client.chat.completions.create( modelmodel or os.getenv(TAOTOKEN_MODEL), messagesmessages, temperature0.2, ) return resp.choices[0].message.content messages [ {role: system, content: 你是一个简洁的助手回答不超过三句话。}, {role: user, content: 用一句话解释什么是 Token。}, ] print(llm_call(messages))这里 system 和 user 的分层就是 Prompt 工程的第一课system 放不变的规则user 放这一轮的任务。后面讲 Prompt Injection 时你会看到这两层混在一起就是安全灾难的起点。3.2 结构化输出让 LLM 返回 JSON 而不是散文Agent 循环里每一步的状态传递都依赖结构化输出。别让模型返回自由文本再自己正则解析直接用 JSON schema 约束。import json def llm_json(messages, schema_hint): sys {role: system, content: f你必须只输出 JSON格式{schema_hint}不要任何解释文字。} resp client.chat.completions.create( modelos.getenv(TAOTOKEN_MODEL), messages[sys] messages, temperature0, ) raw resp.choices[0].message.content.strip() raw raw.removeprefix(json).removeprefix().removesuffix().strip() return json.loads(raw) result llm_json( [{role: user, content: 北京今天天气如何如果你不知道就返回 unknown。}], {city: 城市名, weather: 天气或unknown} ) print(result)实测下来temperature 设 0 能显著降低模型加解释文字的概率。如果还是偶尔带前缀加一层removeprefix兜底就够了。3.3 工具调用给纯函数接上手脚Tool UseAnthropic 叫法和 Function CallingOpenAI 叫法是同一件事让模型返回一段结构化调用指令你的程序执行函数再把结果丢回去。tools [ { type: function, function: { name: get_weather, description: 查询指定城市的实时天气。当用户询问天气时调用。参数 city 为中文城市名。, parameters: { type: object, properties: {city: {type: string, description: 城市名如 北京}}, required: [city], }, }, } ] def get_weather(city: str) - str: fake_db {北京: 晴25 度, 上海: 多云23 度} return fake_db.get(city, unknown) resp client.chat.completions.create( modelos.getenv(TAOTOKEN_MODEL), messages[{role: user, content: 北京天气怎么样}], toolstools, tool_choiceauto, ) msg resp.choices[0].message if msg.tool_calls: call msg.tool_calls[0] args json.loads(call.function.arguments) print(模型想调用, call.function.name, args) print(执行结果, get_weather(**args))注意工具 description 的写法——模型靠它选工具。写清楚「干什么、参数是什么、什么时候调用」比任何代码层兜底都管用。这是新手最容易忽略、却最影响成功率的一步。3.4 ReAct 循环把工具调用串成 Agent单次工具调用还不是 Agent加上循环才是。ReAct 的核心就是「想→做→看」重复到目标达成。def run_agent(user_input, max_steps5): messages [ {role: system, content: 你可以调用工具。需要信息时先调用工具拿到结果后再回答。}, {role: user, content: user_input}, ] for step in range(max_steps): resp client.chat.completions.create( modelos.getenv(TAOTOKEN_MODEL), messagesmessages, toolstools, tool_choiceauto, ) msg resp.choices[0].message messages.append(msg) if not msg.tool_calls: print(f[step {step}] 最终回答{msg.content}) return msg.content for call in msg.tool_calls: args json.loads(call.function.arguments) result get_weather(**args) print(f[step {step}] 调用 {call.function.name}({args}) - {result}) messages.append({ role: tool, tool_call_id: call.id, content: result, }) return 达到最大步数未完成 run_agent(北京和上海哪个更热)跑起来你会看到日志里 step 0 调一次北京、step 1 调一次上海、step 2 给出对比结论。这就是一个可观测的最小 Agent 流程。把get_weather换成 RAG 检索函数就是带知识库的 Agent换成 MCP 工具就是接上外部能力的 Agent。3.5 接上 MCP把工具层标准化MCP 是 Anthropic 推出的开放协议可以理解成「LLM 的 USB 接口」——统一了外部工具怎么接进来。入门阶段先用 stdio 模式跑一个本地 MCP Server配置写进客户端即可。以 Cline 为例MCP 配置放在cline_mcp_settings.json{ mcpServers: { weather: { command: python, args: [/path/to/weather_server.py], env: { TAOTOKEN_API_KEY: sk-你的key, TAOTOKEN_BASE_URL: https://taotoken.net/api } } } }如果你用 Claude Code配置走~/.claude/settings.json或项目级.mcp.jsonBase URL、Key、Model ID 三件套同样要写全{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }Codex 用户则改~/.codex/auth.json把 base_url 和 api_key 指向同一套。三件套缺一个都会报认证或模型找不到的错。4. 验证请求看到日志才算跑通4.1 最小验证清单跑完上面代码按这个清单逐项确认验证项预期结果失败信号单轮调用打印「通了」401 / 404JSON 输出返回 dict 无异常json.JSONDecodeError工具调用打印模型想调用的函数名tool_calls 为空ReAct 循环日志出现多步调用一步就结束MCP 接入客户端工具列表出现 weather工具不显示4.2 观测日志怎么打Agent 最难调的不是模型是中间过程不透明。建议在循环里固定打三类日志每步的 messages 长度、工具调用名与参数、工具返回内容。这样出 bug 时能回放而不是对着一个错误回答瞎猜。import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) def log_step(step, msg): logging.info(fstep{step} content{msg.content!r} tool_calls{len(msg.tool_calls or [])})把log_step塞进循环跑几次你就能看出模型在哪一步开始跑偏。这是从玩具 Agent 走向可用 Agent 的分水岭。4.3 成功结果长什么样一个健康的 ReAct 日志应该像这样step0 调用 get_weather({city: 北京}) - 晴25 度 step1 调用 get_weather({city: 上海}) - 多云23 度 step2 最终回答北京 25 度上海 23 度北京更热。如果 step 数异常多、或者反复调同一个工具说明工具 description 写得不够清楚或者 system prompt 没约束好终止条件。5. 本篇常见错排查5.1 401 Unauthorized最常见的原因是 Key 没读到。检查.env是否被load_dotenv()加载、环境变量名是否拼错、Key 是否带了多余空格。还有一种情况是 Base URL 写成了官网首页而不是 API 地址请求打到了错误路径。5.2 local proxy failed / connection error这类报错通常是网络层问题不是 Key 问题。先确认 Base URL 是https://taotoken.net/api再确认本机没有残留的代理环境变量干扰。如果你在 CI 或容器里跑检查容器是否能正常出网。5.3 reading choices of undefined这个报错说明返回体结构和你预期的不一样通常是请求根本没成功返回的是错误对象而不是 completion。打印完整resp或resp.model_dump()看真实返回八成能看到 401 或 404 的原始信息。5.4 OAuth / 认证方式不匹配Claude Code、Codex 这类工具默认走 OAuth 登录如果你要改成 API Key 模式必须显式配置 Base URL Key Model ID 三件套否则它会继续走 OAuth 流程然后失败。CC Switch 切换配置时也要确认这三项都写全了。5.5 工具调用返回空tool_calls为空通常是两个原因一是tool_choice没设成auto二是工具 description 太模糊模型判断不需要调用。把 description 改成「当用户询问 X 时调用」这种明确触发条件命中率会明显提升。5.6 JSON 解析失败模型偶尔会在 JSON 外面包 markdown 代码块或加一句「好的这是结果」。除了removeprefix兜底更稳的做法是在 system prompt 里强调「只输出 JSON不要任何其他文字」并把 temperature 设 0。6. 下一步把概念一个个接进这条链路跑通上面这套之后30 概念就不再是散落的词而是这条链路上的可插拔模块。RAG 是替换工具函数里的检索逻辑CoT 是在 system prompt 里加推理引导Memory 是在 messages 外面加持久化存储Eval 是给循环加一组测试用例Observability 就是你已经打上的日志。想继续深入可以按这个顺序推进先用模型对话页面手动试不同模型的输出差异确认模型 ID 和调用格式再把工具函数换成真实的 RAG 检索体会 Context Engineering 的分量然后接 MCP Server把工具层标准化最后加上 Eval 和日志回放让它从「能跑」变成「能上线」。接入文档和更多配置示例在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Key 管理在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你打算长期跑编码类 AgentCoding Plan 会比按量调用更省心 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个我踩过的坑别一上来就上向量数据库和多 Agent。先用两三个工具函数把 ReAct 循环跑顺把日志打清楚把工具 description 磨到位。等这条最小链路稳定了再往上加 RAG、加 Memory、加多 Agent 协作每一步都有可回放的日志兜底才不会在概念堆里迷路。