文章从 LLM(大语言模型)的本质出发,剖析了其自回归生成、上下文记忆和概率性等核心特性,以及 Attention 机制带来的上下文限制。接着详细阐述了强化学习如何让 LLM 从“能说会道”变为“能做事”,使其具备 Agentic 能力。在实现原理层面,文章深入介绍了 LLM API 的核心结构(消息数组、工具调用机制、Reasoning Content 保留)、Prompt Caching 的工程实践以及 Agent Loop 的循环机制,并列举了典型工具。针对 Agentic Coding 的常见问题,如会话间失忆、上下文窗口耗尽、有效上下文远小于标称值等,文章不仅分析了问题根源,还提供了 Observation Masking、LLM Summarization 等解决方案。最后,文章总结了短对话、以对话为单位组织工作、编写高效项目配置文件、200K Token 已足够、复利工程、优化开发者体验(更好的文档、代码结构、反馈循环、为 AI 设计友好工具输出)以及用工程约束驯服 Agent 等一系列最佳实践,强调刻意练习才能成为驾驭 AI 的“专家型通才”。

在 Agentic Coding 实践中,提升效率的关键在于优化与 AI 的协作方式,而非寄望于无限的上下文窗口。核心策略是采用“短对话、精简上下文”的模式,将复杂任务拆解为专注的子对话,并借助“复利工程”将 bug 修复、代码审查等日常经验沉淀为可复用的项目知识库,使系统获得记忆并实现效率的持续增长。此外,改善开发者体验(如清晰文档、快速测试)具有双重价值,既能帮助人类开发者,也能显著提升 AI 的表现。最终,与 AI 的协作应被视为一门需要刻意练习的技能,通过不断实践,开发者可以成为驾驭 AI 的“专家型通才”,在更广阔的领域创造价值。

第一性原理|理解 LLM 的本质
LLM 是如何「思考」的
大语言模型的工作方式可以用一句话概括:预测下一个 token。
当你向 LLM 提问时,它并不是先在「脑中」构思好完整答案再输出。相反,它每次只做一件事:基于当前看到的所有文本(你的输入 + 它已经生成的内容),预测最可能的下一个词是什么。然后把这个词加入序列,再预测下一个,如此循环直到生成结束,这种方式被称为 自回归生成(Autoregressive Generation)。
理解这一点至关重要,因为它揭示了 LLM 的几个本质特征:
1. 没有独立于输出的「思考」过程:LLM 的推理就是生成本身。它不能先想好再说,只能边说边想。这就是为什么让模型「一步步思考」(Chain-of-Thought)会提升效果 — 你实际上是在给它更多的「思考空间」,让它通过生成中间步骤来辅助推理。尽管新一代模型很多都带有显式的 reasoning 能力,依然脱离不了这个范式。
2. 上下文就是全部记忆:LLM 没有独立的记忆存储。它对当前对话的所有「理解」,完全来自于输入给它的上下文窗口。窗口之外的内容,对模型来说就是不存在的。
3. 生成具有概率性:每一步预测都是基于概率分布采样,这意味着同样的输入可能产生不同的输出。
自回归(autoregressive)LLM 的核心工作方式是:一次只预测下一个 token,然后把自己刚生成的内容再喂回去继续生成。这个机制很强(通用、可扩展、能「写出」复杂结构),但也天然带来一些局限,在 Coding Agent 里这些局限会被放大,因为软件开发是「长链路、强约束、强验证」的任务。
当前 Coding Agent 普遍存在的问题:
1. 局部最优:一步一步「续写」,不等于全局规划
-
容易先写得很像、很顺,然后在后面发现不对再补丁式修修补补(历史债务快速累积)。
-
多文件重构/架构调整这种「先规划后落地」的任务,会变成边写边想,导致接口不一致、重复实现、遗漏迁移步骤。
-
对「最终能不能通过测试/CI」缺乏感知,除非你把测试结果显式喂回 agent 循环。
2. 一旦走偏,会沿着偏差继续滚雪球
-
早期误读需求/误判代码库的约束 ,导致后面每一步都在错误世界观里自洽。
-
生成了一个不存在的函数名/类型后,后续会越来越「相信它存在」,直到编译/运行时打脸。
-
在长对话、长任务里尤其明显:越写越像「另一个项目」。
3. 输出是流式的,无法回头修改之前输出的内容
-
擅长「写新文件/新段落」,相对不擅长在复杂代码中做精确、最小化、局部一致的编辑。
-
容易倾向于「重写一遍」而不是「进行修改」,导致 diff 过大、review 成本高。
-
在需要保持大量不变量(API、格式、风格、兼容性)的仓库里尤其痛苦。
4. 满足约束的能力有限,而代码是强语法/强语义约束的
-
「看起来合理」的实现,经常在边界条件、并发、错误处理、资源释放上出错。
-
跨文件的符号解析、泛型/模板推导、宏/代码生成等,靠纯文本推断很容易漏。
-
复杂的重构(rename、move、split)没有 AST/语义工具辅助时,错一个引用就全盘崩。
5. 天然单线程思考,难并行探索
-
在方案选择(架构、依赖、实现路径)上容易早早押注一个方向,缺少系统性对比。
-
排查 bug 时更像「单线推理」,不如人类/工具那样并行假设、并行验证
Attention:LLM 如何「阅读」上下文
在预测下一个 token 时,模型需要从上下文中提取相关信息,这个过程由 Attention(注意力)机制 完成。
你可以把 Attention 想象成一个动态的「聚光灯」:当模型生成每个新 token 时,它会扫视整个上下文,对不同位置的内容分配不同的「注意力权重」。权重高的部分会对当前生成产生更大影响,权重低的部分则几乎被忽略。
具体来说,对于当前要生成的 token,模型会:
-
计算相关性:当前位置与上下文中每个位置的相关程度
-
分配权重:相关性高的位置获得更高权重,形成一个概率分布
-
加权聚合:根据权重汇总上下文信息,用于预测下一个 token
这个机制有几个重要特性:
-
注意力是稀疏的:模型不会均匀地关注所有内容。在实践中,Attention 权重往往集中在少数关键位置,大部分位置的权重接近于零。
-
注意力需要「学习」的:模型通过训练学习在什么情况下关注什么内容。这意味着它可能会形成某些偏好模式,比如更关注开头和结尾。
-
计算复杂度是平方级的:标准 Attention 需要计算每个位置与所有其他位置的关系,计算量随上下文长度的平方增长。这是上下文长度存在物理上限的根本原因之一。
理解了 Attention 机制,我们就能更好地理解上下文长度为什么会带来多种限制。
上下文长度:最关键的约束
上下文窗口的限制是影响 Coding Agent 设计最核心的约束条件。结合对 Attention 机制的理解,我们可以更清楚地看到这个限制的多个层次。
物理上限
每个模型都有最大 token 限制,目前主流模型通常在 128K 到 200K 之间。这个硬性边界的存在有两个根本原因:
1. Attention 的计算复杂度通常是 O(n²),上下文长度翻倍,计算量变成四倍
2. 需要存储完整的注意力矩阵,内存消耗同样是平方级增长
超过这个长度的内容根本无法被处理。
有效上下文远小于标称上下文
虽然模型宣称支持 200K tokens,但这不意味着在 200K 长度下都能保持良好表现。
问题出在 Attention 的「稀疏性」上:当上下文很长时,注意力权重需要分散到更多位置。如果关键信息只是海量内容中的一小部分,它获得的注意力权重可能会被稀释到难以有效利用的程度。实际上,一个支持 200K 的模型,可能在超过 80K 或 100K 之后,就开始出现明显的性能退化。
性能退化曲线
随着上下文变长,不仅是「记忆」变差,模型的整体能力,包括推理准确性、指令遵循能力、代码生成质量都会下降。
这是自回归 + Attention 组合带来的累积效应:
-
每一步生成都依赖于对上下文的正确理解
-
更长的上下文意味着更稀疏的注意力分布
-
错误理解会传递到后续生成,形成累积误差
理解这些限制,你就能明白为什么 Coding Agent 需要精心设计上下文管理策略,而不是简单地把所有信息塞进窗口。
强化学习:让模型学会「做事」
前面我们讨论的是 LLM 的基础能力,预测下一个 token。但要让模型从「能说会道」进化到「能做事情」,还需要另一个关键技术:强化学习(Reinforcement Learning, RL)。
预训练的局限
LLM 的预训练本质上是「模仿」:通过阅读海量文本,学会预测「人类会怎么写下一个词」。这让模型获得了广泛的知识和流畅的表达能力,但也有明显的局限:
-
模型学会的是「文本看起来应该是什么样」,而不是「什么行动能解决问题」
-
预训练数据中很少有「调用工具 → 观察结果 → 调整策略」这样的交互序列
-
模型可能会生成看起来合理但实际无效的操作步骤
简单来说,预训练教会了模型「说」,但没有教会它「做」。
强化学习的核心思想
强化学习用一个简单的循环来解决这个问题:尝试 → 反馈 → 调整。
想象教一个小朋友下棋:你不会给他看一百万盘棋谱让他背(这是预训练的方式),而是让他实际下棋,赢了就表扬,输了就复盘。通过无数次的尝试和反馈,他逐渐学会什么是好棋、什么是坏棋。
对 LLM 来说,强化学习的过程类似:
-
尝试:让模型在真实或模拟的环境中执行任务(比如调用工具、编辑文件)
-
反馈:根据结果给出奖励信号(任务完成了?代码能运行?测试通过了?)
-
调整:更新模型参数,让它更倾向于选择能获得高奖励的行动
这个过程会重复成千上万次,模型逐渐学会:在什么情况下应该调用什么工具、如何解读工具返回的结果、什么时候应该继续尝试、什么时候应该换个方向。
为什么 RL 对 Agentic 能力至关重要
传统的 LLM 微调(比如监督学习)需要人类标注「正确答案」。但 Agent 任务的特点是:
-
路径多样:完成同一个编程任务可能有无数种合理的步骤组合
-
结果导向:我们真正关心的是最终结果,而不是中间每一步是否「标准」
-
需要探索:有时候必须尝试几种方法才能找到可行的路径
RL 天然适合这种场景。它不要求你定义「正确的步骤序列」,只需要定义「什么算成功」。模型通过自己的探索,学会找到通往成功的路径。
实际训练中的应用
现代 Coding Agent 背后的模型通常会经历这样的 RL 训练:
-
在模拟的编程环境中执行任务(创建文件、运行代码、修复 bug)
-
用测试通过率、代码质量评分等作为奖励信号
-
学习何时应该读取更多文件来获取上下文,何时应该直接动手修改
-
学习如何从错误信息中提取有用线索,调整下一步策略
值得注意的是,RL 训练的效果高度依赖奖励信号的设计。如果只奖励「任务完成」,模型可能学会走捷径(Reward Hacking);如果奖励信号太复杂,训练可能不稳定。这也是为什么不同模型在 Agent 任务上的表现差异很大,背后的 RL 训练策略可能完全不同。
对使用者的启示
理解了 RL 的作用,你会更清楚为什么某些使用方式更有效:
-
提供清晰的成功标准:当你告诉 Agent 「修好这个 bug,运行 npm test 应该全部通过」,你实际上是在给它一个明确的「奖励信号」,这与它训练时的模式一致
-
允许试错:Agent 在训练中学会了通过尝试来解决问题,给它多次尝试的机会往往比期待一次成功更实际
-
观察它的决策模式:当 Agent 做出某个决定(比如先读文件而不是直接修改),这往往反映了它在 RL 训练中学到的策略

