 AI Agent:TaoToken 統(tǒng)一 Key 接入 ReAct 與 MCP 配置骨架)
1. 從零搭一個(gè) AI Agent為什么卡在“接入”這一步AI Agent 這個(gè)詞這兩年出現(xiàn)頻率很高但真正動(dòng)手從零寫一個(gè)能跑的最小 Agent很多人會(huì)卡在同一個(gè)地方模型通道怎么接、工具怎么掛、ReAct 循環(huán)怎么轉(zhuǎn)起來。我自己第一次寫的時(shí)候代碼邏輯其實(shí)不復(fù)雜難的是把“模型調(diào)用”和“工具調(diào)用”這兩條鏈路拼成一個(gè)閉環(huán)還要保證每一輪推理都能拿到上一輪工具執(zhí)行的真實(shí)結(jié)果。這篇要做的是一個(gè)最小但完整的 AI Agent用 ReAct 推理循環(huán)作為主干用 MCP 作為工具擴(kuò)展骨架用 TaoToken 統(tǒng)一 Key 作為模型接入通道。適合已經(jīng)會(huì)寫 Python、想搞清楚 Agent 內(nèi)部到底怎么轉(zhuǎn)的人也適合已經(jīng)在用各種編程助手、想自己拆一遍原理的人。走完之后你會(huì)得到一個(gè)能跑通“推理 → 行動(dòng) → 觀察 → 再推理”的最小 Agent并且能完成一次真實(shí)的工具調(diào)用驗(yàn)證。整個(gè)鏈路我拆成 6 步對(duì)話歷史、工具調(diào)用、MCP 接入、TODO 錨點(diǎn)、SubAgent 委派、Skills 按需加載。其中第 2 步和第 3 步是核心也是接入配置最容易出錯(cuò)的地方。下面每一步都給可復(fù)制的配置骨架和驗(yàn)證動(dòng)作不堆概念。2. TaoToken 前置統(tǒng)一 Key 與通道準(zhǔn)備在寫 Agent 之前先把模型通道準(zhǔn)備好。TaoToken 在這里的角色是統(tǒng)一 Key 和 API 通道你不需要在代碼里分別維護(hù)多個(gè)模型供應(yīng)商的地址和密鑰Agent 的 LLM 客戶端只認(rèn)一個(gè) base_url 和一個(gè) key后面換模型、加模型都在這一層解決。官網(wǎng)入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注冊(cè)后在控制臺(tái)創(chuàng)建 API Key。API 地址是 https://taotoken.net/api 注意這個(gè)地址不帶任何查詢參數(shù)直接作為 OpenAI 兼容協(xié)議的 base_url 使用。這里有個(gè)容易踩的坑很多人把 base_url 寫成帶/v1或者帶一堆參數(shù)的完整地址結(jié)果 SDK 拼接路徑時(shí)出現(xiàn)雙斜杠或者路徑錯(cuò)位。正確做法是 base_url 只寫到域名加/api具體路徑交給 SDK 處理。如果你用的是 OpenAI 官方 SDK它會(huì)自動(dòng)補(bǔ)/chat/completions。Key 的權(quán)限建議單獨(dú)建一個(gè)只給對(duì)話和工具調(diào)用需要的模型權(quán)限不要用主賬號(hào)的萬能 Key。Agent 在調(diào)試階段會(huì)頻繁發(fā)請(qǐng)求單獨(dú) Key 方便你隨時(shí)吊銷和輪換。拿到 Key 之后先別急著寫 Agent用一條 curl 驗(yàn)證通道是否通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: 只回復(fù)兩個(gè)字通了}], stream: false }返回里有choices[0].message.content就說明通道沒問題。這一步別跳過后面 Agent 報(bào)錯(cuò)時(shí)你能快速判斷是通道問題還是代碼問題。3. 可復(fù)制配置settings.json 與 config.toml 骨架Agent 的配置分兩塊一塊是模型通道配置一塊是 MCP Server 配置。我習(xí)慣把模型通道放在settings.jsonMCP 放在config.toml兩者分開管理改一個(gè)不影響另一個(gè)。3.1 settings.json模型通道與 ReAct 參數(shù){ 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 循環(huán)的硬上限防止模型在工具調(diào)用里繞圈。我一開始設(shè)成 50結(jié)果有一次模型反復(fù)讀同一個(gè)文件燒了不少 token。12 到 15 是比較穩(wěn)的范圍。temperature設(shè) 0.2Agent 需要的是穩(wěn)定決策不是創(chuàng)意寫作。3.2 config.tomlMCP 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 httpMCP 的 transport 有兩種常見形態(tài)本地 Server 走 stdio遠(yuǎn)程 Server 走 http。stdio 模式下 Agent 啟動(dòng)時(shí)會(huì)拉起子進(jìn)程通過標(biāo)準(zhǔn)輸入輸出通信http 模式下直接請(qǐng)求遠(yuǎn)端地址。filesystem這個(gè) Server 給 Agent 提供讀寫文件能力fetch提供網(wǎng)絡(luò)請(qǐng)求能力這兩個(gè)是最小可用組合。注意env里不要塞任何敏感信息MCP Server 的密鑰應(yīng)該通過環(huán)境變量注入不要寫死在 toml 里。如果你在本地調(diào)試custom_tools那個(gè) http 地址可以指向你自己寫的 MCP Server用來驗(yàn)證工具注冊(cè)鏈路。3.3 工具注冊(cè)表結(jié)構(gòu)Agent 啟動(dòng)后需要把內(nèi)置工具和 MCP 工具合并成一張扁平表。結(jié)構(gòu)大概是這樣TOOL_REGISTRY {} def register_tool(name, schema, handler, sourcebuiltin): TOOL_REGISTRY[name] { schema: schema, handler: handler, source: source, } def load_mcp_tools(mcp_client): for tool in mcp_client.list_tools(): register_tool( nametool[name], schematool[inputSchema], handlerlambda args, ttool: mcp_client.call_tool(t[name], args), sourcemcp, )模型看到的只有name和schema它不關(guān)心工具來自內(nèi)置還是 MCP。這個(gè)抽象層很關(guān)鍵后面加工具不用改 ReAct 循環(huán)。4. ReAct 循環(huán)與驗(yàn)證請(qǐng)求配置就緒后核心就是那個(gè)循環(huán)。ReAct 的本質(zhì)是模型輸出工具調(diào)用 → 執(zhí)行工具 → 把結(jié)果塞回對(duì)話歷史 → 再讓模型推理。循環(huán)終止條件是模型不再請(qǐng)求工具直接給出文本回復(fù)。4.1 最小 ReAct 循環(huán)實(shí)現(xiàn)import json from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.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( modelMODEL, messageshistory, toolstools, tool_choiceauto, ) 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 達(dá)到最大迭代次數(shù)任務(wù)未完成這段代碼里有兩個(gè)細(xì)節(jié)值得說。第一history.append(msg)必須把 assistant 的 tool_calls 消息原樣存進(jìn)去否則下一輪模型看不到自己請(qǐng)求過什么工具。第二tool 消息必須帶tool_call_id這是 OpenAI 協(xié)議的要求缺了會(huì)報(bào) 400。4.2 驗(yàn)證一次真實(shí)工具調(diào)用準(zhǔn)備一個(gè)最簡(jiǎn)單的工具比如讀文件def read_file(args): with open(args[path], r, encodingutf-8) as f: return f.read()[:2000] register_tool( nameread_file, schema{ type: function, function: { name: read_file, description: 讀取指定路徑的文件內(nèi)容, parameters: { type: object, properties: { path: {type: string, description: 文件路徑} }, required: [path], }, }, }, handlerread_file, )然后跑history [{role: system, content: 你是一個(gè)會(huì)使用工具的助手。}] result agent_loop(讀一下 ./README.md 的前幾行告訴我這個(gè)項(xiàng)目是做什么的, history, list_tool_schemas()) print(result)如果 Agent 先輸出一個(gè)read_file的工具調(diào)用拿到內(nèi)容后再給出總結(jié)說明 ReAct 循環(huán)通了。這一步是整個(gè) Agent 的“第一次呼吸”跑通之后后面都是在這個(gè)骨架上加?xùn)|西。4.3 MCP 工具接入驗(yàn)證MCP 工具接入后驗(yàn)證方式和內(nèi)置工具一樣只是工具來源不同。啟動(dòng)時(shí)掃描config.toml連接所有 Server拉取工具列表注冊(cè)進(jìn)TOOL_REGISTRY。你可以讓 Agent 執(zhí)行一個(gè)需要 MCP 工具的任務(wù)比如“列出 workspace 目錄下的所有文件”觀察它是否調(diào)用了 filesystem Server 暴露的工具。如果工具沒被調(diào)用先檢查TOOL_REGISTRY里有沒有 MCP 工具再檢查 schema 格式是否和內(nèi)置工具一致。MCP 返回的inputSchema有時(shí)候字段名和 OpenAI 要求的不完全對(duì)齊需要做一層轉(zhuǎn)換。5. 本篇常見錯(cuò)排查接入階段報(bào)錯(cuò)集中在幾個(gè)地方我按出現(xiàn)頻率排一下。401 或 403Key 沒讀到或者權(quán)限不對(duì)。先確認(rèn)環(huán)境變量TAOTOKEN_API_KEY在當(dāng)前 shell 里能echo出來再確認(rèn) Key 沒有多余空格。如果 Key 是從文件讀的注意換行符。404 或路徑錯(cuò)誤base_url 寫錯(cuò)了。正確寫法是https://taotoken.net/api不要加/v1不要加尾部斜杠。SDK 會(huì)自己拼/chat/completions。400 tool_call_id 缺失tool 消息沒帶tool_call_id或者 assistant 的 tool_calls 消息沒存進(jìn)歷史。檢查history.append(msg)那行有沒有執(zhí)行。模型不調(diào)用工具schema 的description寫得太模糊或者tool_choice設(shè)成了none。把工具描述寫清楚“什么時(shí)候用”tool_choice保持auto。MCP Server 啟動(dòng)失敗stdio 模式下command找不到通常是npx不在 PATH 里。用絕對(duì)路徑或者先手動(dòng)跑一遍npx -y modelcontextprotocol/server-filesystem ./workspace確認(rèn)能啟動(dòng)。循環(huán)停不下來max_iterations設(shè)太大或者工具返回結(jié)果太長(zhǎng)把上下文撐爆。給工具結(jié)果加截?cái)啾热缰环祷厍?2000 字符。流式輸出和工具調(diào)用沖突流式模式下 tool_calls 是分片到達(dá)的需要自己拼接。調(diào)試階段建議先關(guān)流式跑通非流式再開。6. 后續(xù)擴(kuò)展與接入入口最小 Agent 跑通之后第 4 到第 6 步是自然延伸。TODO 管理本質(zhì)上是給 Agent 一個(gè)外部狀態(tài)錨點(diǎn)把模型腦子里的計(jì)劃變成可更新的列表防止多步任務(wù)中途跑偏。SubAgent 是把復(fù)雜任務(wù)拆成獨(dú)立上下文單元主 Agent 只負(fù)責(zé)編排子 Agent 各自帶著干凈的歷史執(zhí)行。Skills 則是把領(lǐng)域知識(shí)從系統(tǒng)提示詞里拆出來按需加載平時(shí)不占上下文。這三塊都不需要改 ReAct 循環(huán)它們只是往TOOL_REGISTRY里加新工具然后在系統(tǒng)提示詞里告訴模型什么時(shí)候用。這也是這套骨架的好處核心循環(huán)穩(wěn)定能力通過工具擴(kuò)展。如果你在接入階段卡住優(yōu)先看 API Keys 和接入文檔把通道和 Key 的問題先排掉API Keys 管理https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content想先驗(yàn)證模型通道和工具調(diào)用格式可以直接在模型對(duì)話里試一輪帶 tools 參數(shù)的請(qǐng)求模型對(duì)話https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content如果你打算把 Agent 長(zhǎng)期跑在編碼或自動(dòng)化任務(wù)上Coding Plan 那條通道更適合持續(xù)調(diào)用Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content我自己的習(xí)慣是調(diào)試階段用模型對(duì)話快速驗(yàn)證 schema 和返回格式跑通之后再切到 Agent 代碼里。這樣能把“協(xié)議問題”和“代碼問題”分開排查起來快很多。