AI 辅助编码特性与开发者交互模式的演变

是的,我承认这是一个很炫的标题。但想想看——最初只是简单的自动完成建议,如今却演变成了功能强大的东西:我们已经拥有了可以实时生成整段函数、搭建完整文件并提供合理的架构,甚至能从零开始引导整个代码库的 AI。它们已经从帮助敲代码的小助手,变成了协作式的编码伙伴。

这方面的进展非常疯狂。无论你是新开发者还是经验丰富的专业人士,我都认为了解并适应这些功能/工具都是当务之急。随着这些工具的发展,如何选择能让你保持紧密掌控的工具,以及何时让 AI 接管方向盘,这也变得有些复杂。

我们应该给这些 AI 辅助编码功能多大程度的控制?我在这里举一个类比。可以把 AI 辅助编码特性想象成汽车的挡位。低挡(第一挡),你对引擎有最大的控制,但速度较慢——这对应自动补全。然后随着挡位的上升(对话式助手、光标聊天、代理模式),你会用更少的精细控制来换取更高的速度和自动化。

在这篇文章里,我们会从三个角度探讨 AI 辅助编码的演变:

  1. 历史叙事 - 让我们知道是什么样的突破带领我们走到了今天。我觉得这是我们深入理解和珍视这些工具所不可或缺的一步。有些人真的很被宠坏了,直接就把这些工具当理所当然。

  2. 交互模式 - 我们是如何使用这些工具的?它们带来哪些用户体验?又有哪些新兴的使用模式值得观察?

  3. 挡位类比 - 控制 vs 自动化 - 你愿意为了速度放弃多少控制和信任?

友情提示:这篇博文会在介绍新 AI 功能和历史叙事之间来回跳转。


自动完成

代码建议时代

我是在 2018 年末开始我的编程之旅的。第一门编程语言是 Python。我在 Coursera 的 “Python for Everybody” 专项课程,以及一些 sentdex 和 Corey Schafer 的视频里学到了它。那会儿 Kite 凭借其“基于机器学习”的代码建议很引人注目。但我当时是在 Pycharm 和 Spyder IDE 里纯手动写代码的。

大约同时,微软在 (2018 年-至今) 为 VsCode 和 Visual Studio IDE 推出了 Intellicode,它可以提供更具上下文意识的方法和变量建议,本质上是对“Intellisense”列表中的最可能选项做优先排序。

他们在几个托管于 Github 的开源项目上对模型进行了训练。最初使用的是马尔可夫链,随后在 2020 年转向基于深度学习的建议(GPT-C)。你可以阅读这篇来自该项目个人贡献者的博客文章了解更多细节。

其他显著特性包括“重复编辑”检测——在你的编辑中检测到模式,并建议在整个代码库中自动化类似更改,“快速操作”和重构建议。

也可以在某个公司的代码库上对 Intellicode 进行训练,让它的建议更符合团队的编码规范。(我想这是企业版的部分)

迈向单行自动完成

微软最终引入了“整行预测”。整行代码预测意味着基于上下文预测下一行逻辑代码。还记得编辑器里灰色的预测吗?

值得注意的是,早期的代码补全模型并不像今天的工具那样是语言无关的。每个工具都各有所长:Intellicode 专注于 C# 和 .NET,ReSharper 专注于 C#,Eclipse IDE 则针对 Java。

多行自动完成

真正意义上的第一个突破来自 Tabnine(由 Jacob Jackson 创建)。它是首个把 GPT-2 集成到代码补全中的代码编辑器,为未来的 AI 代码编辑器奠定了基础。我不能百分之百确定,但看起来 TabNine 是首个支持基于本地上下文、可跨语言(语言无关)的多行代码补全的编辑器。

GPT-2 在多样化代码库上进行的训练,使其成为首个真正意义上语言无关的代码补全模型。Karpathy 的推文中展示了当时的 Tab 补全是怎样的,这里还有一个视频。它会显示下一行建议以及对应的百分比。

你会发现自动补全一直以来都用 Tab 键来完成。

进入区块级代码补全