Coding Agent 的实现原理
理解了 LLM 的本质和限制后,我们来看 Coding Agent 是如何在这些约束下被设计出来的。
LLM API 的核心结构
现代 LLM API 采用基于消息的对话结构。理解这个结构是构建 Agent 的基础。
Messages 数组
API 的核心是一个消息数组,每条消息包含角色(role)和内容(content):
{"messages": [{"role": "system","content": "你是一个专业的编程助手..."},{"role": "user","content": "帮我写一个快速排序函数"},{"role": "assistant","content": "好的,这是一个 Python 实现的快速排序..."}]}
三种角色各有用途:
-
system:设定 Agent 的行为准则、能力范围和工具定义
-
user:用户的输入
-
assistant:模型的回复
Tool Calling 机制
Coding Agent 的核心能力来自工具调用。API 允许你定义工具的 schema,模型会在需要时生成结构化的工具调用请求:
{"tools": [{"name": "read_file","description": "读取指定路径的文件内容","parameters": {"type": "object","properties": {"path": {"type": "string","description": "文件路径"}},"required": ["path"]}}]}
当模型决定调用工具时,它的响应会包含工具调用信息:
{"role": "assistant","content": null,"tool_calls": [{"id": "call_abc123","name": "read_file","arguments": "{\"path\": \"src/main.py\"}"}]}
工具执行结果作为新消息返回给模型:
{"role": "tool","tool_call_id": "call_abc123","content": "def main():\n print('Hello, World!')\n..."}
Reasoning Content 的保留
对于具有显式推理能力的模型,API 响应中可能包含 reasoning 或 thinking 字段,记录模型的思考过程:
{"role": "assistant","reasoning_content": "用户需要读取文件来理解项目结构。我应该先查看 src 目录下的主要文件...","content": "让我先看一下项目的主文件。","tool_calls": [...]}
在多轮对话中保留这些推理内容,可以帮助模型维持思维的连贯性。模型可以「回顾」自己之前的思考过程,从而做出更一致的决策。
Prompt Caching:工程实践的关键
由于 LLM 的自回归特性,每次请求都需要重新处理整个上下文。对于动辄几万 token 的 Coding Agent 对话来说,这意味着大量的重复计算和延迟。
Prompt Caching 通过缓存上下文前缀的计算结果来解决这个问题。关键在于:缓存基于前缀匹配。只有当新请求的开头部分与之前的请求完全一致时,才能命中缓存。
这直接影响了 prompt 的组织方式:
┌─────────────────────────────┐│ System Prompt │ ← 稳定不变,可缓存│ (角色定义、行为准则) │├─────────────────────────────┤│ Tool Definitions │ ← 稳定不变,可缓存│ (工具的 schema 定义) │├─────────────────────────────┤│ Project Context │ ← 相对稳定,尽量少变│ (项目说明、代码规范) │├─────────────────────────────┤│ Conversation History │ ← 动态增长│ (对话历史) │├─────────────────────────────┤│ Current User Message │ ← 每次都变│ (当前用户输入) │└─────────────────────────────┘
最佳实践参考:
-
把稳定内容放在前面:system prompt 和工具定义应该保持稳定,避免频繁修改
-
动态内容放在后面:对话历史和当前输入放在最后
-
避免在稳定前缀中插入可变内容:比如不要在 system prompt 中插入当前时间戳
Agent Loop:核心循环
语言模型可以回答问题,而 Agent 可以做事情。Agent Loop 正是实现这一差异的关键。
当模型收到一个无法仅凭训练知识完成的请求时,它需要与外部世界交互:读取文件、查询数据库、执行代码。Agent Loop 就是管理这个「推理-行动」循环的编排层,使模型能够处理需要多个步骤、外部信息或产生实际影响的任务。
循环的基本原理
Agent Loop 的运作遵循一个简单的原则:调用模型 → 检查是否需要使用工具 → 如果需要则执行工具 → 将结果返回给模型再次调用 → 重复直到模型产生最终响应。
这个循环的关键在于上下文的累积。每次迭代都会向对话历史中添加新内容。模型不仅能看到原始请求,还能看到它调用过的每个工具以及收到的每个结果。这种累积的上下文使得复杂的多步骤推理成为可能。
一个具体的例子
假设用户请求:「帮我修复项目中的这个 bug:用户登录后会话没有正确保存」。
这不是模型仅凭知识就能完成的任务,它需要通过 Agent Loop 逐步探索:
-
第一轮:模型收到请求,首先需要了解项目结构。它调用 list_directory 工具查看项目根目录。
-
第二轮:模型看到了目录结构,识别出 src/auth/ 目录可能与登录相关。它调用 read_file 查看 src/auth/login.js。
-
第三轮:模型看到了登录代码,发现它调用了 sessionManager.save()。为了追踪问题,它调用 read_file 查看 src/session/manager.js。
-
第四轮:模型发现了问题,save() 方法中有一个异步操作没有被正确 await。它调用 edit_file 工具修复这个 bug。
-
第五轮:修复完成,模型调用 shell 工具运行测试来验证修复是否有效。
-
第六轮:测试通过。模型不再请求工具,而是生成最终响应:总结问题原因、修复内容和验证结果。
每一轮都遵循相同的模式:模型接收上下文,决定是继续行动还是给出响应,要么继续循环,要么退出。关键在于,模型基于对任务不断演进的理解自主做出这些决策。
循环的终止条件
每次模型调用都会返回一个停止原因(stop reason),决定接下来发生什么:
-
end_turn:模型完成了响应,没有进一步的行动需要执行。这是正常的成功终止,循环退出并返回最终消息。
-
tool_use:模型想要执行一个或多个工具后再继续。循环执行请求的工具,将结果追加到对话历史,然后再次调用模型。
-
max_tokens:模型的响应因达到 token 限制而被截断。这在当前循环中无法恢复,循环以错误终止。
理解这些终止条件有助于预测 Agent 的行为并处理边界情况。
Coding Agent 的典型工具
一个功能完整的 Coding Agent 通常需要以下几类工具:
文件操作
-
read_file:读取文件内容
-
write_file:创建或覆盖文件
-
edit_file:对文件进行局部编辑(而非完全重写)
代码执行
-
shell / terminal:执行命令行命令,用于运行代码、安装依赖、执行测试等
代码搜索
-
grep / search:在代码库中搜索文本或模式
-
semantic_search:基于语义的代码搜索
项目导航
-
list_directory:列出目录内容
-
find_files:根据模式查找文件
这些工具的设计直接影响 Agent 的能力边界,工具的粒度、参数设计、返回格式都需要仔细考量。

