# 从 LLM 到 Coding Agent

> 逐层拆解一个生产级 Coding Agent 在裸 LLM API 之上做的每一件事。

- 作者：David（道雾轩）
- 最后更新：2026-09-13
- 原文：https://daiw.org/manual/llm-to-agent
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 序 · LLM 只是块芯片，Agent 才是整台机器

现在人人都在用 Coding Agent：你说一句“帮我把这个 bug 修了”，它自己去读文件、搜代码、改几处、跑测试、看报错、再改，最后告诉你搞定了。整个过程像有个真人在你的终端里干活。

但如果你去看它底层依赖的东西——**一个 LLM 的 API**——你会发现那玩意儿“什么都不会”：

- 它**无状态**：每次调用都是一张白纸，不记得上一句你说过什么。
- 它**没有记忆**：关掉这次请求，所有上下文烟消云散。
- 它**不能做任何事**：它只会**输出文本**。它读不了你的文件，跑不了命令，改不了代码。它连“现在几点”都不知道。
- 它甚至**不知道自己该停在哪**：给它一段话，它就续写一段话，仅此而已。

于是本专栏的核心问题只有一个：

> **一个只会“输出文本”的无状态函数，是怎么变成一个能自主完成复杂编程任务的 Agent 的？这中间，工程上到底做了哪些事？**

## 一块芯片和一台机器的距离

打个比方：LLM 就像一块 CPU。CPU 很强，但一块裸芯片什么也干不了——你没法拿一块 CPU 去打字、上网、写代码。要让它变成一台能用的电脑，你还得给它配上**内存、总线、硬盘、操作系统、驱动、输入输出**……

Coding Agent 之于 LLM，正是**整台机器之于那块芯片**。模型提供“智能”，但把智能变成“能自主干活的系统”，靠的是外面这一整圈工程：

- 怎么让它**能调用工具**(读文件、跑命令)？
- 怎么让它**自主循环**，而不是问一句答一句？
- 怎么**喂给它恰到好处的上下文**，又不撑爆那有限的上下文窗口？
- 怎么在它**跑飞、报错、被打断**时优雅恢复？
- 怎么**控制权限**，不让它把你的机器搞坏？
- 怎么**省钱省延迟**，不让每次对话烧掉一杯咖啡的钱？

这些问题，每一个都不在“模型”里，而在“模型外面那一圈”。**这一圈，就是本专栏要逐层拆开的东西。**

<Callout type="info">
  本专栏的方法论：**一篇文章只解决一个问题**。每篇都按「这个问题的背景 → 解决方案的思路 → 落到代码的实现细节」三段式展开，力求把每个技术点揉碎讲透。我们会从“一次最朴素的 API 调用”出发，每加一篇，就往这块裸芯片上焊一个部件，直到最后拼出一台完整的机器。
</Callout>

## 路线图

专栏按“从裸 API 逐步长成完整 Agent”的顺序推进，大致分七个阶段：

**第一阶段 · 起点** —— 先看清我们手里到底有什么。
- 一次 LLM API 调用到底给了你什么，又没给你什么。

**第二阶段 · 让模型能做事** —— 从“输出文本”到“能操作世界”。
- 工具调用(function calling)：让模型开口说“我要调用哪个工具”。
- **Agent Loop**：把一次性问答变成一场自主循环——这是“Agent”这个词真正的分水岭。
- 工具系统的抽象设计：一个可扩展、类型安全、自带权限的工具到底长什么样。

**第三阶段 · 流式与并发** —— 让它快起来、并行起来。
- 流式处理：边生成边解析，以及增量到达的工具参数。
- 边流边执行 + 并发编排：工具块一到就派发，安全的工具并行跑。

**第四阶段 · 安全与控制** —— 别让它把你机器搞坏，也别让它跑飞。
- 权限系统：allow / deny / ask，权限模式，危险操作的闸门。
- 中断与转向：如何在流式中途叫停，并保持消息历史的合法性。
- Hooks：在工具调用前后、回合结束时插入你自己的逻辑。