OpenAI Codex 模型发布之前,我们实际上并没有“区块级”的代码补全,也就是根据用户输入和上下文来生成整个函数或方法的代码,或根据用户意图进行代码生成和理解。

OpenAI Codex 为 Github Copilot 提供了支持。它能比较准确地实现上面所说的这些功能。

图片来源:How Github Copilot is getting better at understanding your code

“填补中间”功能

我们前面谈到的自动补全都是“从左到右”的方式,这些模型只能“往后拼接”,无法向前看句尾进行上下文分析。早期的模型没法填补中间的空白。

def calculate_total(items):
    # [gap here]
    return total

这种限制后来通过 OpenAI 提出的 “Fill in the middle” 技术得到了缓解。FIM 代表 “fill in the middle”,有时也被称为双向感知(bidirectional awareness),即在光标前后都能理解上下文。也许很多读到这里的人可能是第一次听说 FIM。

我推荐阅读 Codeium 的这篇博文来理解 FIM 的直觉。具体的训练细节可以参考 OpenAI 的论文

Codeium 是第一个在它的扩展中集成 FIM 的,比 Github Copilot 早——后者也在 2023 年 5 月开始大规模支持这一特性。从他们的博客里,你已经可以看到诸如“提示工程”(prompt engineering)和“上下文窗口”(context-window)这样的概念。

现代自动补全还通过以下方式进一步进化:

  • LSP(语言服务器协议)集成,用于实时语法信息和符号引用

  • 更健壮的代码库索引,用于追踪函数间的关系

  • 通过 AST 和符号表分析实现更完善的上下文意识

从简易的文本补全到上下文感知的代码生成,展示了我们已经走了多远,但这仅仅是 AI 辅助编码所能实现的开端。

到这里,我们在时间线上已经到了 2023 年 5 月。当然,我们不想只停留在自动补全。大约在 2022 年末,我们开始有了“编码助手”。是时候来聊聊编码助手了,之后我们再回到自动补全话题。


迈向(对话式)编码助手及 AI 优先的编辑器

2022 年 11 月 30 日 - ChatGPT 研究预览版(GPT-3.5)

ChatGeeBeeDee 3.5 大概是人们首次真正意识到通过 LLM 进行代码生成的潜力。你可以和这个聊天机器人对话,它可以向你解释整段代码。如果你给它一个足够好的提示词和相关上下文,它也能提供准确的代码。

快进到 2023 年 3 月,我们迎来了 GPT-4,它的代码生成能力非常强。Copilot 将 GPT-3.5/GPT-4 整合到它的 Copilot Chat 中。

再到 2023 年 7 月,我们迎来了 Llama-2 的发布,这在开源本地 LLM 圈子里带来了井喷式爆发——在代码生成微调、角色扮演等领域。

AI 代码编辑器的机会

在相当长的一段时间里,人们的工作模式都是把代码复制到 ChatGPT(付费版)里,写出还算不错的提示词,然后再把代码复制回来。这需要你找准粘贴代码的位置。我个人很久以来一直都用这个工作流 xD,因为这样能更仔细地阅读差异并检查代码。

另一个缺点是“上下文切换”的代价——不仅仅是切换窗口的问题,而且你可能会开始浏览网页哈哈。GPT-3.5/4 的推出,为各种 AI 编辑器催生了市场需求。有很多问题需要去解决以获得更好的开发者体验,主要包括:

  • 消除在聊天平台和编辑器之间来回切换的麻烦

  • 实现直接用 LLM 输出来修改代码

  • 探索如何更有效地向 LLM 提供上下文

  • 代码库索引/更强的上下文感知,以便传递给 LLM

Cursor 的出现

要在流畅且低延迟的情况下做到上述这些是一个挑战。我推荐看看 Cursor 的 Problems-2023 博客

Cursor 在这方面率先行动,为实现聊天体验和差异式编辑生成工作流(自动应用编辑,并以 diff 格式展示)提供了支持。事后看来,这是在 GPT-4 热潮中充分利用其优势的关键。快速上线并提供优秀的用户体验也很重要。