常见问题与解决方案
在了解了 LLM 和 Coding Agent 的基本原理后,我们再来看看一些常见的问题就能更好地理解了。
会话间失忆
Coding Agent 面临的一个根本性问题是:它们在会话之间没有持久记忆。
每次启动新会话时,Agent 只知道它能在磁盘上找到的内容。就像电影《记忆碎片》或《初恋 50 次》中的主角一样,Agent 每天醒来都不记得昨天发生了什么。而典型的工程工作流往往需要跨越多个会话才能完成一个功能——因为需要测试、代码审查和后续清理。
这导致了一个荒谬的局面:你需要在每个新会话开始时,重新向 Agent 解释项目背景、当前进度和接下来的计划。
💡 解决方案:
-
使用结构化的任务追踪系统(如 issue tracker)
-
在每个会话结束时,让 Agent 生成一个状态摘要,供下次会话使用
-
将重要决策和发现记录在固定位置,而不是对话历史中
上下文窗口耗尽
每轮循环都会向对话历史添加消息。对于需要多次工具调用的复杂任务,历史记录可能超出模型的上下文窗口。当这种情况发生时,Agent 无法继续。
具体表现包括:模型提供商返回输入长度错误,或随着上下文填满不太相关的早期消息,模型性能明显下降。
目前有两种主流的上下文管理策略:
💡 Observation Masking(观察遮蔽)
这种方法只针对工具返回的观察结果进行处理,而完整保留 Agent 的推理和行动历史。具体做法是:用占位符替换较早轮次的观察内容(比如 「内容已省略」),只保留最近 N 轮的完整输出。
这种方法简单高效,因为典型 Coding Agent 的每轮交互中,工具输出(如文件内容、命令执行结果)往往占据了绝大部分 token。通过遮蔽旧的观察内容,Agent 仍然可以访问自己过去的推理和决策,但不再重复处理早期轮次的冗长文本。
💡 LLM Summarization(LLM 摘要)
这种方法使用另一个 LLM 将较早的交互历史(包括观察、行动和推理)压缩成简短的摘要。摘要会替代原始的详细历史,而最近的几轮对话保持完整。
这种方法理论上可以支持无限长的对话,因为历史会被反复压缩。但它也有代价:每次生成摘要都需要额外的 API 调用,而且摘要可能会丢失某些细节信息。研究发现,摘要有时会掩盖 Agent 应该停止尝试的信号,导致 Agent 运行更多轮次,反而增加了成本。
两种方法各有优劣:Observation Masking 实现简单、成本低,但上下文仍会缓慢增长;LLM Summarization 可以更彻底地压缩历史,但引入了额外开销和信息损失风险。实践中,可以考虑混合使用,以 Observation Masking 作为主要策略,仅在上下文确实过长时触发 LLM Summarization。
其他解决方案:
-
减少工具输出的冗长程度,返回摘要或相关片段,而非完整数据
-
简化工具 schema,深度嵌套的结构会在工具配置和模型推理中消耗大量 token
-
将大型任务分解为子任务,每个子任务使用新的上下文
有效上下文远小于标称值
即使拥有 1M token 的上下文窗口,Coding Agent 实际上只能有效利用其中的 10-15%。超过 20% 后,成本和性能都会急剧恶化。大多数 Agent 会在达到约 20% 时强制中断,而最佳实践是在 15% 之前就重启会话。
这意味着,以全速工作时,你大约只有 5-10 分钟的有效工作时间,然后 Agent 就会「耗尽上下文」,需要重启(相当于死亡)或进行 compaction(相当于记忆清除)。
有一个生动的比喻:上下文窗口就像潜水员的氧气罐。所有人都说「我们给他一个更大的氧气罐:100 万 token!」但他最终还是会耗尽氧气,更大的窗口并不能解决根本问题。
「Dumb Zone」:中间区域的性能退化
研究发现,上下文窗口的中间 40-60% 区域存在一个「Dumb Zone」,在这个区域,模型的召回率下降,推理能力变差。这与前面提到的「Lost in the Middle」现象相呼应。
当 Agent 深入工作时,它会逐渐表现出类似「痴呆」的症状:迷失方向、产生幻觉接口、忘记原始目标。这不是因为模型变笨了,而是因为关键信息被淹没在大量的上下文中,无法被有效利用。
新发现的任务被丢弃
这是 Agentic Coding 中一个容易被忽视但影响巨大的问题:LLM 在工作过程中会注意到各种问题,但在上下文空间紧张时,会选择忽略这些发现,不采取任何行动。
例如,Agent 在修复一个 bug 时,可能会注意到代码中的另一个潜在问题、一个需要重构的地方、或一个缺失的测试用例。但如果当前上下文已经很满,Agent 可能会「假装没看到」,继续专注于手头的任务。这些被发现但被丢弃的工作,就这样悄悄消失了。
💡 解决方案:
-
使用外部工具让 Agent 随时记录发现的问题
-
在任务结束时,要求 Agent 列出所有观察到但未处理的问题
-
建立「发现即记录」的工作流程
过早宣告完成
当 Agent 经历多次 compaction 后,可能会出现一个荒谬的情况:它会自信地宣布「恭喜,任务完成了!让我们开始手动测试吧!」而实际上还有大量的阶段没有完成。
解决方案:
-
将整体计划保存在上下文之外的固定位置
-
定期让 Agent 对照原始计划检查进度
-
使用结构化的任务追踪,而不是依赖 Agent 的记忆
工具选择不当
当模型持续选择错误的工具时,问题通常出在工具描述的模糊性上。从模型的角度审视描述:如果两个工具的描述有重叠,模型就没有选择的依据,我们应该确保每个工具的用途清晰且互不重叠。

