
1. 为什么你的 Agent 项目总在第三周卡住一个真实的学习路径问题AI Agent 开发最容易踩的坑不是某个 API 不会调而是学到一半发现前面搭的东西后面用不上。我见过太多人第一周兴致勃勃读完 ReAct 论文第二周开始写 Function Calling第三周卡在“工具描述到底怎么写才不会被模型忽略”这种细节上然后就没有然后了。这个问题的根源在于学习路径是线性的但 Agent 系统是网状的。LLM、Function Calling、MCP、ReAct 这四个东西不是四个独立章节而是互相咬合的齿轮。你单独学 Function Calling不知道 MCP 的存在就会把每个工具都硬编码进项目你单独学 ReAct不理解 Function Calling 的结构化输出机制写出来的循环就是一堆 if-else 堆砌。所以这篇内容的核心不是给你一份“第 1 周学什么、第 2 周学什么”的清单而是给你一条能跑通、能验证、能自查产出的 14 周路径。每一周结束时你都有一个可以运行的东西而不是一堆笔记。另外还有一个现实问题多模型调用。你在第 1 周可能用 Claude 做推理第 4 周想换成 GPT 做工具路由第 8 周又要接一个国产模型做成本优化。如果每次换模型都要改一遍 Base URL、改一遍 Key 管理、改一遍 SDK 初始化你的学习节奏会被这些琐事打断。这也是为什么我在路径里把 TaoToken 的统一 Key 和 API 通道放在前置位置——它不是学习内容本身而是让你少折腾基础设施的工具。这篇适合谁有 Python 基础、调过至少一次 LLM API、想系统走完 Agent 从理论到落地的人。不适合完全没写过代码的纯小白也不适合已经做过三个 Agent 项目想找高级技巧的老手。接下来按 14 周推进每个阶段都有可复制的配置片段和验证请求。你可以按周跟做也可以按阶段跳着看。2. TaoToken 统一 Key 与 API 通道多模型 Agent 开发的前置配置在开始 14 周路径之前先把基础设施搭好。这一步不做后面每周换模型都会浪费你半小时。TaoToken 的核心作用是用一个 Key、一个 Base URL 管理多个模型的调用。对于 Agent 开发来说这意味着你在第 1 周用 Claude 写 ReAct 循环第 4 周换成 GPT 做工具路由第 8 周加一个国产模型做成本优化都不需要改代码里的认证逻辑只需要改一个 model 参数。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 注意 API 地址不加 UTM 参数。2.1 环境变量配置不管你用什么语言第一步都是把 Key 和 Base URL 放进环境变量。不要硬编码在代码里这是 Agent 项目的基本纪律。Linux/macOS 下编辑~/.zshrc或~/.bashrcexport TAOTOKEN_API_KEYsk-your-key-here export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell$env:TAOTOKEN_API_KEYsk-your-key-here $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用.env文件管理推荐配合 python-dotenv# .env TAOTOKEN_API_KEYsk-your-key-here TAOTOKEN_BASE_URLhttps://taotoken.net/api2.2 Python SDK 初始化Anthropic SDK 的初始化方式import os from anthropic import Anthropic client Anthropic( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL] ) response client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, messages[{role: user, content: 用一句话解释什么是 ReAct}] ) print(response.content[0].text)OpenAI SDK 的初始化方式import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL] ) response client.chat.completions.create( modelgpt-4.1, messages[{role: user, content: 用一句话解释什么是 Function Calling}] ) print(response.choices[0].message.content)注意两个 SDK 的 base_url 是同一个Key 也是同一个。这就是统一通道的价值——你不需要为每个模型厂商维护一套认证配置。2.3 模型 ID 对照在 Agent 开发的不同阶段你会用到不同模型。以下是常用模型 ID 的对照方便你在代码里切换用途模型 ID特点复杂推理 / ReAct 循环claude-sonnet-4-20250514工具调用稳定指令遵循好高难度自主决策claude-opus-4-20250514最强推理成本高简单工具路由claude-haiku-3-5-20241022快、便宜适合分类任务通用工具调用gpt-4.1Function Calling 成熟低成本批量任务gpt-4.1-mini性价比高在 Agent 项目里我建议你从第 1 周就用claude-sonnet-4-20250514做主力因为它的工具调用格式最清晰调试起来最省心。等到第 8 周做成本优化时再把简单步骤路由到 Haiku 或 mini 模型。2.4 验证配置是否生效在进入第 1 周之前先跑一个最小验证import os from anthropic import Anthropic client Anthropic( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL] ) # 验证基础对话 resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens100, messages[{role: user, content: 回复 OK 两个字母}] ) print(基础对话:, resp.content[0].text) # 验证 Function Calling tools [{ name: get_weather, description: 查询指定城市的天气。当用户询问天气时使用。, input_schema: { type: object, properties: { city: {type: string, description: 城市名称} }, required: [city] } }] resp2 client.messages.create( modelclaude-sonnet-4-20250514, max_tokens200, toolstools, messages[{role: user, content: 北京今天天气怎么样}] ) for block in resp2.content: if block.type tool_use: print(工具调用:, block.name, block.input)如果两段都正常输出说明你的统一 Key 通道已经打通可以开始 14 周路径了。如果报 401检查 Key 是否正确如果报连接错误检查 Base URL 是否写成了https://taotoken.net/api不要加尾部斜杠。3. 第 1-3 周ReAct 循环与 Function Calling 的可复制配置这个阶段的目标是不依赖任何框架用纯 Python 手写一个能调用工具的 ReAct Agent。为什么不用框架因为框架会帮你隐藏太多细节而你需要先理解这些细节后面用 LangGraph 时才知道它在帮你做什么。3.1 第 1 周Function Calling 的结构化输出Function Calling 的本质是LLM 不直接执行操作而是输出一个结构化的 JSON告诉你“我想调用哪个工具、传什么参数”。你的代码负责解析这个 JSON、执行工具、把结果回传。先定义一个工具tools [ { name: search_docs, description: 搜索内部技术文档返回与查询最相关的片段。当用户询问 API 用法、配置参数、错误码时使用。不要用于搜索外部互联网信息。, input_schema: { type: object, properties: { query: { type: string, description: 搜索关键词如 Function Calling 参数格式 }, max_results: { type: integer, description: 最大返回数量默认 3, default: 3 } }, required: [query] } } ]注意工具描述的三个要素做什么搜索内部技术文档、什么时候用询问 API 用法时、什么时候不用不要搜外部信息。这三句话直接决定模型会不会在错误的场景调用这个工具。调用并解析import json def run_tool_call(user_message): resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, toolstools, messages[{role: user, content: user_message}] ) tool_results [] for block in resp.content: if block.type tool_use: # 这里执行真实工具示例用假数据 result {docs: [f关于 {block.input[query]} 的文档片段]} tool_results.append({ type: tool_result, tool_use_id: block.id, content: json.dumps(result, ensure_asciiFalse) }) if tool_results: # 把工具结果回传给模型 final client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, toolstools, messages[ {role: user, content: user_message}, {role: assistant, content: resp.content}, {role: user, content: tool_results} ] ) return final.content[0].text return resp.content[0].text print(run_tool_call(Function Calling 的参数格式是什么))第 1 周的产出一个能根据用户问题自动决定是否调用工具、并基于工具结果生成回答的脚本。3.2 第 2 周手写 ReAct 循环ReAct 的核心是 Thought → Action → Observation 的循环。用 Function Calling 实现时Thought 是模型的推理文本Action 是 tool_use 块Observation 是 tool_result。def react_agent(user_message, max_steps5): messages [{role: user, content: user_message}] for step in range(max_steps): resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens2048, toolstools, messagesmessages ) # 收集本轮的工具调用 tool_uses [b for b in resp.content if b.type tool_use] if not tool_uses: # 没有工具调用说明模型认为可以给出最终答案 text_blocks [b.text for b in resp.content if b.type text] return \n.join(text_blocks) # 把 assistant 的回复加入历史 messages.append({role: assistant, content: resp.content}) # 执行工具并构造结果 results [] for tu in tool_uses: output execute_tool(tu.name, tu.input) results.append({ type: tool_result, tool_use_id: tu.id, content: json.dumps(output, ensure_asciiFalse) }) messages.append({role: user, content: results}) return 达到最大步数限制任务未完成 def execute_tool(name, params): if name search_docs: return {docs: [f搜索结果: {params[query]}]} return {error: f未知工具: {name}}这个循环的关键点max_steps必须设置否则模型可能陷入无限调用。第 2 周的产出是一个能多步推理、连续调用工具的 Agent。3.3 第 3 周加入反思与错误处理第 3 周给 ReAct 循环加上两个东西工具执行失败时的重试、以及模型输出格式错误时的纠正。def execute_tool_with_retry(name, params, max_retries2): for attempt in range(max_retries): try: result execute_tool(name, params) if error not in result: return result except Exception as e: if attempt max_retries - 1: return {error: str(e)} return {error: 重试次数耗尽}同时在 System Prompt 里加入反思指令SYSTEM_PROMPT 你是一个技术文档助手。 工作流程 1. 收到问题后先判断是否需要搜索文档 2. 如果需要调用 search_docs 工具 3. 分析搜索结果如果信息不足可以再次搜索 4. 如果工具返回错误尝试换一个查询词重试 5. 最多搜索 3 次然后基于已有信息给出回答 安全规则 - 不要编造文档中不存在的内容 - 如果搜索无结果明确告知用户 第 3 周的产出一个带错误恢复和反思能力的 ReAct Agent能处理工具失败、查询无结果等异常情况。这个阶段结束时你应该有一个不依赖框架、约 150 行代码的 Agent能完成“搜索文档 → 分析结果 → 生成回答”的完整流程。这个 Agent 是你后面所有工作的基线。4. 第 4-7 周MCP 工具链接入与验证请求前 3 周你的工具是硬编码在 Python 里的。第 4 周开始把工具改成 MCP Server这样你的 Agent 可以复用社区已有的工具生态也可以把自己的工具暴露给其他 Agent 使用。4.1 MCP 的核心概念MCP 把工具提供者抽象成 ServerAgent 作为 Client 连接 Server。Server 暴露三类能力Tools可调用的函数、Resources可读取的数据、Prompts预定义模板。对 Agent 开发来说最常用的是 Tools。一个最小的 MCP ServerPython SDKfrom mcp.server import Server from mcp.types import Tool, TextContent import json server Server(agent-tools) server.tool(query_user_db) async def query_user_db(user_id: str) - list[TextContent]: 查询用户数据库返回用户的基本信息。 Args: user_id: 用户 ID格式为 u_ 开头的字符串 # 实际项目中这里连接数据库 data {user_id: user_id, name: 测试用户, plan: pro} return [TextContent(typetext, textjson.dumps(data, ensure_asciiFalse))] server.tool(create_ticket) async def create_ticket(title: str, priority: str normal) - list[TextContent]: 创建一个工单。 Args: title: 工单标题 priority: 优先级可选 low/normal/high默认 normal ticket_id T-12345 return [TextContent(typetext, textf工单已创建: {ticket_id})]4.2 把 MCP Server 接入 Agent在 Agent 代码里你需要一个 MCP Client 来连接 Server 并获取工具列表from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def get_mcp_tools(server_script: str): server_params StdioServerParameters( commandpython, args[server_script] ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools_resp await session.list_tools() # 转换成 Anthropic API 的工具格式 anthropic_tools [] for tool in tools_resp.tools: anthropic_tools.append({ name: tool.name, description: tool.description, input_schema: tool.inputSchema }) return anthropic_tools4.3 验证请求一次完整的 MCP 工具链调用下面是一个完整的验证脚本测试 MCP Server 的工具能否被 Agent 正确调用import asyncio import json from anthropic import Anthropic from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client client Anthropic( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL] ) async def verify_mcp_chain(): server_params StdioServerParameters( commandpython, args[agent_tools_server.py] ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() # 1. 获取工具列表 tools_resp await session.list_tools() print(f发现 {len(tools_resp.tools)} 个工具:) for t in tools_resp.tools: print(f - {t.name}: {t.description[:50]}...) # 2. 构造 Anthropic 工具格式 anthropic_tools [{ name: t.name, description: t.description, input_schema: t.inputSchema } for t in tools_resp.tools] # 3. 发起请求触发工具调用 resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, toolsanthropic_tools, messages[{ role: user, content: 帮我查一下用户 u_1001 的信息然后给他创建一个高优先级工单 }] ) # 4. 执行工具调用 for block in resp.content: if block.type tool_use: print(f\n调用工具: {block.name}) print(f参数: {json.dumps(block.input, ensure_asciiFalse)}) result await session.call_tool(block.name, block.input) print(f结果: {result.content[0].text}) asyncio.run(verify_mcp_chain())预期输出发现 2 个工具: - query_user_db: 查询用户数据库... - create_ticket: 创建一个工单... 调用工具: query_user_db 参数: {user_id: u_1001} 结果: {user_id: u_1001, name: 测试用户, plan: pro} 调用工具: create_ticket 参数: {title: 用户 u_1001 的工单, priority: high} 结果: 工单已创建: T-12345如果这个脚本能跑通说明你的 MCP 工具链已经打通。第 4-7 周的产出是一个基于 MCP 的工具系统Agent 可以通过标准协议调用外部工具而不是硬编码。4.4 第 5-7 周的推进方向第 5 周把 MCP Server 部署成独立进程支持多个 Agent 同时连接。测试并发调用时的稳定性。第 6 周接入社区已有的 MCP Server比如文件系统、数据库、GitHub 相关的 Server。验证你的 Agent 能否正确选择不同 Server 的工具。第 7 周设计 Skill 层。把相关的工具组织成一个 Skill比如“用户管理 Skill”包含 query_user_db 和 create_ticket“文档管理 Skill”包含 search_docs 和 update_doc。在 System Prompt 里按 Skill 组织工具说明降低模型的选择难度。5. 第 8-14 周常见报错排查与阶段自查这个阶段你会遇到最多的问题。下面按真实报错整理排查路径。5.1 401 认证失败报错信息anthropic.AuthenticationError: Error code: 401 - {error: {message: Invalid API key}}排查步骤第一检查环境变量是否真的被读取。在 Python 里打印import os print(Key 前 8 位:, os.environ.get(TAOTOKEN_API_KEY, 未设置)[:8]) print(Base URL:, os.environ.get(TAOTOKEN_BASE_URL, 未设置))如果输出“未设置”说明.env文件没有被加载。如果你用 python-dotenv需要在代码开头加from dotenv import load_dotenv load_dotenv()第二检查 Key 是否有多余空格。从网页复制 Key 时经常带上换行符或空格。用.strip()处理api_key os.environ[TAOTOKEN_API_KEY].strip()第三检查 Base URL 是否写错。正确格式是https://taotoken.net/api不要加尾部斜杠不要写成/v1。5.2 local proxy failed / 连接超时报错信息httpx.ConnectError: [Errno 111] Connection refused或者anthropic.APIConnectionError: Connection error.排查步骤第一确认 Base URL 没有写成localhost或127.0.0.1。如果你之前配过本地代理检查环境变量里有没有HTTP_PROXY或HTTPS_PROXY残留import os print(HTTP_PROXY:, os.environ.get(HTTP_PROXY)) print(HTTPS_PROXY:, os.environ.get(HTTPS_PROXY))如果有值在代码里临时清除os.environ.pop(HTTP_PROXY, None) os.environ.pop(HTTPS_PROXY, None)第二用 curl 直接测试连通性curl -X POST https://taotoken.net/api/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d {model:claude-sonnet-4-20250514,max_tokens:50,messages:[{role:user,content:hi}]}如果 curl 能通但 Python 不通说明是 SDK 配置问题如果 curl 也不通检查网络环境。5.3 reading choices 报错报错信息TypeError: Cannot read properties of undefined (reading choices)或者 Python 里KeyError: choices这个报错通常出现在你用 OpenAI SDK 的格式去解析 Anthropic 的响应或者反过来。两个 SDK 的响应结构不同Anthropic 的响应结构resp.content[0].text # 文本在 content 数组里OpenAI 的响应结构resp.choices[0].message.content # 文本在 choices 数组里排查方法打印完整响应看结构import json print(json.dumps(resp.model_dump(), ensure_asciiFalse, indent2))如果你在 Agent 循环里混用了两个 SDK建议统一用一个。Anthropic SDK 的 tool_use 块结构更清晰适合 Agent 开发。5.4 OAuth / 权限相关报错报错信息anthropic.PermissionDeniedError: Error code: 403或者{error: {type: forbidden, message: Model not available}}排查步骤第一确认你的 Key 有权限调用目标模型。有些 Key 可能只开通了部分模型。用模型对话页面测试一下目标模型是否可用。第二检查模型 ID 是否拼写正确。比如claude-sonnet-4-20250514不要写成claude-sonnet-4或claude-4-sonnet。第三如果你在 Claude Code 或 Cline 里配置检查配置文件格式。以 Claude Code 的 settings.json 为例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-your-key-here, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }三件套必须完整Base URL、Key、Model ID。缺任何一个都会报错。5.5 工具调用不触发现象模型回复了文本但没有调用你定义的工具。排查步骤第一检查工具描述是否清晰。如果描述太模糊模型不知道什么时候该用。对比差的描述搜索文档好的描述搜索内部技术文档返回与查询最相关的片段。当用户询问 API 用法、配置参数、错误码时使用。不要用于搜索外部互联网信息。第二检查工具数量。如果一次给模型 30 个工具它可能选择困难。第 7 周的 Skill 分层就是解决这个问题。第三在 System Prompt 里明确工具使用流程工作流程 1. 收到问题后先判断是否需要搜索文档 2. 如果需要调用 search_docs 工具 3. 基于搜索结果给出回答5.6 阶段自查清单每两周做一次自查确认产出物第 3 周末能跑通一个不依赖框架的 ReAct Agent能调用 2-3 个工具完成任务。第 7 周末能跑通 MCP 工具链Agent 通过标准协议调用外部工具有 Skill 分层设计。第 11 周末有一个端到端项目包含评估、监控、错误处理。第 14 周末能独立设计 Multi-Agent 系统理解编排模式的选择依据。如果某一周的自查没通过不要急着推进下一周。Agent 开发是累积性的前面的坑不填后面会以更复杂的形式出现。6. 从学习路径到长期开发把统一 Key 通道用成基础设施14 周走完你手里应该有一个能跑的 Agent 项目、一套 MCP 工具链、以及一套排查问题的经验。但真正的开发才刚刚开始。长期做 Agent 开发最容易被低估的成本是模型切换成本。你今天用 Claude 做主力明天想试试 GPT 的工具调用后天要接一个国产模型做成本优化。如果每次切换都要改认证、改 Base URL、改 SDK 初始化你的迭代速度会被拖慢。统一 Key 通道的价值在这里体现你的 Agent 代码里只认一个环境变量、一个 Base URL模型切换只是改一个字符串。这意味着你可以第一在同一个项目里做模型路由。简单分类任务走 Haiku复杂推理走 Sonnet成本敏感步骤走 mini 模型。代码里只需要一个 client 实例。第二快速做 A/B 测试。同一个 Prompt 分别发给两个模型对比工具调用准确率和响应质量不需要维护两套认证配置。第三降低新模型接入成本。新模型上线时你只需要确认它的模型 ID然后在代码里加一个分支不需要重新配置认证。如果你还在用多个厂商的 Key 分别管理建议花半小时把配置统一到环境变量里。这个投入在后面的迭代中会省回来。接入文档在 https://taotoken.net/doc 里面有各语言 SDK 的完整配置示例。API Keys 管理在 https://taotoken.net/api-keys 可以创建和轮换 Key。如果你主要做长期编码和 Agent 开发Coding Plan 在 https://taotoken.net/coding-plan 有更详细的用量方案。模型对话测试在 https://taotoken.net/chat 可以快速验证某个模型是否可用。最后给一个实用建议把 14 周路径里的每个验证脚本都保存下来放在项目的tests/目录里。每次换模型或改配置后跑一遍这些脚本确认基础功能没有回归。这比重新读一遍文档快得多。