随后不久,Cursor 添加了部分接受/拒绝、代码库上下文(可语义搜索代码库)等功能。后来,又加了 CMD+K(行编辑)、CMD+L(聊天)等工作流。

图片来源:Cursor Changelog

也有其他项目如 Continue Dev(也支持开源模型)、Codeium,但它们没有像 Cursor 那样较早地实现“编辑体验”。他们的 diff-apply(现在称 fast-apply)和聊天接口确实也做得不错,而且也挺流畅,但很多人更喜欢 Cursor。

回到自动补全 → Copilot++

Cursor 的自动补全是我用过的最先进的自动补全。它就像前面讨论的各种高级功能的集大成者——据说它被训练(或微调?)得能预测开发者下一步要做什么,非常注重意图驱动。“我做了这个改动,下一步模型应该去修改第 18 行……模型自己应该知道”。你可以试着重构一个变量的名称,它就能给出多行重构的建议;你可以在函数签名或代码里写注释,然后它就会提供合适的补全(当然也支持 FIM)。

Karpathy 转向使用 Cursor 或许是对 Cursor 来说的一个拐点。但在我看来,更重要的拐点是 Cursor 在 2024 年 6 月下旬发布 Sonnet 3.5。它的代码生成比任何其他模型都更出色,而且具有更强的代理性(更好地执行指令和函数调用)。稳固的 UI/UX 体验以及 Copilot++ 功能帮助它搭上了 Sonnet 的顺风车。

Supermaven(主要做自动补全)

Supermaven(出自 Tabnine 创始人)也有与 Cursor 不相上下的自动补全。我自己没用过,以下只是基于道听途说和别人的反馈:它的效果大概能与 copilot++ 持平或者稍微差一点,但速度肯定很快。SuperMaven 在刚上线时的亮点在于它能提供 100K token 的上下文窗口(比 GPT-4 还要大,而 Sonnet(200K 上下文)那时还不存在),这能为自动补全生成提供大量上下文。它们自己训练的模型也对 vanilla self-attention 做了一些修改。

之后他们也添加了类似 Copilot++ 的“下一步行动预测”能力(包括跨文件跳转,这一点估计 Cursor 还不行),1M 的上下文窗口 等等。

Supermaven 没有集成聊天功能——它是一个扩展。由于 VSCode 的 API 限制,它们没法添加更多特性。他们最近和 Cursor 合并了。我对他们在 Cursor 的代码库索引和自动补全体验上的新动作感到非常期待。 Jacob 在这个访谈里提到:

0:49-1:14:他们在 AI 编码方面的愿景和做法正趋于一致,并且有很大的重合

2:11-2:14:Super Maven 最终也会需要构建自己的 IDE 来突破 VS Code 扩展的限制,而这和 Cursor 在做的工作有重复

2:35-2:52:两个团队有互补的优势——Cursor 专注于用户体验,而 Super Maven 在 AI 模型上更擅长

28:12-28:19:Jacob 的原话:“在做 Super Maven 的过程中,我一直在想,如果要和另一个团队联手的话,那一定非 Cursor 莫属。”

我预计对 Tab 键自动补全这个循环会有重大升级——它可能会跨多个文件预测你的下一步动作。Supermaven 的 1M 超长上下文模型也能改善自动补全和 Cursor 聊天/Composer 代理模式里的上下文检测。

开源代码编辑器

2024 年也出现或进一步流行了一批开源的代码编辑器和扩展,比如 Continue.dev(自动补全、开源模型、代码解释,但不支持自动应用编辑)、Aider-chat(llm 聊天 + 通过终端编辑)、Pear AI、AIDE、avante.nvim 插件……我个人稍微用过 Aider-chat,觉得还蛮有意思。我也喜欢他们的博客。


模式

在车里挡位越低,你对引擎的掌控就越强,但速度越慢。如果你觉得驾驭得了,就切到更高的挡;如果你觉得吃力或卡住了,就换到更低的挡。AI 辅助编码的核心就在于何时你需要更细粒度的掌控,何时你又需要放手去换取更快的速度。更高的挡位意味着更多的错误风险和对模型的信任要求。提示词(prompts) 就是方向盘(从第二挡开始)。