通过上述的探讨,我们已经完成了从 LLM 本质到 Agentic Coding 原理的理论铺垫:从逐个预测 token 的底层逻辑,到注意力机制的上下文约束,再到强化学习如何让模型学会「做事」;从 API 结构的基础框架,到 Agent Loop 的核心循环,最后到解决上下文耗尽、会话失忆等工程问题的实践策略。
这些内容共同构成了 Agentic Coding 的第一性原理基石——理解这些,你就能跳出"黑盒工具"的使用层面,看到各种最佳实践背后的深层逻辑。
接下来,我们将从理论走向实操:结合具体案例拆解如何设计高效的 Agent 对话流程、如何选择合适的工具集、如何优化上下文管理策略,以及如何将这些原理应用到实际的编码任务中(如 bug 修复、功能开发、代码重构)。你将看到,当我们用第一性原理的视角重新审视 Agentic Coding 时,那些看似零散的技巧会形成一套系统的方法论,帮助你真正成为 AI 编程助手的「驾驭者」而非「使用者」。
了解了 LLM 的特性、Coding Agent 的实现原理以及常见问题后,我们总结出了一些最佳实践,帮助大家更高效地使用这些工具。

短对话优于长对话
这可能是最重要的一条实践:保持对话简短、专注,每个对话只做一件事。
很多人认为更大的上下文窗口意味着更强的能力,可以把更多任务塞进一个对话里。但实际情况恰恰相反,最好的对话是短对话,它们只做一件事,并且只包含完成这件事所需的上下文。
为什么短对话更好?
当你往上下文里塞太多内容时,Agent 的表现就像喝醉了一样:它会开始犯错、跌跌撞撞、甚至开始和你争论,如果你继续喂它更多 token,它甚至会「吐」得你一身(产生大量无意义的输出)或者进入死循环。
对话越长,上下文窗口里就会积累越多与当前任务不太相关的内容。为了让 Agent 发挥最佳工况,你需要给它完成当前工作所需的上下文。
长对话不仅效果差,还更贵。每次发送消息时,整个上下文都会被发送给模型提供商。这意味着对话越长,新消息的成本就会指数级增长。而且长对话更容易因为消息间隔时间长而错过缓存窗口,导致费用飙升。所以,长对话既效果差,又花费高,尤其在以 tokens 消耗计费的套餐下面。
拆分对话,本质上是拆分任务,大任务应该被分解为小任务。这在 Agent 时代之前就是软件工程的最佳实践,现在看也依然如此,短对话让任务拆分这件事变得自然甚至有趣。就像小任务更容易管理一样,小对话也更容易追踪:每个对话都有明确的目标,你可以轻松掌握整体进度。

以对话为单位组织工作
如果把对话视为任务的基本单位,那么一个功能或 bug 修复就自然变成了一组相互关联的对话。
一个典型的工作流程
假设你要实现一个新功能,可以这样组织对话:
功能:用户登录后的会话管理[对话 1] 调研现有代码结构├── 了解 auth 模块的实现├── 查看 session 管理的现状└── 输出:关键文件列表和当前架构理解[对话 2] 实现基础功能├── 参考对话 1 的发现├── 实现核心的 session 保存逻辑└── 输出:基础实现代码[对话 3] 添加错误处理├── 参考对话 2 的实现└── 增加边界情况处理[对话 4] 编写测试├── 参考对话 2、3 的实现└── 添加单元测试和集成测试[对话 5] 代码审查├── 检查实现是否符合项目规范└── 确认没有引入安全问题[对话 6] 清理和重构└── 根据审查结果进行调整
每个对话都很短,只专注于一件事。它们加在一起,完成了整个功能的开发。
那对话之间如何共享上下文?
当你开始一个新对话时,可以通过以下方式传递必要的上下文:
-
引用之前对话的结论:在新对话开头简要说明之前的发现或决策
-
利用 Git 状态:让 Agent 查看 git diff 或检查最近的提交
-
使用项目文档:将重要决策记录在 AGENTS.md 或类似文件中,Agent 每次都能读取
-
直接提及相关文件:在新对话中 #mention 需要的文件
关键是:不要试图在一个对话里完成所有事情。每当你发现当前任务已经完成,或者对话开始变得混乱,就应该开始一个新对话。

