首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Coding Agent 不是「聪明的自动补全」:一个完整系统的工程解剖

Coding Agent 不是「聪明的自动补全」:一个完整系统的工程解剖

作者头像
陆业聪
发布2026-04-15 19:29:22
发布2026-04-15 19:29:22
30
举报
Coding Agent 系统工程解剖
Coding Agent 系统工程解剖

📰 科技要闻

• 小马智行发布 PonyWorld 世界模型 2.0,自动驾驶领域多模态世界模型再上台阶

• Nathan Lambert 撰文批评 Anthropic 将 Claude 神话化,误导开源模型安全讨论

• OpenVLThinkerV2 开源多模态推理模型,GRPO 训练范式向通用多域视觉任务扩展

• Sebastian Raschka 深度拆解 Coding Agent 系统组件:工具、记忆、Repo 上下文三位一体

• Lilian Weng & John Schulman 联合发布长文《Why We Think》,系统梳理 TTC 和 CoT 推理机制

有一个现象挺有意思:很多人用 Cursor 用了几个月,觉得「就是个好用点的 Copilot 嘛」。然后某天让它做一个稍微复杂的重构任务——改一个跨 10 个文件的接口,它一口气读代码、写测试、定位报错、修 bug、跑 CI——突然就懵了:这他妈哪是自动补全,这是一个系统在跑。

这个「懵」才是正确的起点。

Sebastian Raschka 上周发了一篇拆解 Coding Agent 组件架构的深度文章,读完之后我把它和 arXiv 上几篇 Agent 元认知论文一起梳理了一遍,想把这件事说清楚:Coding Agent 的真正壁垒不在于底层模型有多强,而在于系统架构。模型只是引擎,剩下那些「工具调用」「记忆管理」「Repo 上下文感知」才是决定 Agent 能不能真正交活儿的关键。

下面我们就逐层拆开来看。

一、为什么「更大的模型」解决不了这个问题

先说一个认知误区:很多人觉得 Coding Agent 做不好,是因为模型不够聪明。换个更大的模型就行了。

这个直觉是错的,或者说是不完整的。

做一个真实的代码任务,拿「在现有 FastAPI 项目里加一套完整的鉴权体系」举例,模型需要做什么?

• 理解现有项目结构(数百个文件,几万行代码)

• 找到所有需要鉴权保护的 endpoint

• 读懂现有 middleware 的写法和命名惯例

• 写代码、运行测试、看报错、再修

• 在几轮迭代里保持对「我改了什么、还没改什么」的准确认知

一个 context window 只有 200K token 的模型,就算智商再高,把整个 repo 塞进去也塞不下。就算塞进去了,中间层注意力漂移,它也容易忘掉 50 轮对话之前自己说过什么。

这不是智力问题,是工作记忆问题。解决它需要的是系统设计,不是更大的参数量。

更直接的证据:OpenAI 的 Codex、Anthropic 的 Claude Code 和 Cursor,底层都跑的是差不多量级的模型,但用起来差距肉眼可见。差在哪?差在系统层。

二、Coding Agent 的三层架构

把 Coding Agent 拆开来,核心是三个组件:工具调用层、记忆管理层、Repo 上下文层。这三层各自解决不同的问题,但彼此高度耦合。

在进入细节之前,先建立一个统一的心智模型。可以把一个 Coding Agent 想象成一个刚入职的高级工程师,他聪明、训练有素,但对这个项目一无所知:

工具调用层 = 他的双手——能打开终端、查文档、跑测试、搜代码

记忆管理层 = 他的工作记事本——不靠脑子记所有细节,而是把进度、发现的问题、已改的文件都写下来

Repo 上下文层 = 他对代码库的地图感知——知道哪个模块在哪、调用关系是什么、命名惯例是什么

这三层不是「插件」,是一个工程师能工作的最小必要条件。少了任何一层,他要么乱翻代码(没地图),要么做了忘做了忘(没记事本),要么乱按键盘(没手)。

2.1 工具调用层:Agent 的双手

工具调用不是「让 LLM 输出一个 JSON 然后执行」这么简单。一个设计良好的 Coding Agent 至少需要以下工具类型:

文件系统工具:read_file、write_file、list_directory、search_in_files

代码执行工具:run_shell、run_tests、run_linter

符号分析工具:find_definition、find_references、get_call_graph

版本控制工具:git_diff、git_log、git_blame

设计这些工具时有一个核心原则很容易被忽视:工具粒度要和模型的决策粒度匹配

举个反例:如果你把 search_in_files 设计成「全文搜索,返回所有匹配行」,模型每次调用都会收到几百行输出,context 瞬间爆炸。更好的设计是分级:先搜文件名,再读文件摘要,再按需读具体函数体。

arXiv 上一篇叫《Act Wisely: Meta-Cognitive Tool Use in Agentic Multimodal Models》的论文把这个问题理论化了。它研究的核心问题是:Agent 如何决定「什么时候用内部知识推理,什么时候调用外部工具」?结论是这个元认知决策本身是一个可以被训练的能力,而不是自然涌现出来的。

换句话说:你不能指望一个强大的基础模型自动学会合理使用工具。工具设计和工具选择策略需要被显式优化。

一个实用的工程建议:给每个工具加上「成本标注」,让模型在调用前估算收益和开销。比如:

代码语言:javascript
复制
tools = [
{
"name": "read_file",
"description": "Read the full content of a file",
"cost": "medium",  # tokens consumed: ~file_size
"when_to_use": "Only after confirming file is relevant via search or directory listing"
},
{
"name": "search_symbol",
"description": "Find definition or references of a code symbol",
"cost": "low",  # returns compact results
"when_to_use": "When you need to locate where a function/class is defined or used"
},
{
"name": "run_tests",
"description": "Execute test suite and return results",
"cost": "high",  # takes time, verbose output
"when_to_use": "After completing a change, to verify correctness"
}
]

这不是什么魔法,但它让模型在 system prompt 里就建立起工具使用的优先级直觉。

2.2 记忆管理层:Agent 的工作记事本

这是 Coding Agent 里最容易被低估、也最容易做烂的一层。

记忆系统不等于把所有对话历史都塞进 context。这样做有两个问题:context 会超限,而且注意力会被无关信息稀释。

一个实用的记忆分层方案:

记忆类型

存储位置

生命周期

典型内容

工作记忆

Context window

当前任务

当前文件内容、最近工具调用结果

短期记忆

Scratchpad 文件

当前 session

任务进度、已修改文件列表、发现的 bug

长期记忆

向量数据库

跨 session

项目惯例、曾解决的问题、架构决策

语义记忆

代码索引

持久

Repo 结构、函数签名、依赖关系

其中「短期记忆用 Scratchpad 文件」这个设计值得单独说一下。

OpenDevin(现在叫 OpenHands)的早期版本有一个设计:Agent 在执行复杂任务时,会维护一个叫 TASK_STATE.md 的文件,记录:

• 任务目标分解

• 已完成的子任务

• 当前正在做什么

• 遇到的阻塞问题

每次调用模型时,把这个文件的最新内容加到 context 最前面。这样即使对话历史很长,模型对任务的全局认知也不会漂移。

这个方案的本质是:把隐式的「模型工作记忆」外化成显式的「文件状态」,让整个 Agent 的状态可观测、可调试、可恢复。这比直接依赖模型记住一切要健壮得多。

代码示意:

代码语言:javascript
复制
class AgentScratchpad:
def __init__(self, path: str = "/tmp/agent_task_state.md"):
self.path = path
def update(self, section: str, content: str):
"""Update a specific section of the scratchpad"""
state = self._read()
state[section] = content
self._write(state)
def to_context_string(self) -> str:
"""Format for injection into model context"""
state = self._read()
lines = ["## Current Task State\n"]
for section, content in state.items():
lines.append(f"### {section}\n{content}\n")
return "\n".join(lines)
def _read(self) -> dict:
if not os.path.exists(self.path):
return {}
# Simple key-value parsing from markdown
...
def _write(self, state: dict):
with open(self.path, 'w') as f:
for k, v in state.items():
f.write(f"## {k}\n{v}\n\n")

2.3 Repo 上下文层:Agent 的代码地图