**第五阶段 · 上下文工程** —— 整个 Agent 最难、最见功力的部分。
- Token 预算：与有限上下文窗口的持久战。
- 系统提示与上下文注入：该塞什么、在哪塞、怎么塞。
- Prompt 缓存：省钱省延迟的命脉，以及“字节稳定性”这条隐形红线。
- 自动压缩：上下文快满了，怎么把历史“折叠”成摘要还不丢关键信息。
- 精细化上下文管理：按需删除巨型工具结果、外置存储、内容替换。

**第六阶段 · 健壮性** —— 生产环境里，出错是常态。
- 错误恢复与模型回退：重试、退避、降级换模型、孤儿消息清理、各类限额恢复。
- 成本与用量追踪：把 token 换算成钱，并实时盯住。
- 思考(extended thinking)的处理：签名、模型绑定，以及那些能让你调试一整天的坑。

**第七阶段 · 扩展与规模** —— 一个 Agent 打不过，就上一群。
- 子 Agent：任务委派与上下文隔离，让主线程不被细节淹没。
- MCP 与工具的懒加载：接入外部工具生态，以及几百个工具怎么按需加载。
- 会话历史与持久化：JSONL 落盘、断点续接、撤销。

**实战番外 · 综合运用** —— 把上面所有零件组装起来，干一件真正复杂的活。
- 仓库总结的完整流程：一个几百万 token 的仓库塞不进窗口，Agent 是怎么建立起那张地图的。

<Callout type="warn">
  **一句前置声明**：本专栏聚焦**通用的 Agent 工程技术**——这些模式适用于任何基于 LLM 构建的 Agent。文中所有代码均为讲解而写的**独立示例**，用来演示技术要点本身，你可以照着在任何 LLM API 上复刻。我们只谈技术，不谈具体产品。
</Callout>

## 配套代码：一个从零搭起的最小 Agent