编写有效的项目配置文件
大多数 Coding Agent 都支持在项目根目录放置配置文件(比如 Rules 或者 Agent.md),这个文件会自动注入到每一个对话中,这意味着它是你影响 Agent 行为的杠杆支点,但这把双刃剑也很容易用错。
理解 Agent 的无状态本质
LLM 是无状态函数。它的权重在推理时是冻结的,不会随着使用而学习,Agent 对你代码库的全部了解,完全来自于你放进上下文窗口的 token,这有三个重要含义:
1. 每次新对话开始时,Agent 对你的代码库一无所知
2. 任何重要的项目信息都需要在每次会话中告诉它
3. 项目配置文件是实现这一点的首选方式
因此,你应该把这个文件视为每次会话的入职培训文档。
配置文件应该包含什么
一个好的项目配置文件应该回答三个问题:
-
WHAT(是什么):技术栈、项目结构、各模块的职责。这在 monorepo 中尤其重要,应该告诉 Agent 有哪些应用、哪些共享模块、每个部分是做什么的
-
WHY(为什么):项目的目的、设计决策的背景。为什么选择这个架构?为什么有些代码看起来不合理(比如历史债务)?
-
HOW(怎么做):如何运行项目、如何测试、如何验证改动。用 bun 还是 npm?测试命令是什么?
少即是多
这是最容易犯的错误:试图把所有可能需要的信息都塞进配置文件。
研究表明,前沿的思考模型大约能可靠地遵循 150-200 条指令,而 Coding Agent 的系统提示本身可能已经包含了约 50 条指令。这意味着你的配置文件应该尽可能精简,理想情况下只包含那些对所有任务都普遍适用的内容。
# 不好的做法:塞满各种可能用到的信息## 数据库 Schema 设计规范(500 行详细规范...)## API 设计指南(300 行规范...)## 代码风格指南(200 行规范...)# 好的做法:简洁 + 指向详细文档## 项目概述这是一个 Next.js 电商平台,使用 PostgreSQL + Prisma。## 关键目录- `apps/web`: 前端应用- `apps/api`: 后端服务- `packages/shared`: 共享类型和工具## 开发命令- `bun dev`: 启动开发服务器- `bun test`: 运行测试- `bun typecheck`: 类型检查## 详细文档根据任务需要,查阅以下文档:- 数据库设计:`docs/database-schema.md`- API 规范:`docs/api-guidelines.md`- 代码风格:`docs/code-style.md`
一个经验法则:配置文件应该控制在 300 行以内,越短越好。有些团队的配置文件甚至不到 60 行。
渐进式披露
与其在配置文件中塞满所有信息,不如使用渐进式披露策略:
agent_docs/├── building_the_project.md├── running_tests.md├── code_conventions.md├── service_architecture.md└── database_schema.md
在配置文件中列出这些文档并简要描述,让 Agent 根据当前任务决定读取哪些。这样,只有相关的信息才会进入上下文,避免不必要的干扰。
偏好指针而非副本:不要在文档中复制代码片段——它们很快会过时。使用 file:line 引用指向代码的权威位置。
不要让 Agent 做 Linter 的工作
很多人喜欢在配置文件中写详细的代码风格指南,这不是个好的实践。
LLM 做格式检查既慢又昂贵,更重要的是,这些指南会增加指令数量,降低 Agent 对其他指令的遵循能力。
更好的做法:
-
使用真正的 linter 和 formatter(如 ESLint、Prettier、Biome)
-
配置自动修复,让工具处理格式问题
-
如果 Agent 产生了格式错误,让 linter 在后处理阶段修复它
LLM 是上下文学习者。如果你的代码库遵循一致的风格,Agent 通常会自动模仿这种风格,不需要你明确告诉它。
这是最高杠杆点,要认真对待
一行糟糕的代码就是一行糟糕的代码,一个糟糕的技术方案可能产生很多行糟糕的代码,而配置文件中的一行糟糕的指令会影响每一个会话、每一个任务、每一个产出。
杠杆效应:配置文件 → 影响每个会话的行为↓研究/规划阶段 → 影响实现计划的质量↓实现阶段 → 影响最终代码的质量
花时间仔细考虑配置文件的每一行,这是你能做的 ROI 最高的投资之一。

200K Token 足够了
当大家都在追求更大的上下文窗口时,一个反直觉的事实是:200K token 对于大多数任务来说已经绰绰有余了。
关键不在于你有多大的上下文窗口,而在于你如何使用它。一个 200K 的窗口,如果你用短对话的方式工作,可以支持你完成非常复杂的功能。因为虽然每个对话只有几十 K 到上百 K 个 token,但你可以开启 10 个、20 个甚至更多对话,它们加起来的总量远超任何单一上下文窗口。而且,由于每个对话都是从相对干净的状态开始,Agent 的表现会一直保持在最佳水平,而不是随着上下文膨胀而逐渐退化。
实践建议
-
当对话超过 80K-100K token 时,考虑开始新对话
-
完成一个独立的子任务后,开始新对话处理下一个任务
-
如果 Agent 开始表现出「醉酒」症状(重复、遗忘、偏离目标),立即开始新对话
-
把「开始新对话」视为正常工作流程的一部分,而不是「失败后的重试」