这一层很多人做得太粗糙。最常见的做法是:把整个 repo 用 embedding 向量化,然后用语义搜索找相关代码。这个方向是对的,但魔鬼在细节。

问题一:代码的语义相似不等于调用相关

两个功能相似的函数,embedding 距离很近,但可能根本没有调用关系。而真正需要一起修改的代码,往往是调用链上相邻的节点,语义距离却可能很远(一个叫 handle_payment,一个叫 update_ledger)。

更好的方案是语义搜索 + 调用图分析的混合检索:

代码语言:javascript
复制
def retrieve_relevant_context(query: str, repo_index: RepoIndex, top_k: int = 10):
# 1. 语义相似搜索
semantic_results = repo_index.semantic_search(query, k=top_k)
# 2. 对语义搜索结果做调用图扩展
expanded = set()
for node in semantic_results:
expanded.add(node)
# 加入直接调用者和被调用者
expanded.update(repo_index.call_graph.callers(node, depth=1))
expanded.update(repo_index.call_graph.callees(node, depth=1))
# 3. 按 token 预算裁剪,优先保留核心节点
return repo_index.budget_trim(expanded, max_tokens=8000)

问题二:Repo 索引需要增量更新

Agent 在执行任务过程中会修改代码。如果索引是静态的,Agent 修改了 A 文件,再去搜索和 A 有关的代码,检索到的可能是旧版本。

解决方案是在每次文件写入后触发增量重建索引,只更新被修改文件及其直接依赖的索引节点。这听起来很自然,但很多框架没做,导致 Agent 在多轮修改后越来越容易犯低级错误。

问题三:Repo 上下文需要多粒度表示

不同任务阶段需要不同粒度的 Repo 信息:

规划阶段:需要项目级别的架构概览(目录结构、核心模块依赖图)

实现阶段:需要函数级别的签名和 docstring

调试阶段:需要具体的代码行,包括相关测试

这要求索引系统支持多粒度查询,而不是一刀切地把所有东西切成等长的 chunk。这里有一个实践经验:按 AST 节点(函数/类/模块)切分,比按固定 token 数切分效果明显更好,因为代码的语义边界天然是语法边界。

三、Agent 的执行循环:ReAct 之外

大部分人理解的 Agent 执行是 ReAct 循环:Reason → Act → Observe → Reason...这个框架没错,但对 Coding Agent 来说太简单了。

真实的 Coding Agent 执行循环更接近这个结构:

第一阶段:任务分解(Planner)。接到任务后,先不急着动手,而是产出一份子任务清单。就像那个新入职工程师,第一件事是和 lead 对齐:「我打算先加 JWT 中间件,再改三个 endpoint,最后跑测试」——这个规划步骤单独占一次 LLM 调用,结果写入 Scratchpad。

第二阶段:子任务执行循环(Executor)。针对每个子任务,进入 Reason → 工具调用 → 结果观察 → Scratchpad 更新的小循环。这里有两个关键设计:失败要分类(是代码错了?环境问题?测试本身有 bug?不同失败走不同分支),步数要有梯度(70% 时检查进度,90% 时进入收尾模式,不开新坑)。

第三阶段:结果汇总 + 长期记忆更新。任务完成后,把这次踩过的坑、用过的解法写入向量库。下次遇到类似任务,那个新工程师就不再完全是新人了。

几个关键点:

Planner 和 Executor 要分开。一个 Agent 既负责规划又负责执行,很容易在执行过程中被局部细节带偏,忘掉最初的目标。让一个 LLM 调用负责拆任务,另一个调用负责执行子任务,规划层保持全局视角,执行层专注当前步骤。

失败分类很重要。不是所有失败都值得重试。运行测试失败,可能是代码逻辑错了(应该重新分析),也可能是环境问题(应该检查依赖),也可能是测试本身写得有问题(应该跳过或标记)。把这三种情况混在一起交给模型自己判断,成功率很低。显式的失败分类器(可以是一个小模型,或者基于规则的分类器)能显著提升稳定性。

最大步数限制要有梯度。不要只设一个「到了就停」的硬限制。更好的设计是:在 70% 步数时触发一次「进度检查」,让模型评估是否还在正轨上;在 90% 时触发「收尾模式」,优先完成现有改动而不是开新坑。