本专栏配一个**可运行的开源仓库** 👉 [github.com/davidapp/coding-agent-from-scratch](https://github.com/davidapp/coding-agent-from-scratch)。它不是一堆零散片段，而是**一个随专栏逐篇长大的真实 Agent**：每读完一篇，仓库就演进一步，加上那一篇讲的部件。你可以 `git checkout` 到任意一篇对应的版本，直接把那个阶段的 Agent 跑起来。

读到第二阶段结束，这个仓库已经是一个**真能自主读写文件、跑命令、改代码**的最小 Coding Agent 了——总共不过几百行。文章讲“为什么这么设计”，代码给“具体怎么落地”，两边对照着看。

## 读之前，你最好有

- 用过至少一次某个 LLM 的 API(知道 `messages`、`system`、`stream` 大概是什么)。
- 会读 TypeScript(示例用 TS 写，但讲的是**思想**，换成 Python/Go 一样成立)。
- 对“Agent 到底是不是黑魔法”心存好奇——读完你会发现，它一点也不魔法，全是**扎实的工程**。

准备好了，我们从最朴素的起点开始：[一次 LLM API 调用，到底给了你什么](https://daiw.org/manual/llm-to-agent/raw-llm-api)。

## 本专栏篇目

- [从 LLM 到 Coding Agent](https://daiw.org/manual/llm-to-agent.md): 逐层拆解一个生产级 Coding Agent 在裸 LLM API 之上做的每一件事。

### 起点 · 裸 LLM API

- [01 · 一次 LLM API 调用，到底给了你什么](https://daiw.org/manual/llm-to-agent/raw-llm-api.md): 在搭 Agent 之前，先把地基看清：一次 LLM API 调用的请求与响应长什么样、stop_reason 和 usage 意味着什么，以及三个决定性的“它不给你”——无状态、无记忆、不能行动。这三个“没有”，正是后面每一篇要填的坑。

### 让模型能做事

- [02 · 工具调用：让模型的“意图”变成真实的“动作”](https://daiw.org/manual/llm-to-agent/tool-use.md): 模型只会输出文本，怎么让它读文件、跑命令？答案是工具调用：你用 JSON Schema 声明工具，模型输出一个结构化的“调用请求”，你来执行、再把结果喂回去。讲清 tool_use / tool_result 的完整闭环，以及“模型永远不碰你的机器”这个安全内核。
- [03 · Agent Loop：所谓智能体，内核是一个 while 循环](https://daiw.org/manual/llm-to-agent/agent-loop.md): 把“工具调用”从一次性问答变成自主推进，只差一个循环。本篇给出最小可用的 Agent Loop(约 30 行)，再逐层剖开一个生产级循环真正要处理的：终止条件、失控保护、流式产出、跨轮状态，以及它为什么必须是一个 async generator。
- [04 · 工具系统的抽象设计：一个工业级工具的解剖](https://daiw.org/manual/llm-to-agent/tool-abstraction.md): 前三篇里的 executeTool(name, input) 是个玩具。真实工具要自带参数校验、动态描述、权限检查、并发/只读/危险性标记、以及“给模型看”和“给人看”两套结果渲染。本篇解剖一个工业级 Tool 接口，并讲透两个决定性设计：元数据驱动编排，与安全默认(fail-closed)。

### 流式与并发

- [05 · 流式处理：边生成边消费，以及工具参数的“逐字到达”](https://daiw.org/manual/llm-to-agent/streaming.md): 非流式调用让用户干等，还让你没法对半成品输出做任何事。本篇拆开流式协议的事件序列，重点讲一个反直觉的细节——工具调用的参数是作为“部分 JSON”一片片流进来的，这直接决定了工具能多早被派发。
- [06 · 边流边执行 + 并发编排：把等待时间榨干](https://daiw.org/manual/llm-to-agent/streaming-tool-execution.md): 工具块一流完就立刻派发，让工具执行和模型继续生成在时间上重叠；同一批工具里，并发安全的并行跑、其余保序串行。讲清 isConcurrencySafe 如何驱动编排，以及“读可以乱序、写必须有序”这条铁律。

### 安全与控制

- [07 · 权限系统：在“放手让它干”和“别让它闯祸”之间划线](https://daiw.org/manual/llm-to-agent/permissions.md): 一个能自主跑任意命令、改任意文件的 Agent 足够危险。权限系统就是那道闸门。讲清分层权限模型(工具自分类 → 规则 → 权限模式)、canUseTool 闸门，以及一个关键设计：拒绝要“喂回模型”，而不只是报错。
- [08 · 中断与转向：在流式、并发、欠着一堆 tool_result 时干净地叫停](https://daiw.org/manual/llm-to-agent/interrupt.md): 用户想中途喊停，或改主意换个方向。难点在于：此时可能已经发出了 tool_use 却还没产出 tool_result，而历史里每个 tool_use 都必须配对。讲清中断如何合成“已取消”结果、如何把 AbortSignal 贯穿到底，以及“硬中断”与“转向”的区别。
- [09 · Hooks：给 Agent 开一组可编程的插槽](https://daiw.org/manual/llm-to-agent/hooks.md): 用户想在工具执行前后、回合结束时插入自己的逻辑——自动格式化、提交前检查、强制“测试不过不许停”。讲清生命周期钩子的设计：PreToolUse 拦截/改写、PostToolUse 反应、以及最有意思的 Stop 钩子如何“拽住”想收工的 Agent。

### 上下文工程

- [10 · Token 预算：与有限上下文窗口的持久战](https://daiw.org/manual/llm-to-agent/token-budget.md): 每轮重发全部历史，而工具结果(一次文件读、一条命令输出)动辄上万 token，上下文窗口迟早撑爆。讲清为什么要估算 token、如何用 API 回报的 usage 作为地面真值、以及“警告—自动压缩—硬阻断”的分级水位线。这是整个上下文工程的起点。
- [11 · 系统提示与上下文注入：每轮喂给模型的那坨东西，到底是怎么拼的](https://daiw.org/manual/llm-to-agent/context-injection.md): 上下文远不止消息历史。每轮发给模型的 prompt，是由系统提示、环境信息、项目记忆、动态附件层层拼装出来的。讲清各层该放什么、放哪里，以及一个贯穿始终的分界原则——静态的放高处、动态的放低处，这直接决定了下一篇 Prompt 缓存的成败。
- [12 · Prompt 缓存：省钱省延迟最立竿见影的一招](https://daiw.org/manual/llm-to-agent/prompt-caching.md): 系统提示和工具定义每轮重发、重算，又贵又慢。Prompt 缓存让稳定的前缀被复用，cache-read 的价格常只有正常输入的十分之一。讲清缓存断点怎么放、滚动断点如何跟住增长的历史，以及那条“改一个字节、缓存全废”的字节稳定性红线。
- [13 · 自动压缩：上下文快满了，把历史折叠成摘要](https://daiw.org/manual/llm-to-agent/auto-compaction.md): 缓存省钱但不缩小历史；当历史真的装不下，就得把老对话摘要掉。讲清压缩边界(保留近期、摘要远古)、摘要 prompt 为何是成败关键、proactive 与 reactive 两种触发，以及压缩与缓存那对绕不开的矛盾。
- [14 · 精细化上下文管理：外科手术，而非大刀阔斧](https://daiw.org/manual/llm-to-agent/context-compaction.md): 自动压缩是钝器——它摘要掉一整段、还击穿缓存。有时你只想精准处理“那个占了两万 token 的巨型工具结果”。讲清三把手术刀：工具结果预算(超限外置)、按 tool_use_id 定点删除的 microcompact、以及内容外置引用，以及它们如何在减负的同时保住缓存。

### 健壮性

- [15 · 错误恢复与模型回退：把“出错”当成常态](https://daiw.org/manual/llm-to-agent/error-recovery.md): 生产环境里模型调用会以各种方式失败——限流、过载、超时、上下文超长、输出被截断。讲清可恢复与致命错误的分野、指数退避重试、模型降级回退、输出截断的两种恢复、以及重试时那些必须清理的“孤儿消息”，还有一条贯穿始终的红线：别把错误恢复写成死亡螺旋。
- [16 · 成本与用量追踪：把 token 换算成钱，并实时盯住](https://daiw.org/manual/llm-to-agent/cost-tracking.md): 每次调用都在花钱，而且四种 token(输入/输出/缓存写/缓存读)价格天差地别。讲清如何用分价目表把 usage 换算成美元、为什么必须分开统计这四类、子 Agent 与压缩调用也要计入，以及预算硬闸怎么接回主循环。
- [17 · 扩展思考的处理：签名、模型绑定，和那些能调一整天的坑](https://daiw.org/manual/llm-to-agent/thinking.md): 扩展思考能显著提升 Agent 解难题的能力，却带来一组独特的坑：思考块必须整段保留、带着不可篡改的签名、而且签名和具体模型绑定。讲清思考块的三条铁律、它和流式/历史/模型回退的相互作用，以及为什么它是最容易让你调一整天的特性。

### 扩展与规模

- [18 · 子 Agent：用上下文隔离，把细节挡在主线程之外](https://daiw.org/manual/llm-to-agent/subagents.md): 一个 Agent 读 20 个文件回答一个问题，会把 20 份文件内容全灌进主上下文。子 Agent 让子任务在独立上下文里跑，只把结论带回来。讲清“上下文防火墙”这个核心价值、子 Agent 作为一个工具的实现、以及扇出并行。
- [19 · MCP 与工具懒加载：接入开放生态，并解决“几百个工具”的规模问题](https://daiw.org/manual/llm-to-agent/mcp.md): 内置工具写死了，真实用户想接入数据库、浏览器、公司 API，还可能有几百个。讲清 MCP 作为外部工具的标准协议如何让工具生态开放化、任何外部工具如何复用同一个 Tool 抽象，以及“几百个工具塞爆 prompt”怎么用延迟加载 + 工具搜索来解。
- [20 · 会话历史与持久化：让 Agent 活过进程的生命周期](https://daiw.org/manual/llm-to-agent/session-history.md): 到目前为止一切都在内存里，进程一关就灰飞烟灭。收官篇讲如何用 append-only JSONL 把会话落盘、崩溃安全地重放、大块内容外置、并发写加锁，以及为什么“抄本即真相”。最后回望全专栏——我们如何把裸 LLM 缺的三样东西，一件件补齐。

### 实战 · 综合运用

- [21 · 实战：LLM 是怎么总结一个代码仓库的](https://daiw.org/manual/llm-to-agent/repo-summarization.md): 一个中等仓库有几百万 token，窗口只有几十万——“总结仓库”本质是采样与预算分配问题，不是“读完再总结”。本篇把这条流水线的六个阶段逐个拆开：git 边界裁剪、确定性骨架采集、tree-sitter 符号 map、重要性排序、分治总结与归并、验证与失效管理，并给出七个常见失败模式。