Compounding Engineering:让系统自我改进
传统的 AI 编程是关于短期收益的:你给 prompt,它写代码,然后发布,然后从头开始。Every.to 提出的 Compounding Engineering(复利工程) 则是关于构建具有记忆的系统:每个 PR 都在教育系统,每个 bug 都成为永久的教训,每次代码审查都在更新 Agent 的默认行为。普通 AI 工程让你今天更高效,Compounding Engineering 让你之后的每一天都更高效。
核心理念:你不只是在解决问题,而是在教育系统
当你使用 Coding Agent 时,问自己一个问题:我是在解决今天的问题,还是在教系统?
-
每次修复 bug 时,如果不能防止同类问题再次发生,就只完成了一半
-
每次代码审查如果不能提取出可复用的教训,就是浪费时间
-
每次成功的工作流程如果不能被记录和复用,就会随着会话结束而消失
如何实践 Compounding Engineering
1. 将经验沉淀到项目文档
大多数 Coding Agent 都支持读取项目根目录下的特定文件,这是你指导系统的主要途径:
# AGENTS.md## 代码风格- 使用 async/await 而非 Promise.then()- 错误处理必须包含具体的错误类型- 变量命名遵循 PR #234 确立的模式## 已知陷阱- session 模块的 save() 方法是异步的,必须 await- 不要在循环中调用 API,使用批量接口## 成功模式- 新增 API 端点时,参考 PR #241 的错误处理方式- 测试覆盖率要求参考 PR #219 的反馈
每次你发现一个重复出现的问题或一个有效的解决方案,就把它加入这个文件。Agent 在每次对话开始时都会读取它,自动应用这些经验。
2. 让 bug 修复产生长期价值
当你修复一个 bug 时,不要只是改代码。问自己:
-
这类问题能否通过添加 lint 规则来预防?
-
是否应该在 Rules 或者 AGENTS.md 中记录这个陷阱?
-
能否编写一个测试来防止回归?
-
代码审查清单是否需要更新?
一个真正的 bug 修复应该让同类问题再也不会发生。
3. 从代码审查中提取模式
每次你在审查中指出问题或提出建议,可以考虑:
-
这个反馈是否适用于未来的类似代码?
-
是否应该成为项目的编码规范?
-
Agent 能否在下次自动应用这个改进?
如果答案是肯定的,就把它记录下来。让你的审查意见成为系统的永久知识,而不是一次性的对话。
4. 建立可复用的工作流程
当你找到一个有效的工作模式时,把它进行沉淀:
## 工作流程:添加新的 API endpoint1. 先编写接口测试(参考 tests/api/example.test.ts)2. 实现端点,遵循 src/api/users.ts 的模式3. 添加错误处理,使用 AppError 类4. 更新 API 文档5. 运行完整测试套件验证
下次你或 Agent 需要做类似的任务时,可以直接说「按照添加新 API endpoint 的工作流程来做」,系统已经知道该怎么做了。
复利效应
Compounding Engineering 的魔力在于累积效应。第一周,你可能只是记录了几条编码规范。第一个月,你有了一套完整的项目知识库。三个月后,Agent 开始自动应用你从未明确告诉它的模式,因为它从之前的 PR、bug 修复和代码审查中学习了这些。
想象一下:你打开一个 PR,发现 Agent 的评论是「根据 PR #234 的模式修改了变量命名,按照 PR #219 的反馈移除了过度测试,添加了与 PR #241 类似的错误处理」。它学会了你的品味,就像一个聪明的同事,而且还有记录可查。这就是复利,每次修复、每次审查、每次教训都在为未来投资。