我对一小群人的观察发现:经验更丰富或在技术前沿的人往往更喜欢低挡;而非技术人员往往更受高挡的吸引(这多少也有点讽刺)。

第一挡 自动补全

现在有很多关于通过 LLM 进行代码生成的讨论,但真正让编辑器变得像一个神奇舒适之地的 “模型级用户体验突破”(“融进模型权重里”)其实是自动补全。自动补全或许是 AI 代码编辑器里被使用最频繁的特性之一——你会更多地在已有的代码库里工作,而不是从零开始写代码。

如果你问还在写代码(而不是整天开会)的资深工程师,他们会说自己主要用自动补全。对于在 LLM 不太行的专业领域里工作的人,也同样最受益于自动补全。

当你在已有的代码库里工作时,你做更多的是编辑,而不是从头写新代码。在生产环境的代码中,你经常需要遵循模式来写新功能。生产代码往往比大刀阔斧的新代码更需要精准度。

你可能会在多个文件里进行编辑,为了插入一个功能或修复一些 bug,或者做些重构。此时,本地化上下文(想想 Supermaven 的 1M 上下文模型?**)对模型很重要。

我想表达的是:自动补全契合了你对更细粒度控制的需求。它就像在第一挡开车。挡位越低,你对引擎的掌控就越强。而一个好的自动补全模型会很好地感知上下文和用户意图,从而满足你的控制需求。

我很喜欢 Cursor 工程师们把自动补全重新定义为更通用的“下一步行动预测”模型问题的观点。

首先,我们对 Copilot++ 做了扩展,让它可以预测你的下一个位置。把这和下一步编辑预测结合起来,模型就能顺利完成一系列低熵的变动:

我们按了 11 次 tab 键和 3 次其他键。我们把这称作 Cursor Flow(原因不言自明)。

我们正在开发让模型预测你下一个要切换到的文件、你下一个要在终端运行的命令、以及基于你先前命令的下一个编辑!一个下一步行动预测模型

此外,模型应该在你需要它的那一刻就把信息显示出来。不管是那段代码还是文档。

Cursor 应该让你感受到它就像你意志的延伸。只要你想到了要做某个改动,就几乎不需要明确表达,语言模型就能立刻执行。

第二挡 - 对话式 LLM / 独立聊天

很多编程工作可以归结为三个方面:

  • 知识

  • 本地化上下文

  • 理解能力

如果你想在现有代码库里添加一个功能,而你已经打算实施,那么下一步就是搞清楚到底在哪儿加这段代码。之所以要用到“到底在哪儿”这么强调的词,是因为在庞大的 Java 代码库里找要修改的文件,我确实留下了 PTSD(当时我还不能用 AI,于是最快的方式就是运行服务器,调试器断点,然后一点点摸清程序流程)。

这时候像 Cursor Chat 这种功能就很好用。你可以通过 @codebase 功能来检索代码库,进行语义搜索,找到相关文件。接下来,你可以发问来加深对代码库的理解。

或者,如果可以复制粘贴,你可以把相关文件复制到 claude 的 200K 上下文或 1M 超长上下文模型(比如 gemini 2.0 flash / 1206 exp)里,然后问它们要找的东西在哪。

当自动补全和聊天功能结合起来,尤其在你进行编辑或调试时,会感觉像是打了强心针。这是一种“认知层面”的升级,因为你能更好地理解代码。

2024/25 年非常强调代码生成和快速交付,却很少谈到如何利用这些工具来加深对代码库乃至技术本身的理解。这可能是另一个帖子才能讨论的话题了。

第三挡 - Cursor Chat + 一键应用编辑,Windsurf Cascade Chat

如果把从 ChatGPT/Claude 上生成的代码再复制粘贴回编辑器也视为第二挡,那么它在用户控制上更高,因为需要你自己手动决定更改的位置,而且相对更慢。它还涉及了上下文切换,很容易就分心去开浏览器逛一圈。

