尧图精选

复刻 Cursor 的 ReAct 循环写 200 行 Agent,只用 TaoToken 发的 Key 行不行?

🕒 发布时间:2026/9/21 0:37:33 📁 来源:尧图网络
1. 复刻 Cursor 的 ReAct 循环200 行 agent.py 到底把 Key 放哪层复刻 Cursor 的 ReAct 循环写 200 行 Agent只用 TaoToken 发的 Key 行不行先给结论行但你要把 Key 放在模型调用这一层而不是指望它参与循环。Cursor 那套骨架拆开看很朴素LLM 当大脑工具当手一个 for 循环当心跳。长会话里每一轮都要把 messages 整段发回模型Token 就是在一次次 tool_calls 往返里消耗掉的。你真正要先定的是请求发去哪一层本地 agent.py 负责循环、文件读写、命令执行和上下文裁剪TaoToken 只提供 Key 和 Base URL。打开官网创建 Key把调用里的 Base URL 写成 https://taotoken.net/api剩下循环逻辑一行都不用交给它。这个判断很重要因为很多人第一次写 Agent 会把“模型服务”和“Agent 运行时”混在一起。模型服务负责根据当前 messages 判断下一步要不要调工具Agent 运行时负责执行工具、把结果塞回消息数组、决定什么时候停。Cursor 看起来像黑魔法核心也是这个循环只是它把工具链、上下文管理、diff 预览、错误重试做得更工程化。你自己写的 200 行版本不需要一开始就追平 Cursor先把 ReAct 循环跑通再逐步补上下文压缩和工具安全。所以本文不讨论怎么把 TaoToken 塞进循环逻辑而是讨论怎么让 agent.py 通过 TaoToken 发请求。你最后会得到三个东西一个可复制的 Python 骨架、一套从零建 React Todo 的验证流程、一份常见报错排查表。适合正在写 Agent、Harness、长会话编程助手的人也适合想理解 Cursor 原理但不想只看概念图的人。下面按“先定边界再写循环再验证请求最后排障”的顺序来。2. TaoToken 前置Key、Base URL 与长会话 Agent 的边界2.1 你只需要拿到 Key 和 Base URLTaoToken 在这个方案里的角色很明确发 Key发 Base URL不参与你的 ReAct 循环也不负责读写文件。也就是说你不需要把工具执行结果上传到某个“Agent 托管层”更不需要让外部服务碰你的本地目录。你的 agent.py 仍然是唯一的心跳它拼 messages、发请求、解析 tool_calls、执行工具、把结果塞回 messages然后进入下一轮。第一次接入时建议按这个顺序走先从官网进入控制台创建 API Key再到 API Keys 页面确认 Key 可用同时在模型列表或接入文档里复制一个支持工具调用的模型 ID。Base URL 固定写https://taotoken.net/api不要自己拼/v1/chat/completionsOpenAI SDK 会自动补路径。如果你在接入文档里看到示例优先按示例的模型名和参数格式来。官网入口可以走这里 TaoToken 官网创建 Key 和查接入文档 API Keys 接入文档2.2 准备目录与依赖先建一个干净目录别一上来就在真实项目根目录跑 Agent。工具会写文件、执行命令工作区隔离是最低成本的保险。目录结构建议这样agent-demo/ agent.py .env workspace/安装依赖和创建虚拟环境mkdir -p agent-demo/workspace cd agent-demo python -m venv .venv source .venv/bin/activate pip install openai python-dotenvWindows 下激活命令换成.venv\Scripts\activate.env里放两个变量不要把 Key 写进代码TAOTOKEN_API_KEY你的Key TAOTOKEN_MODEL控制台里支持工具调用的模型ID代码里通过环境变量读取后面换模型只改.env不用动循环骨架。注意Key 不要提交到 Git也不要在日志里打印完整值。需要检查时只打印前几位或长度。3. 可复制配置agent.py 里接上 https://taotoken.net/api3.1 客户端初始化和工具 schema核心只有一句base_urlhttps://taotoken.net/api。下面这段是 Agent 的模型调用层循环逻辑仍然在你本地。先写导入、客户端、工作区限制和工具定义。import os import json import subprocess from pathlib import Path from dotenv import load_dotenv from openai import OpenAI load_dotenv() client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api, ) MODEL os.environ[TAOTOKEN_MODEL] WORKSPACE Path(./workspace).resolve() WORKSPACE.mkdir(parentsTrue, exist_okTrue) MAX_STEPS 12 TOOLS [ { type: function, function: { name: list_dir, description: 列出 workspace 内某个目录的文件和子目录path 用相对路径默认 ., parameters: { type: object, properties: {path: {type: string}}, required: [], }, }, }, { type: function, function: { name: read_file, description: 读取 workspace 内的文本文件内容path 是相对路径。, parameters: { type: object, properties: {path: {type: string}}, required: [path], }, }, }, { type: function, function: { name: write_file, description: 写入文本文件到 workspace目录不存在会自动创建。, parameters: { type: object, properties: { path: {type: string}, content: {type: string}, }, required: [path, content], }, }, }, { type: function, function: { name: run_cmd, description: 在 workspace 下执行 shell 命令返回退出码和输出。, parameters: { type: object, properties: {command: {type: string}}, required: [command], }, }, }, ]工具描述不要写太长但要写清楚边界。容易混淆的参数写详细比如path明确是相对 workspace 的路径。写太长会占上下文长会话里每轮都要带上这些描述Token 消耗会上去。3.2 工具执行器与安全路径工具执行器是 Agent 的“手”。这里加两个基本保护路径不能越出 workspace命令不能包含明显高危片段。生产环境还应该做白名单不要直接开放任意 shell。def safe_path(rel: str) - Path: p (WORKSPACE / rel).resolve() if not str(p).startswith(str(WORKSPACE)): raise ValueError(路径越界只能访问 workspace 内文件) return p def tool_list_dir(args): path args.get(path, .) p safe_path(path) if not p.exists(): return f目录不存在{path} items [] for child in sorted(p.iterdir()): mark / if child.is_dir() else items.append(child.name mark) return \n.join(items) if items else 空目录 def tool_read_file(args): p safe_path(args[path]) if not p.exists(): return f文件不存在{args[path]} text p.read_text(encodingutf-8, errorsreplace) return text[:8000] def tool_write_file(args): p safe_path(args[path]) p.parent.mkdir(parentsTrue, exist_okTrue) p.write_text(args[content], encodingutf-8) return f已写入 {args[path]}共 {len(args[content])} 字符 def tool_run_cmd(args): cmd args[command].strip() danger [rm -rf /, mkfs, :(){, shutdown, reboot] if any(d in cmd for d in danger): return 拒绝执行高危命令 r subprocess.run( cmd, shellTrue, cwdWORKSPACE, capture_outputTrue, textTrue, timeout180, ) out (r.stdout r.stderr)[-4000:] return fexit{r.returncode}\n{out} TOOL_MAP { list_dir: tool_list_dir, read_file: tool_read_file, write_file: tool_write_file, run_cmd: tool_run_cmd, }这里有个常见坑命令已经在cwdWORKSPACE下执行所以 system prompt 里要明确告诉模型需要进子目录时在一条命令里写cd todo-app npm install不要既写cd又幻想有个不存在的workingDirectory参数。否则模型很容易在同一轮里重复切换目录把命令跑歪。3.3 ReAct 循环体循环体是整个 Agent 的心跳。每一轮发请求看模型是否返回 tool_calls没有 tool_calls 就结束有 tool_calls 就逐个执行把结果塞回 messages再进入下一轮。注意 assistant 消息和 tool 消息要配对tool_call_id必须一致。SYSTEM_PROMPT 你是一个在本地 workspace 里工作的编程 Agent。 规则 1. 只能使用提供的工具所有路径都是相对 workspace 的路径。 2. 写文件前先 list_dir 或 read_file不要凭空猜文件内容。 3. 命令默认已经在 workspace 下执行需要进子目录时用 cd xxx 命令。 4. 每轮只做一件小事拿到工具结果后再决定下一步。 5. 完成后直接输出总结不要继续调用工具。 def run_agent(task: str): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: task}, ] for step in range(1, MAX_STEPS 1): resp client.chat.completions.create( modelMODEL, messagesmessages, toolsTOOLS, tool_choiceauto, temperature0, ) msg resp.choices[0].message assistant_msg {role: assistant, content: msg.content or } if msg.tool_calls: assistant_msg[tool_calls] [ { id: tc.id, type: function, function: { name: tc.function.name, arguments: tc.function.arguments, }, } for tc in msg.tool_calls ] messages.append(assistant_msg) if not msg.tool_calls: print(f[step {step}] finish) print(msg.content) return msg.content for tc in msg.tool_calls: name tc.function.name args json.loads(tc.function.arguments or {}) print(f[step {step}] {name} {args}) try: result TOOL_MAP[name](args) except Exception as e: result f工具执行失败{e} messages.append({ role: tool, tool_call_id: tc.id, content: str(result), }) print(达到最大步数任务未完成) return 达到最大步数 if __name__ __main__: import sys task sys.argv[1] if len(sys.argv) 1 else 在 workspace 创建一个 hello.txt内容写 hello agent run_agent(task)这段不到 200 行已经包含 Cursor 式 ReAct 的核心绑定工具、模型决策、执行工具、结果回填、下一轮思考。temperature0建议保留写代码要稳定不要让它自由发挥。工具结果必须原封不动塞回去否则模型下一轮会“失忆”反复调用同一个工具。4. 验证请求从零建 React Todo七轮循环看 tool_calls4.1 设置环境变量并启动先把.env填好然后激活虚拟环境给 Agent 派一个多步骤任务。为了验证长会话循环任务不要太小最好包含建项目、写文件、装依赖、构建验证。下面这条命令会跑 Vite React 项目但不会常驻启动 dev server。export TAOTOKEN_API_KEY你的Key export TAOTOKEN_MODEL控制台里支持工具调用的模型ID python agent.py 在 workspace 里用 Vite 创建一个 React TodoList。步骤1 执行 npm create vitelatest todo-app -- --template react2 进入 todo-app改写 src/App.jsx 实现增删改查、筛选、localStorage3 写必要的 CSS4 执行 npm install5 执行 npm run build 验证构建成功。不要启动常驻 dev server。Windows PowerShell 用$env:TAOTOKEN_API_KEY你的Key $env:TAOTOKEN_MODEL控制台里支持工具调用的模型ID python agent.py 同上任务4.2 观察日志和成功结果正常情况下你会看到类似这样的轮次[step 1] run_cmd {command: npm create vitelatest todo-app -- --template react} [step 2] list_dir {path: todo-app} [step 3] read_file {path: todo-app/src/App.jsx} [step 4] write_file {path: todo-app/src/App.jsx, content: ...} [step 5] write_file {path: todo-app/src/App.css, content: ...} [step 6] run_cmd {command: cd todo-app npm install} [step 7] run_cmd {command: cd todo-app npm run build} [step 7] finish成功时npm run build的退出码应为 0workspace 下会出现todo-app/dist。打开todo-app/src/App.jsx你能看到模型写入的增删改查、筛选和 localStorage 逻辑。这个验证的重点不是 Todo 有多漂亮而是确认三件事模型能稳定返回 tool_calls本地循环能正确执行工具工具结果能回填后继续下一轮。实测下来七轮左右能完成一个可构建的 React Todo。如果模型在第 3 步后就不再调工具通常是 tools schema 或模型能力问题如果一直在 read_file 和 list_dir 之间打转通常是工具结果没有正确塞回 messages。长会话里还要关注 Token 曲线每轮都会带上历史 messages工具输出越长后续每轮越贵。所以run_cmd里截断输出到 4000 字符、read_file截断到 8000 字符是很有必要的工程习惯。4.3 长会话下的上下文裁剪Agent 要跑长任务不能无限把历史堆进 messages。一个简单策略是保留 system 消息、最近若干轮对话和任务摘要但不要直接按条数硬切因为可能把 assistant 的 tool_calls 和后面的 tool 消息切断。更稳的做法是按“轮次”保留或者只截断工具输出内容。def trim_tool_output(text: str, limit: int 4000) - str: if len(text) limit: return text return text[:2000] \n...[中间省略]...\n text[-1500:]你可以把工具执行结果先过一遍这个函数再放进messages。TaoToken 不负责你的上下文管理这是 Harness 该做的事。真正跑长期编码时上下文裁剪和工具安全比模型名字更影响体验。5. 本篇常见错排查401、404、tool_calls 丢失与历史膨胀5.1 401 Unauthorized报错通常长这样AuthenticationError: 401。先检查环境变量名有没有写错代码里是TAOTOKEN_API_KEY.env里也必须是同一个。再确认load_dotenv()在创建客户端之前执行。Windows 下如果同时用了系统环境变量和.env优先级可能和你想的不一样。检查时不要打印完整 Key打印长度即可python -c import os; print(len(os.getenv(TAOTOKEN_API_KEY,)))如果长度是 0说明没读到。到 API Keys 重新确认 Key再看 接入文档 的示例格式。5.2 404 Not Found 或 model not found最常见原因是 Base URL 写错。正确写法是base_urlhttps://taotoken.net/api不要写成https://taotoken.net/api/v1也不要手动拼/chat/completions。OpenAI SDK 会自己补路径。另一个原因是模型 ID 不对或当前 Key 没有该模型权限。模型 ID 从控制台复制不要凭记忆写。如果你在模型对话里能正常聊天但代码里 404优先检查 base_url 和模型名。5.3 tool_calls 为空Agent 不干活如果模型只返回文本不返回tool_calls先看三处模型是否支持工具调用tools数组格式是否合法tool_choice是否写成auto。有些模型在中文描述下会犹豫可以把工具描述写得更直接例如“必须使用 write_file 写入文件”。如果换了模型还是不行就换一个明确支持 function calling 的模型。验证模型能力可以去 模型对话 里发一条带 tools 的请求看它能否稳定返回工具调用。5.4 死循环、反复调用同一个工具典型日志是read_file、read_file、read_file不停重复。原因通常是工具结果没有塞回 messages或者tool_call_id对不上。每次执行完工具必须追加{role: tool, tool_call_id: tc.id, content: result}。另外assistant消息里也要保留对应的tool_calls。少任何一边模型都会觉得工具没执行于是继续调。这个错误非常隐蔽建议在循环里打印len(messages)和每一步的工具名方便确认配对关系。5.5 历史膨胀、中途断掉长任务跑到后面messages 会越来越大。表现是响应变慢、成本上升、模型开始忽略早期指令。处理方式有三层工具输出截断按轮次保留最近 N 轮把早期任务和目标压缩成一条摘要。不要直接把 messages 切片因为可能破坏 tool 消息配对。简单做法是只对role tool的消息做内容截断或者每 5 轮把早期对话总结成一段system补充。如果你准备做长期编码 Agent可以看 Coding Plan里面更适合持续多轮的编码场景。5.6 命令跑飞或路径越界把WORKSPACE设成项目子目录不要设成用户根目录。run_cmd至少加高危命令拦截生产环境建议白名单。write_file和read_file必须走safe_path。另外模型很擅长写“看起来对”的命令但不擅长处理交互式命令。像npm create vite这种可能交互的脚手架要加-- --template react让它非交互执行。否则命令卡住Agent 会以为工具失败然后反复重试。6. 语义一致 CTAAgent 接入排障、模型验证与长期编码怎么选把 Key 放对环境变量把base_url写成https://taotoken.net/api再让 agent.py 自己跑 for 循环这套 200 行 ReAct 骨架就能持续跑多轮。TaoToken 只发 Key 和 Base URL循环逻辑、工具执行、文件读写、上下文裁剪都在你本地。你不需要改 Cursor 式 ReAct 的“绑定工具、执行工具、结果回填、下一轮思考”这四步只改模型调用这一处。如果你现在卡在 401、404 或 tool_calls 为空先去 API Keys 确认 Key再对照 接入文档 检查参数。只是想验证模型能不能稳定返回工具调用去 模型对话 发一条最小请求最直接。准备把循环跑成长期编码或 Agent 工作流再看 Coding Plan 会少走弯路。官网入口在 TaoToken 官网API 地址直接记https://taotoken.net/api。我自己的习惯是 Key 只放.env循环只留agent.py换模型只改两个环境变量这样 200 行骨架就能一直复用。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →