编程 Agent 是怎么工作的?

进阶原理新技术约 7 分钟读完

一句话回答

编程 Agent 本质上是一个带编程工具的 Agent 循环:用搜索和读文件工具理解代码库,用编辑工具修改代码(精确替换或应用补丁),用命令行运行测试、类型检查和构建,再根据报错迭代,直到验证通过。上下文有限,要靠项目说明文件、对话压缩、子任务隔离来管理;执行命令有风险,危险操作需要用户确认,最好在沙箱里运行。测试和类型检查提供客观的对错信号,是 Agent 能自己发现并修正错误的关键。

详细解析

工作循环

文本
用户需求
  ↓
理解:按文件名找文件、按内容搜索 → 读关键代码 → 读项目说明文件
  ↓
修改:精确替换代码片段 / 应用补丁
  ↓
验证:运行类型检查、测试、构建 ──失败──→ 读报错,回到"修改"
  ↓ 通过
总结改动,交给用户审查 diff

以 Claude Code、Cursor 这类工具为例,具体实现各不相同,但大体都是这个循环。

工具

类别 作用 设计要点
搜索 按文件名匹配,按内容搜索(类似 grep) 结果要截断,返回文件路径和行号
读取 读文件内容,支持按行范围读 大文件分段读,节省上下文
编辑 精确替换一段文本、应用补丁或写入新文件 要求先读后改,被替换的原文必须唯一
执行 运行测试、构建、git 等命令 输出要截断,设置超时,危险命令要确认
其他 查文档、读取编辑器的诊断信息等 按需提供

为什么不把整个仓库塞进上下文:仓库通常远大于上下文窗口,而且大部分代码和当前任务无关。按需搜索和读取,既能保证读到的是最新内容,又只占用必要的上下文。有的工具还会为代码库建立语义索引,用来回答"鉴权逻辑在哪"这类模糊的问题。

编辑方式的取舍

  • 精确替换:给出原文和新内容,原文必须在文件里只出现一次。改动集中,出错时会明确失败(没找到或匹配到多处),模型重新读取后再试即可
  • 补丁:一次描述多处修改,适合跨多个位置的改动,但补丁里的上下文行必须和文件一致
  • 整文件重写:实现最简单,但大文件很费 token,还可能不小心丢掉没改的部分

"先读后改"的约束,是为了防止模型凭记忆或想象去改一个已经变化的文件。

上下文管理

  • 项目说明文件:每次会话开始时自动加载,写明构建和测试命令、代码规范、目录结构、注意事项,例如 Claude Code 读取的 CLAUDE.md,以及很多工具支持的 AGENTS.md
  • 对话压缩:上下文快满时,把早期对话摘要成要点(做过的决定、改过的文件、剩余的待办),腾出空间
  • 子任务隔离:大范围的代码搜索交给子 Agent,在独立的上下文里完成,只返回结论
  • 控制输出:测试日志、构建输出往往很长,只保留失败的部分和关键报错

为什么测试和类型检查这么重要

模型写出的代码"看起来对",不等于真的对。测试、类型检查、lint 给出确定、可重复的反馈:报错指出具体的文件和行号,模型能据此定位修复;测试通过也是判断任务完成的客观标准。没有这些反馈,Agent 只能自己判断对错,常常在代码没跑通时就宣布完成。所以先让 Agent 补上测试,或者写一个能复现问题的测试,再动手修改,效果往往更好。

安全

  • 读文件、搜索这类只读操作可以自动执行;修改文件、执行命令按风险请用户确认,常用的安全命令(如运行测试)可以加入白名单
  • rm -rf、git push --force、数据库迁移、部署这类危险命令必须确认,最好在容器等沙箱里运行
  • 不要让 Agent 读到 .env、密钥文件;仓库里的 README、注释、issue 内容也可能藏着注入指令,见 什么是间接提示注入
  • 用 git 兜底:改动随时可以查看 diff、回退

代码示例

一个精确替换的编辑工具:

TypeScript
import fs from 'node:fs/promises'
import path from 'node:path'

const ROOT = path.resolve(process.cwd()) // 只允许修改项目目录内的文件

function resolveInWorkspace(file: string) {
  const full = path.resolve(ROOT, file)
  // 防止 ../ 越界(生产环境还要处理符号链接)
  if (full !== ROOT && !full.startsWith(ROOT + path.sep)) throw new Error('只能修改项目目录内的文件')
  return full
}

async function editFile({ file, oldString, newString }: { file: string; oldString: string; newString: string }) {
  if (!oldString) return '错误:oldString 不能为空'
  const full = resolveInWorkspace(file)
  const text = await fs.readFile(full, 'utf8')
  const count = text.split(oldString).length - 1
  if (count === 0) return '错误:没有找到 oldString。请重新读取文件,确认内容和缩进完全一致'
  if (count > 1) return `错误:oldString 出现了 ${count} 次,请带上更多上下文,让它只出现一次`
  // 用函数作为替换值,避免 newString 里的 $& 之类被当成特殊的替换模式
  await fs.writeFile(full, text.replace(oldString, () => newString), 'utf8')
  return `已修改 ${file}`
}

面试官可能追问

有了执行命令的工具,为什么还要专门的读文件、改文件工具?

命令行什么都能做,但对应用来说,它只是一串不透明的命令。专门的工具参数是结构化的,应用可以检查(是否先读后改、路径是否越界),可以渲染(把修改展示成 diff 让用户审查),可以并行(多个只读调用同时执行),也方便审计。常见做法是用命令行保证覆盖面,把高频、需要管控的操作做成专门的工具。

编程 Agent 在大仓库里怎么找到相关代码?

先从项目说明文件了解整体结构;按关键词搜索函数名、报错信息、路由路径;顺着 import 和调用关系往下读;大范围的探索交给子 Agent 完成。有语言服务器或代码索引时,跳转到定义、查找引用比纯文本搜索更准确。

怎么让编程 Agent 遵守团队规范?

把规范写进项目说明文件,规则要简洁、具体、可执行;用 lint、格式化、类型检查兜底,有的工具支持在每次编辑后自动执行这类钩子;让 Agent 参照仓库里已有的同类代码来写。最终还是要人工审查 diff。更多上下文管理的思路见 什么是上下文工程。

易错点

  • 以为编程 Agent 会把整个仓库读进上下文,实际上是按需搜索和读取
  • 没有测试和类型检查就让 Agent 大范围改动,只能靠"看起来对"
  • 为了省事自动批准所有命令,或者在有生产凭证的环境里运行 Agent

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。