对人难的事,对 AI 也难
有一个简单但常被忽视的事实:如果一个任务对人类开发者来说很难,那么它对当前的 AI 来说大概率也很难。
这听起来显而易见,但它的推论却很深远:所有那些能提升人类开发者体验的工作,对 AI 同样有价值。更好的文档、更清晰的架构、更快的反馈循环,这些「老生常谈」的工程实践,在 AI 时代不仅没有过时,反而变得更加重要。
为什么 AI 面临和人类相似的挑战?
回想一下 LLM 的工作原理:它通过阅读上下文来理解任务,然后生成响应。这个过程和人类开发者阅读代码、理解需求、编写解决方案的过程惊人地相似。
-
当文档缺失或过时时,人类需要花大量时间阅读源码猜测意图。AI 也一样,它会在代码库中反复搜索,消耗大量上下文空间,最终可能还是理解错误。
-
当架构混乱、模块边界不清时,人类很难知道该改哪里。AI 也会迷失,它可能改了错误的文件,或者遗漏了需要同步修改的地方。
-
当测试运行缓慢时,人类倾向于跳过测试。AI 也面临同样的压力,长时间的等待会消耗对话的「耐心」和上下文空间。
值得投资的开发者体验
既然 AI 和人类面临相似的挑战,那么以下这些传统的「开发者体验」优化就具有了双重价值:
更好的文档
# 好的文档对 AI 的价值## 之前(无文档)Agent 需要:1. 读取 5-10 个相关文件2. 猜测模块的职责和边界3. 推断 API 的使用方式4. 可能还会猜错消耗:大量上下文 + 高错误率## 之后(有文档)Agent 只需要:1. 读取 README 或 API 文档2. 直接了解正确的使用方式消耗:少量上下文 + 高准确率
好的文档不仅帮助新人上手,也帮助 AI 快速建立正确的心智模型,比如:
-
架构决策记录(ADR):解释「为什么这样设计」,避免 AI 做出违背设计意图的修改
-
API 使用示例:比纯粹的类型定义更有效
-
已知陷阱和常见错误:直接告诉 AI 什么不该做
更清晰的代码结构
当你在纠结要不要花时间重构一个混乱的模块时,考虑一下:这个混乱不仅困扰你,也会困扰每一个试图理解它的 AI。
-
清晰的命名:processUserData() 比 doStuff() 对 AI 的帮助和对人类一样大
-
单一职责:一个做一件事的函数,比一个做十件事的函数更容易被正确修改
-
显式依赖:依赖注入比全局变量更容易被 AI 理解和测试
更快的反馈循环
这可能是最容易被低估的一点。Agent Loop 的每一轮都需要等待工具执行完成,如果:
-
测试套件需要 10 分钟才能跑完 → Agent 要么跳过测试,要么在等待中浪费大量上下文
-
构建需要 5 分钟 → 每次小改动的验证成本都很高
-
部署需要 30 分钟 → 几乎不可能让 AI 做端到端的验证
相反,如果你有:
-
秒级的单元测试 → Agent 可以频繁验证,快速迭代
-
快速的增量构建 → 改动能立即得到反馈
-
本地可运行的环境 → 不需要等待远程部署
具体的改进建议
1. 为 AI 优化你的测试
# 不好:运行所有测试需要 10 分钟npm test# 好:可以只运行相关测试,几秒完成npm test -- --grep "session"npm test -- src/auth/__tests__/
确保 Agent 知道如何运行局部测试,而不是每次都跑完整套件。
2. 提供快速的健康检查
# 创建一个快速验证脚本# scripts/quick-check.sh#!/bin/bashecho "Type checking..."npm run typecheckecho "Linting changed files..."npm run lint -- --changedecho "Running related tests..."npm test -- --related
让 Agent 可以在几秒内验证基本的正确性。
3. 文档放在代码旁边
src/auth/README.md # 这个模块是做什么的login.tslogin.test.tssession/README.md # session 管理的设计决策manager.ts
当 AI 浏览目录时,它能立即看到相关文档,而不需要去别的地方找。
4. 让错误信息更有帮助
// 不好thrownew Error("Invalid input");// 好thrownew Error(`Invalid session token: expected format 'sess_xxx', got '${token}'. ` +`See docs/auth.md for token format specification.`);
好的错误信息帮助 AI(和人类)快速定位问题,而不是盲目搜索。
反过来未必成立,有时需要专门为 AI 设计
需要注意的是,反过来的推论并不总是成立:对人来说简单的事,对 AI 未必简单,例如:
-
人类可以轻松地「看一眼」就理解一个 UI 的布局问题,但 AI 需要解析整个 DOM 结构
-
人类可以凭直觉判断「这个改动风险很高」,但 AI 缺乏这种隐性知识
-
人类可以在飞书里随口问一句就获得关键信息,但 AI 只能依赖文档化的知识
更有趣的是,有时候你需要专门为 AI 设计工具和接口,即使这对人类来说可能不是最自然的方式。
LLM 需要专门的信息架构
用户体验领域有一个概念叫「信息架构(Information Architecture)」,它关注的是如何组织和呈现信息,以提供最佳的用户体验,好的信息架构你很少会注意到,但糟糕的信息架构会让你抓狂。当我们观察 Agent 使用现有命令行工具时的困惑和迷失,这强烈表明:我们现有工具的信息架构对 LLM 来说是不够的。
LLM 是在我们现有的 CLI 工具上训练的,所以它们知道如何使用这些工具。但这些工具是为人类设计的,它们的输出格式、错误信息、交互方式都假设用户是人类。我们需要为 Agent 增强这些工具,提供对 LLM 更有用的上下文,甚至调整输出格式以便 Agent 更好地消费。
API 设计:在信息量和上下文消耗之间取得平衡
当你为 Agent 设计工具接口(比如 MCP 工具)时,需要在两个目标之间取得平衡:
-
提供足够的信息:减少 Agent 需要的工具调用次数
-
避免填满上下文:不要返回过多无关信息
一个好的实践是:提供便捷函数和底层函数两套 API,并通过工具描述引导 Agent 优先使用便捷函数。
def get_global_variable_at(address: str) -> dict:"""Get the value of a global variable at the specified address.Automatically identifies the type and returns the best stringrepresentation.This is the preferred method for reading global variables."""# 智能的、高层的实现...def data_read_byte(address: str) -> int:"""Read the 1 byte value at the specified address.Only use this function if `get_global_variable_at` failed."""# 底层的、更通用的实现...
通过在 docstring 中明确指出「只有在 get_global_variable_at 失败时才使用这个函数」,你可以引导 Agent 优先使用更智能的 API,减少不必要的工具调用。
为 AI 设计友好的命令行输出
如果你观察 Agent 的工作方式,会发现它经常使用类似 head -n100 的方式来限制输出。这看起来是在节省 token,但实际上引入了新问题:Agent 不知道还剩多少行没看到,如果需要完整信息就必须重新运行命令,而重新构建项目是非常耗时的。
一个更好的设计是:让工具主动告诉 Agent 还有多少内容被截断了,甚至缓存输出以便后续获取。
另一个常见问题是 Agent 在错误的目录中执行命令,它会反复尝试,在不同目录之间跳来跳去,浪费大量 token。一个简单的 shell hook 可以帮助它快速定位:
# 在 .zshrc 中添加command_not_found_handler() {echo "zsh: command not found: '$1'"echo "zsh: current directory is $PWD"return127}
现在当命令失败时,Agent 能立即知道自己在哪个目录:
$ npm run buildzsh: command not found: 'npm'zsh: current directory is /Users/ryanzsh: Perhaps you meant to run: cd project_directory; npm run build
很多命令行工具都提供了 --json 或 --porcelain 选项,在给 Agent 使用的工具中优先使用这些格式——人类喜欢格式化的输出,但 AI 更擅长解析结构化数据。
用工程约束来「驯服」Agent
Agent 有时会试图走捷径,绕过你设定的规则。与其在 prompt 中反复强调「不要跳过测试」,不如用工程手段来强制执行。
借助 linters、formatters 和 git hooks
让 Agent 频繁提交代码是个好习惯(在Rules 或者 Agent.md 中告诉它),但它往往会忽视「确保构建不失败」和「修复失败的测试」这样的指令。一个 .git/hooks/pre-commit 脚本可以强制执行项目标准:
# .git/hooks/pre-commitecho "Running type check..."npm run typecheck || exit1echo "Running linter..."npm run lint || exit1echo "Running tests..."npm test || exit1echo "All checks passed!"
这样,无论 Agent 多么想跳过验证,它都必须通过所有检查才能提交。
拦截 Agent 的「偷懒」行为
Agent 有时很「聪明」,当它发现测试一直失败时,可能会进入这样的循环:
1. 修改代码
2. 构建:通过
3. 运行测试:失败
4. 尝试修复测试
5. 修复失败
6. 说「这个测试之前就是失败的,我用 --no-verify 提交」
然后它就绕过了所有检查!(RL 训练中的 Reword Hacking)
解决方案是用一个 git 命令 wrapper 脚本拦截这种行为:
$ git commit --no-verify------------------------------------------------------------------❌ ERROR: Commit Rejected.------------------------------------------------------------------🤖 GUIDANCE FOR THE AI AGENT:You have attempted to bypass the required pre-commit verification.All code must pass quality checks before it can be committed.DO NOT BYPASS THE CHECKS. YOU MUST FIX THE UNDERLYING ERRORS.The pre-commit hook is likely failing. Diagnose and fix the issues.After all commands complete successfully, attempt the commit again*without* the '--no-verify' flag.
这个技巧的本质是:把对 Agent 的指导嵌入到工具的输出中,Agent 会读取命令执行的结果,所以错误信息本身就是最好的 prompt 注入点。
每当 Agent 发明新的「偷懒」方式,你就需要堵上这个漏洞。但总体来说,工程约束比 prompt 指令更可靠。
显式优于隐式
// 对人友好,对 AI 可能困难(隐含状态)client.connect()client.authenticate(user, password)client.query("SELECT * FROM users")// 对 AI 更友好(显式、无状态)const result = await db.query({connection: { host, port },auth: { user, password },sql: "SELECT * FROM users"})
有状态的 API 需要 AI 理解和跟踪隐含的状态变化,而无状态的、显式的 API 更容易被正确使用。
结构化的错误信息
Error: Something went wrong. Please try again later.Error [AUTH_TOKEN_EXPIRED]: Token expired at 2024-01-15T10:30:00Z.Call refreshToken() to obtain a new token. See: docs/auth.md#token-refresh
人类可以通过上下文推断「something went wrong」是什么意思,但 AI 需要明确的错误代码、原因和解决方案。
AI 眼中的「合理」可能和人类不同
这是一个更微妙的发现:AI 认为合理的代码结构和命名,可能和人类的直觉不一致。
Amp 团队分享过一个有意思的案例:他们让 AI 构建了一个 TUI 框架,过程中开发者一开始会干预 AI 的决策。比如,AI 给一个交换屏幕缓冲区的函数命名为 present(),开发者觉得这个名字不够直观,改成了 swapScreens()。
但随后他们发现了问题:Agent 在后续工作中反复尝试寻找一个叫 present() 的函数,找不到后报告「让我尝试其他方法」,最终才找到 swapScreens()。这浪费了 token,也浪费了时间。
为什么会这样?因为 Agent 的命名「直觉」来自训练数据的统计概率。present() 是 Flutter 等框架中双缓冲交换的常见命名,对于 Agent 来说是「最可能」的名字。当开发者用自己的命名覆盖它时,实际上是在对抗 Agent 的统计直觉。Agent 不能再问「过去的我会怎么命名这个」并从权重中找到答案——它必须记住人类的特殊习惯。
后来,开发者决定放手让 Agent 自己决定命名和代码结构。结果呢?Agent 在这个代码库上的工作效率大幅提升。
最终的代码可能看起来有些「奇怪」:
-
比代码库其他地方更多的 OOP 模式和类
-
开发者不会选择的命名约定
-
不太常见的泛型用法
-
文件布局和人类习惯不同
但 Agent 在这个自己构建的框架中如鱼得水:它知道如何添加滚动条,知道动画系统如何工作,知道键盘快捷键的处理方式——尽管这个框架没有任何文档,甚至无法完整放入一个上下文窗口。
这是一个「由 Agent 构建、为 Agent 优化」的代码库。在这里,东西放在 Agent 的「直觉」认为它们应该在的地方,命名符合 Agent 的统计预期,语法和概念在「统计上最可能」和「实际能编译」之间取得平衡。
启示与权衡
这给我们的启示是:
-
不要过度干预:如果你频繁地因为「我觉得这个名字更好」而覆盖 Agent 的决策,可能反而在降低效率
-
注意「找不到」的信号:如果 Agent 反复在某个地方「找不到」东西,考虑是否是你的命名和它的预期不一致
-
拥抱常见模式:使用广泛使用的设计模式和命名约定,AI 的训练数据中更可能包含这些
-
模块级的风格隔离:在某些由 Agent 主导开发的模块中,可以考虑让 Agent 保持它自己的风格
当然,这需要权衡。人类仍然需要阅读和维护代码,完全「AI 风格」的代码可能会让人类开发者困惑。一个务实的做法是:
1. 把「只存在于人脑中」的知识显式化:写下来,放进文档
2. 在 Agent 主导的模块中,给 Agent 更多自主权
3. 在人类频繁维护的核心模块中,保持人类友好的风格
4. 在工具接口上,提供 AI 友好的选项(如 --json 输出)
投资回报是双倍的
当你投资于更好的文档、更清晰的架构、更快的测试时,你获得的回报是双倍的:
1. 人类开发者(包括未来的你)会更高效
2. AI 助手也会更高效
这些投资不会因为 AI 的进步而贬值。相反,随着你越来越多地依赖 AI 来完成任务,这些基础设施的价值只会越来越高。
所以,下次当你犹豫要不要花时间写文档、重构代码、优化测试速度时,记住:你不只是在帮助人类,你也在帮助 AI。而在这个 AI 辅助编程越来越普遍的时代,这是一笔非常划算的投资。