第三挡的大概就是由 Cursor 引入的。解析 LLM 输出并在正确的位置应用编辑(特别强调“正确的位置”)的能力,省去了大量体力活,也解决了上下文切换的问题。

diff 格式还便于我快速审查代码。这样做很快,而且我仍然可以审查并拒绝更改,因此我的信任风险也不那么高。

这对开发者来说其实是个相当大的挑战,因为 GPT-4 等模型时常会比较“懒惰”地生成一些东西,不过 Sonnet 也许在某种程度上把这件事变简单了。他们的博文现在找不到了,但最初大多是借助 unified diff 来解决的。后来引入了 speculative editing 来加速这一过程。)

对于写大量新功能或做从 0 到 1 的开发的朋友来说,第三挡是个甜蜜区。

我对于在第三挡的建议是:把你的大任务拆分成子任务,然后一个个去做。每做一个都可以刷新一下上下文窗口。提示:点击加号按钮。

如果你在一个更成熟的代码库里工作,你大多数时候会用到的就是第一挡到第三挡。

第四挡 - 代理型特性 - Windsurf Cascade 和 Composer Agent 模式

值得一提:Composer - 正常模式对于多文件编辑和重构很有帮助。它能自动检测上下文,但并不是非常可靠。Composer 的代理(agent)模式可以更好地解决这个问题。

到了这里就有点烧脑了。这一挡只有在 Claude 3.5 Sonnet 这类具有强大“代理能力”(agentic abilities)的模型出现后才成为可能。这里的“代理性”简单来说,指的是模型能够自主地分解并主动完成子任务以达成用户的最终目标。

更具体地说,模型可以自己规划子任务、执行部分操作、感知中间状态、基于中间上下文进行调整,最终完成用户的需求。

代理性能力主要是在训练和微调中通过函数调用的方式“写”进模型的权重中。当人们说某个模型具有代理性时,意味着它可以准确地理解你的指令(“指令遵循性好”),并且善于调用工具/函数。

要能在代码生成上具备代理性,模型本身也需要非常优秀的代码生成能力。Sonnet 在优秀的代码能力与最先进的代理性表现之间取得了平衡。Sonnet new 在代理性代码生成方面更进一步。它比旧版 Sonnet 更具有主动性。总之:代理模式是模型级别的能力升级。

你可以从上面的视频中看到 Sonnet 是如何多次调用 “Artifact” 来完成任务的。我猜这是一个打开 Artifacts 的工具调用。这就是代理性。

Windsurf(由 Codeium 开发)似乎是第一个在编辑器层面正式引入“代理模式”的。它的主要工作模式“Cascade”。我第一次用它时,感觉就像看到了编程未来的一角。

补充:我对 Windsurf 的体验是几个星期前的。它们现在也加了一个 Chat 模式,可以进行头脑风暴并手动应用编辑。

你基本可以给出一些高层次的指令,模型就会不停地进行规划、执行一堆任务、在终端里跑命令、创建文件等,直到任务完成。它会展示它本身的“自主行为”。Windsurf 提供了一个很棒的界面,可以像 Cursor 那样展示它要进行的所有编辑操作,也会显示 diff。就我个人体验和听到的反馈来说,我觉得它在上下文检测方面比 Cursor 的代理模式稍好一点(我是基于几周前的个人使用和朋友反馈,仅代表主观观点)。

“代理模式”在编辑器里对多文件重构和多文件生成/编辑非常好用。在从 0 到 1 的阶段尤其有用,但在中后期的开发阶段就没那么高的收益。有时候也会出错,因为要生成的代码很多,可能会超出上下文窗口,还会遇到知识截止问题。

你是在用更少的控制去换更快的速度。 编辑器还提供 diff 视图让你审查,算是最后一道保险。我个人在 Cursor 的代理模式中更乐于使用它来做大体的生成或多文件编辑,前提是我心里大概知道哪些文件需要改动。