四、当前工具的对比:Claude Code vs Cursor vs Codex

直接说结论,理由在后面:

Claude Code:工具调用设计最成熟,工具粒度细、有明确的成本意识;记忆管理偏弱,没有显式的跨 session 持久化

Cursor:Repo 上下文层最强,实时索引、多粒度检索做得扎实;执行循环相对保守,不太敢做大范围自主修改

Codex(OpenAI):执行循环设计最激进,并行任务执行、沙箱隔离;工具生态弱,对非主流技术栈支持差

用 Sebastian Raschka 文章里的框架来分析:这三个工具其实各自在三层架构里有不同的侧重,没有谁在所有维度都是最优的。

我目前的日常用法是:用 Cursor 做代码理解和精准修改,用 Claude Code 处理需要多轮工具调用的复杂任务。两者互补,而不是非此即彼。

一个值得关注的信号:Anthropic 在 Claude Code 的最新版本里开始加入 memory 工具,允许 Agent 主动写入跨 session 记忆。这个方向是对的,但还很早期——写什么、什么时候写、怎么检索,这些策略的设计水平目前差距很大。

五、一个被忽视的视角:Agent 元认知

arXiv 那篇关于 Agent 元认知的论文(Act Wisely)提出了一个值得深想的问题:Agent 如何知道「自己不知道」?

对 Coding Agent 来说,这个问题很具体:

• 这段代码逻辑,我是应该凭训练数据里的知识直接写,还是先读一下现有实现?

• 这个函数名的语义,我是猜一个,还是去找定义确认一下?

• 测试失败了,是我改错了,还是测试环境有问题?

每次错误的判断都会浪费几十步的工具调用,或者更糟,引入难以发现的 bug。

论文的结论是:元认知能力可以通过专门的 RL 训练来提升,而不是自然涌现。具体做法是在训练数据里显式标注「该用工具的时刻」和「不该用工具的时刻」,让模型学会这个决策边界。

对做 Agent 系统的工程师来说,在当前模型元认知能力有限的情况下,一个实用的补充是:在 system prompt 里明确定义「不确定性阈值」——

代码语言:javascript
复制
system_prompt = """
You are a coding agent. Follow these uncertainty rules strictly:ALWAYS use tools to verify before acting when:
- You're not 100% sure about a function's signature or behavior
- The file hasn't been read in the last 5 steps
- You're about to make a change that affects more than 3 filesSKIP tool use and act directly when:
- You just read the relevant code (within last 2 steps)
- The change is purely additive (adding new code, not modifying existing)
- Standard library / well-known framework behavior (e.g., Python's datetime, FastAPI routing)
"""

这不完美,但在没有专门元认知训练的情况下,显式规则是可操作的退而求其次。

六、真正的壁垒在哪里

把上面这些拼在一起,我的判断是:

Coding Agent 现阶段的核心壁垒不是模型能力,而是系统工程的完整度。具体来说:

1. 工具设计的精细程度(粒度、成本标注、失败语义)

2. 记忆系统的分层设计(工作记忆 + Scratchpad + 向量库 + 代码索引)

3. Repo 上下文的检索质量(语义 + 调用图 + 多粒度 + 增量更新)

4. 执行循环的健壮性(Planner/Executor 分离、失败分类、步数梯度)

这四点做好了,用一个中等偏上的模型也能出很好的结果。这四点没做好,用最强的模型也会在复杂任务上翻车。

从这个角度看,Coding Agent 的竞争格局在接下来一两年里不会完全由模型能力决定。系统工程的积累是真实的壁垒——这也是为什么 Cursor 这样一个没有自己模型的公司,能在这个赛道上活得很滋润。

值得继续探索的方向:Agent 执行轨迹的可解释性与调试工具。当前 Coding Agent 出错了,很难判断是工具调用哪步出了问题、是记忆检索失效了还是模型推理偏差。把 Agent 的执行轨迹可视化、可 replay、可断点调试,这套工具链目前基本是空白,但对提升 Agent 可靠性来说是最缺的一块。


如果你在做 Coding Agent 或者 AI 编程工具,欢迎交流 → 评论区见

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陆业聪 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档