Cursor、Claude Code、Codex 这批 Agent 工具起来之后,很多人以为背后是什么魔法。真撕开看,内核薄得离谱——一个 while 循环,加一堆工具函数注册,就跑起来了。我自己动手写了一个能跑通的最小版本,去掉注释和空行,主循环三十行左右。分享一下这个东西是怎么长出来的,以及从玩具到能用之间还差什么。
三十行的核心
import jsonfrom openai import OpenAIclient = OpenAI()TOOLS = {}deftool(schema):defwrap(fn): TOOLS[fn.__name__] = (fn, schema)return fnreturn wrap@tool({"type": "function", "function": {"name": "read_file","parameters": {"type": "object","properties": {"path": {"type": "string"}}, "required": ["path"]}}})defread_file(path):return open(path).read()defagent(task, max_steps=20): msgs = [{"role": "user", "content": task}]for _ in range(max_steps): r = client.chat.completions.create( model="gpt-5.5", messages=msgs, tools=[s for _, s in TOOLS.values()]) m = r.choices[0].message msgs.append(m)ifnot m.tool_calls:return m.contentfor tc in m.tool_calls: fn, _ = TOOLS[tc.function.name]try: out = fn(**json.loads(tc.function.arguments))except Exception as e: out = f"ERROR: {e}" msgs.append({"role": "tool","tool_call_id": tc.id, "content": str(out)})return"步数用光"
关键三件事:LLM 决定调什么工具、工具返回值再喂回 LLM、没有工具调用就退出。Cursor 们做的事情,就是把工具做得非常丰富——read_file、edit_file、run_terminal、grep_search,然后配一个几千字的 system prompt 逼它按规矩来。
玩具跑起来之后,坑立刻冒出来
自己拿真实任务跑几次,你会碰到三个问题,一个都躲不掉。
上下文炸掉。 让 Agent 读几个大文件、跑几次测试,msgs 数组瞬间爆到十万 token。就算 GPT 有 1M 窗口,多轮下来也扛不住,成本也上天。我的做法是给消息数组加一个软阈值,比如 80k token 就触发压缩:把中间的 tool 结果丢给一次 LLM 做 summary,只保留最后三四轮完整消息。别搞得太精细,粗暴一点反而稳。
工具调用会出错。 路径不存在、JSON 字段漏了、命令超时,都是家常便饭。最省事的处理不是让 Agent 自己"意识到",而是把异常 stack trace 直接塞回 tool 返回值。上面代码里那个 try/except 就是干这个的。这一步差距最大——很多玩具项目一报错就整个中断,其实只要错误信息能回喂,Agent 自己就会调头。
串行太慢。 一次一个工具,读十个文件要十轮。Claude Code 的 parallel_tool_calls 就是这么来的。Python 里把 tool_calls 循环换成 asyncio.gather 并发跑,速度立刻起飞。再进一步是 subagent——主 Agent 派一个子 Agent 去做独立子任务,子任务的中间上下文不占主线程 token。这个模式看着复杂,实际就是递归调 agent() 而已。
这三个补丁打完,才勉强能叫"能用"。
真正的门槛在项目索引
前面这套写完,你会发现 Agent 在小项目里挺聪明,一到大代码库就抓瞎。它靠 grep 加 read_file 找代码,命中率低,来回读一堆无关文件。
这时候得给它接一层项目索引。CodeGraph 是一个思路,或者自己用 tree-sitter 加 LSP 建一套:把代码库解析成符号图——函数、类、调用关系、import 依赖。然后给 Agent 加两个工具:find_symbol 按名字查定义,find_references 找所有引用。
区别有多大?以前 Agent 修一个 bug 要读十几个文件,接上索引之后可能三个就够了。而且它不会再"猜"文件位置,直接问符号图。
Cursor 真正强的地方,不是 LLM 也不是那个 UI,是背后的代码索引。自己写 Agent 的最后一公里就在这。前面那三十行谁都写得出,索引这一步没做扎实,就永远停留在玩具阶段。
要不要动手试试?