看起来经验较少的程序员更容易被 Windsurf 吸引,因为 Cascade 的自动化特性看起来很酷(不过我觉得第三挡才是他们的更优选择)。Windsurf 与 Cursor 的竞争很激烈,但它依然需要在一些边缘特性上做打磨(比如从链接里获取文档)。Cursor 更偏向高级用户,挡位更多。Windsurf 则倾向让用户少做决策。

个人观察

在编程中,被卡住是家常便饭。当我卡住的时候,我倾向于切换到更低挡、速度更慢但掌控度更高的模式。换句话说,能从代理模式切回聊天模式对我来说很好,可以放慢脚步好好思考问题。对初学者来说,这种卡住的情况出现得更频繁。

代理模式还会更快地消耗你的请求配额。没啥经验或非技术背景的人也更容易陷入“循环”而白白消耗大量请求。鉴于此,我更喜欢 chat+edit 这种模式,你在无形中就能控制你的请求配额。

我比较喜欢的一种用法是:让代理模式先创建一个功能的初版草稿(因为它能在正确的位置创建文件),然后再用 chat + autocomplete 的方式继续下去。

第五挡 - Coding agent 工具

我一直对“代理”比较怀疑,但在新 Sonnet 发布后,以及我最近在一个带有代理组件的项目里工作后,我发现如果把问题的范围限定得足够小,代理是有效的。它带来的价值在于减少人工的投入。

我个人尝试过 bolt.new。它基于同样的原理,范围很明确:做一个带有某种固定前端框架(LLM 也比较擅长)的从 0 到 1 的快速搭建。bolt.new 非常适合从头构建。一旦代码库逐渐成熟,你就会看到收益下降。

我们也看到了 Replit Agent、Devin 等产品的出现,虽然我没亲自试过。这些的范围更广——更像“AI 软件工程师”。例如,Devin 能在代码库里导航、做笔记、修改代码、汇报结果。它可以异步工作(但据说比较慢)。

代理就是把大量的控制权让渡出去以换取更快的速度。范围越广,出错的空间也越大。个人更偏好用编辑器(第一到第四挡),而不是纯代理工具。

代理工具的常见陷阱及注意事项

用 Cursor 代理或 Windsurf Cascade 确实能更快地开发。同样,独立的代理工具也类似。但一旦你被卡住,可能会花更多时间来调试(尤其是对经验少或非技术背景的用户)。模型常因知识过时而陷入死循环(比如 Sonnet 没法调试 Nextjs15 对 Nextjs14 的破坏性变更)。

这时你就必须回到传统的方法:读文档、查 stackoverflow 或 github 讨论等。也就是换档——第二挡和第三挡此时非常有用。我的建议是加深你对代码及你想解决问题本身的理解,好让你写出更精准的提示,或者干脆自己手动解决。

AI 辅助编码可能很快,但也可能忽然变得很慢

大多数减速都发生在功能的“最后一公里”。所以你必须清楚地知道代码里发生了什么,否则最后的调试就会很痛苦。推荐看这篇博文“the 70% problem: Hard truth about AI assisted coding”,里面给出了很多在 AI 辅助编码场景下的可执行建议。

视野受限

不要陷入眼睛只盯着一个点的死胡同。如果你卡住了,可以先停一停。跳出来从更大视角去思考。和 LLM 或同伴讨论其他可行方案,然后再继续。

如果你刚接触某个领域,先去学学基础

如果你在一个自己完全陌生的领域里工作,至少要先了解下基础,这样才能写出更有效的提示词。学了基础知识,你就能把原来的“未知未知”变成“已知未知”。

好了,这篇文章差不多到此结束了,确实很长。

结论

AI 辅助编码只会在现有的基础上越来越强,是时候好好利用它了。我们目前已有的模型非常强大,并且只会变得更好。在整合上下文这一块上依旧有大量工作要做。抱歉,这篇博文里 “上下文 (context)” 出现了超过 30 次,但它确实是核心关键。

希望这篇文章的“挡位”类比能多少在你的日常工作流里给你带来帮助。感谢阅读。如果你喜欢这篇文章,欢迎分享。