刻意练习:像学乐器一样学习 AI
为什么有些人说「AI 对我不起作用」,而另一些人却能用 AI 完成大量的工作?
这个问题需要区分来看,如果你只在公司的大型私有代码库中使用过 AI,你的体验可能确实不好,那些代码库可能有古老的架构和专有模式,AI 的训练数据中根本没有这些,这是完全可以理解的。但问题是:你有没有在个人项目中尝试过 AI?你有没有进行刻意的、有意识的练习?
AI 就像一件乐器
以吉他为例,每个人都知道吉他是什么,也都知道如果投入刻意练习,就能变得擅长,但这需要时间、努力和实验。
AI 工具也是一样。那些从 AI 中获益最多的人,都投入了刻意练习。他们不会因为一次失败就下结论说「它给了我完全错误的答案」,然后假设这将是他们的常态体验。
他们会玩/Hack。
AI 工具也有这种潜力,它们的「正确用法」还在被发现中,那些愿意实验、愿意失败、愿意从失败中学习的人,会找到别人看不到的可能性。
如何进行刻意练习
1. 创造一个干净的实验环境
不要只在工作的复杂代码库中评估 AI 的能力,启动一个个人项目,一个没有历史包袱的新项目。在这里,AI 可以展示它真正的能力,你也可以专注于学习如何与它协作。
2. 从失败中提取教训
当 AI 给出错误的结果时,不要只是说「它不行」然后放弃。问自己:
-
我的 prompt 是否足够清晰?
-
我是否提供了足够的上下文?
-
我是否在一个对话里塞了太多任务?
-
这个错误是否揭示了 AI 的某个系统性弱点?
每次失败都是一次学习机会。把它记录下来,下次避免同样的陷阱。
3. 观察和模仿高手的实践
关注那些公开分享 AI 工作流程的开发者,观看他们的演示,阅读他们的文章,尝试复制他们的技巧。很多时候,差距不在于 AI 工具本身,而在于如何使用它。
4. 建立肌肉记忆
就像弹吉他需要建立手指的肌肉记忆一样,高效使用 AI 也需要建立某种「肌肉记忆」:
-
什么时候应该开始新对话?
-
如何组织一个复杂任务的 prompt?
-
遇到某类问题时,哪种工具组合最有效?
这些直觉只能通过大量练习获得。没有捷径。
5. 投入时间
最关键的是:你需要投入真正的时间。不是偶尔试一试,而是持续地、有意识地练习。就像学习任何乐器一样,每天练习 30 分钟,坚持几个月,效果会远超每周练习一次几个小时。

总结
AI 正在以惊人的速度发展。本文讨论的许多「限制」和「问题」:上下文窗口的约束、会话间的失忆、中间区域的性能退化等等,这些很可能在未来几年内被大幅改善甚至解决。每隔几个月,我们就会看到新的突破:更长的有效上下文、更好的长程推理、更可靠的工具使用。
但这并不意味着我们应该等待这一天的到来。恰恰相反,正是这个充满限制的阶段,给了我们工程师极大的探索和成长空间。那些现在就开始深入理解 LLM 工作原理、积极实践最佳方法、在限制中寻找创造性解决方案的人,将在 AI 能力进一步释放时获得最大的杠杆效应。
这是一个转型的窗口期。通过刻意练习,我们不仅能提升当下的生产力,更重要的是在构建自己的核心竞争力——理解这些工具的本质,知道何时信任它们、何时质疑它们,以及如何让它们发挥最大价值。
从第一性原理理解 LLM 的本质,理解它们如何「思考」、如何受到上下文的限制、如何在 Agent Loop 中发挥作用,这些知识不会随着具体工具的迭代而过时。
无论你使用的是哪个 Coding Agent,无论模型如何更新换代,这些基础原理都将帮助你更好地与 AI 协作。短对话优于长对话、刻意管理上下文、将经验沉淀为可复用的知识、为 AI 友好的工作环境投资,这些实践同样具有持久的价值。
AI 编程的未来会是什么样子,没有人能确切知道。但有一点是确定的:那些现在就开始认真学习、积极实践、深入理解的人,将最有能力塑造和适应这个未来。
去实验,去失败,去学习。像学习乐器一样学习 AI。
这个过程本身,就是价值所在。

