利用 ACP 与 Deep Agents 构建定制化 AI 编程助手
本文由 LangChain 创始软件工程师 Jacob Lee 撰写。他致力于打造一款更符合个人工作习惯的编码代理,并在此分享了他利用 Deep Agents 和代理客户端协议(ACP)构建该工具的过程及心得。
我已逐渐接受这样一个事实:作为一名软件工程师,我将把越来越多的工作委托给大语言模型(LLM)。我曾是 Claude Code 的早期忠实粉丝,尽管我的自尊心仍告诉我,在某些特定场景下我编写的代码比数据中心里 Anthropic 的“原型天才”们更出色,但如今我更多是在进行细微的修改和建议,而不是亲手编写完整的模块。
这种转变极大地提高了我的生产力,但我对盲目地将如此重要的工作交给一个不透明的第三方感到越来越不安。考虑到各种显而易见的原因,从头训练自己的模型是不可能的(况且模型可解释性本身就是一个尚未解决的难题),但模型之上的代理框架和用户体验(UX)仅仅是软件,而软件恰好是我擅长的领域。因此,在陪产假期间,我有了一些空闲时间,便尝试根据自己的需求构建了一些工具。
我在 LangChain 这家初创公司工作,我们一直在开发自己的一套开源代理构建模块。我决定在我们的 Deep Agents 框架与 代理客户端协议 (ACP) 之间构建一个适配器。我的初衷只是想打造一个适合我工作流的定制化编码代理,但结果超出了预期。在过去的几个月中,它已经完全取代了 Claude Code,成为我的日常主力工具。此外,通过在其之上运行 LangSmith,我还获得了对代理行为的完全可观测性。在本文中,我将介绍它是如何工作的,以及如何亲自上手配置。
为什么选择 IDE + ACP 而不是终端 + TUI?
如果你还不熟悉 ACP,它是一项开放协议,定义了客户端(通常用于 WebStorm 或 Zed 等 IDE)与 AI 代理之间的交互方式。它允许你实现一些很酷的功能,例如将 IDE 中当前查看的代码上下文快速传递给编码代理。
在从事软件开发的十年间,我已习惯于在 IDE 中高效工作。我认为 IDE 依然具有价值,原因如下:
- 我偶尔仍会手动编辑代码。 通常,这些小改动我手动完成比向代理解释问题要快得多,或者是因为我可以在代理运行的同时并行处理一些任务(例如添加调试语句),这仍然能提供一定的“超额收益”(alpha)。
- IDE 是查看代码上下文的绝佳界面。 我通常利用这一点在提示之前理解问题的总体范围,或者进行当前分支的自我检查。而且,与其让代理在项目中到处
grep搜索,不如直接指引它查看某个文件,这样往往更快。
以前,我在 IDE 的独立终端窗格中使用 Claude Code,这虽然可行,但总感觉是两个脱节的工具。在 JetBrains IDE 中,代理生活在一个原生的工具窗口中,并实现了深度集成。我可以 @mention 我当前正在查看的文件或代码块,我的许多对话线程中都充斥着诸如“看看这段代码,它看起来正常吗?@thisFile”之类的消息。
它是如何工作的
代理部分
虽然我可以从零开始创建代理的各个部分,但 Deep Agents 提供了一个很好的、具有明确设计理念的起点,包括:
- 文件系统交互工具(
read/write/edit_file、ls、grep等)。 - Shell 访问权限:允许代理运行 lint、测试等验证任务。
- 在此基础上,支持人机回环(human-in-the-loop),以限制危险操作。
write_todos工具:鼓励代理采取规划步骤,将工作拆解为若干步骤并跟踪进度。- 在实践中,对于较长期的重构任务,这能显著增强代理的专注度。
- 派生隔离子代理的能力:用于并行或分层工作。
- 每个子代理都有自己的上下文,独立运行并汇报结果,从而保持模型上下文窗口的可控性。
- 其他重要的 UX 特性:如流式传输、取消操作、提示词缓存和上下文摘要。
我还添加了一些自定义中间件,在系统提示词中追加有关当前项目设置的信息,例如 IDE 中打开的当前目录、是否存在 git 仓库、包管理器检测等。
此外,直接通过 Python 添加技能、调整系统提示词、增加自定义工具或 MCP 服务器也变得更加容易,无需创建新的 CLI 配置选项。
ACP 适配器
在确定了基本的代理设置后,我需要通过 ACP 将该代理连接到客户端。我创建了一个适配器,实现了 ACP 接口并处理会话生命周期、消息路由、模型切换和流式传输。
令我惊喜的是,代理的能力与 ACP 概念的映射是如此清晰。例如:
- 代理的规划步骤 (
write_todos) 自然地映射到 ACP 的 代理计划。 - 来自代理的中断(例如“我想运行这个命令”)映射到 权限请求。
- 线程和会话持久化与 Deep Agents 的检查点机制几乎是一一对应的。
这意味着我不需要发明太多的粘合逻辑——协议本身已经为我想要的大部分功能提供了良好的原语。除去工具调用和消息格式化,代理运行器的核心逻辑大致如下:
人机回环流程是我花费精力最多的部分。当代理想要运行 Shell 命令或进行需要批准的文件编辑时,适配器会拦截来自 Deep Agents 的中断。根据用户选择的权限模式以及他们之前的批准记录,适配器要么立即恢复,要么向 IDE 发送权限请求,并提供 approve(批准)、reject(拒绝)或 always-allow(总是允许)该类命令的选项。
always-allow 是会话级别的——如果你批准了一次 uv sync 并选择“总是允许”,后续的 uv sync 调用会自动跳过提示。但我同时也采取了措施,防止 uv run script.py 等类似命令绕过权限检查。
效果如何
虽然我没有进行正式的评估,但经过几次迭代后,代理的表现让我惊喜。我原本没打算完全弃用 Claude Code,这同时也是一次很棒的“吃自己的狗粮”(dogfooding)练习,因为我们的开源团队能够将我的一些反馈上游同步回 Deep Agents 本身。
我最初的目标是重新获得对日常工作流的代码级(而非配置级)控制,这一点也实现得很好。几周前 Anthropic 发生故障时,我可以毫不费力地切换到 OpenAI 的 gpt-5.4,甚至还发现它有一些有趣的特性。我会在会话中来回切换模型,以便在处理棘手任务时从不同模型获得视角。我还发现像 GLM-5 这样的开源模型也非常强大,同时能显著降低成本。
另一个好处是通过 LangSmith 追踪 实现的可观测性,它让我在遇到问题时能够调试和改进代理。能够精确查看传递给模型的上下文、它调用的工具以及它在何处出错,帮助我理解了那些原本隐藏在框架内部的行为。
例如,当我发现代理开始对我的文件系统进行广泛而缓慢的扫描时,我通过追踪发现系统提示词中存在一个 Bug,它误导代理认为项目位于文件系统根目录,而不是当前工作目录。
为乐趣和收益重掌开发工作流
这始于一个我在照顾新生女儿的间隙所做的深夜小项目,最终却取得了巨大的成功,无论是在我对代理行为的理解上,还是在改善我的日常工作流方面。
它向我证明了 Claude Code 并非什么魔法,而是一系列巧妙技巧的集合。框架层仅仅是软件,而软件是任何开发者都可以根据自己的工作方式去塑造的东西。
如果你对此感到好奇,我强烈建议你也尝试一下这样的实验。即使是一个小原型也能让你深入了解这些系统是如何思考的,以及它们在何处容易出错。克隆仓库并按照此处的设置指南从源代码开始尝试吧。我很想知道你的想法,欢迎在 X 上联系我 @Hacubu!
特别感谢 @veryboldbagel 和 @masondxry 在将该适配器产品化以及处理我无穷无尽的问题和反馈方面所提供的帮助!