“Agency Agents”这个名字,说白了就是给 AI 编程能力套了一层“代理”外壳,让一组具备自主规划、写码、调工具能力的智能体可以以统一的接口跑在编辑器、命令行、网页、甚至 CI 流水线里。这个东西听起来很玄,但拆开看核心就三件事:智能体怎么组织,能力怎么抽象,平台怎么适配。这篇博文我想从架构设计、核心模块、跨平台工程实现到落地踩坑,完整聊聊一个跨平台 AI 编程智能体生态是怎么搭出来的,给正准备做类似基础设施的人一份可以直接参考的实操记录。
先交代背景。我参与过的是一个内部孵化的智能体框架项目,目标不是做一个聊天框里帮你补全代码的小工具,而是要把“AI 编程智能体”做成一套生态:不同的模型后端、不同的客户端入口、不同的工具集,都能按同一套机制接入。项目代号就叫 Agency Agents,核心思路是“协议驱动、引擎中立、平台无关”。到今天这个框架支撑了编辑器插件、命令行工具、网页 Demo、自动化任务脚本四个端的落地。整个过程中踩过的坑、改过的架构决策非常多,这篇就按五个部分来讲。
1. 架构全景:为什么 AI 编程智能体需要“跨平台生态”
1.1 从单点工具到生态:智能体不是“聊天框加代码补全”
先说一个很常见但错误的认知:很多人觉得 AI 编程智能体就是把大模型接进 IDE,加一个代码自动补全。实际上,真正能承担工程任务的智能体,远不止“生成代码”这一步。
我们在一开始定方向时做过一次调研:假设我们只做一个编辑器插件,把模型接进去,让用户选中代码问问题或生成新代码,那它充其量是个“AI 助手”,离“智能体”差得很远。智能体的核心特征是什么?是它能自己拆解任务、自己调用工具、根据执行结果修正下一步动作。换句话讲,它有循环:思考、行动、观察、再思考。
这就带来一个架构问题:如果这套循环逻辑只写在某个编辑器的插件里,那它就只能服务那个编辑器和那个平台。命令行用户怎么办?网页用户怎么办?批量跑脚本的场景怎么办?如果每个平台都单独写一套智能体循环,那模型策略、工具注册、上下文管理、日志追踪全都得重复实现,维护成本会指数级上升。
所以跨平台不是“锦上添花”,而是智能体工程化的必然产物。我们需要把“智能体核心”做成一个中立引擎,跟具体的前端平台解耦,让插件、CLI、Web 都是这个引擎的“客户端”。
1.2 四层架构:接入层、引擎层、能力层、模型层
我们最终把系统拆成了四层,每层职责边界很清晰:
第一层是接入层,负责跟具体的运行环境打交道。比如编辑器插件要响应快捷键、显示 diff、处理选中文本;CLI 要解析参数、打印日志、处理 stdin/stdout;Web 端要建立 WebSocket 连接、展示事件流。这一层只做“交互适配”,不碰智能体逻辑。
第二层是引擎层,也就是智能体主循环。它负责接收任务、规划步骤、管理执行状态、编排子任务。这一层是系统的“大脑”,只依赖标准化的消息协议,对上下层都无感知。
第三层是能力层,提供工具集。包括代码读取、文件修改、命令行执行、单元测试运行、代码检索、版本管理操作等。工具以标准 schema 注册,向引擎暴露统一接口。
第四层是模型层,负责跟各种大模型后端通信。我们封装了一个统一的补全/对话接口,底层可以切换不同的模型服务。
这个分层的价值,在后期维护中体现得特别明显。模型层换成新模型时,完全不影响工具注册和引擎状态机;新增一个 Slack 机器人前端时,只需要写一个接入层适配器,复用整套智能体循环。
1.3 协议先行:中间消息格式是架构稳定的关键
跨平台生态最容易踩的坑是“接口耦合”。如果智能体引擎直接调用编辑器 API,那这套系统就绑死在一个编辑器上。我们的解法是:定义一套中间层协议,所有平台通过这套协议跟服务端通信,服务端只处理协议消息,不关心对方是谁。
这套协议最核心的规范是消息流。一次智能体任务的进程由多条消息组成,包括但不限于:
- 任务创建消息:包含目标描述、约束条件、初始上下文路径
- 工具调用请求:包含工具名、入参、调用 ID
- 工具执行结果:携带状态、输出内容、错误信息
- Agent 状态变更:规划中、执行中、等待输入、已完成、已失败
- 信息增量:用户新增要求、仓库文件变化、模型的新思考
这个类似事件流的协议有一个关键设计:所有消息都是“可追踪的”,都带全局唯一的 request_id 和 session_id。这么做的好处是,哪怕五个智能体并行工作,日志系统也能把一条任务链完整串起来,出问题时能回溯到具体决策点。
提示:架构设计时最值得花时间的不是代码结构,而是消息格式。消息格式一旦确定,相当于给整个生态画了边界线,后续所有适配工作都在这条线上做文章。
2. 核心引擎拆解:智能体循环里的每个齿轮
2.1 任务规划器:把模糊需求切成可执行单元
智能体的第一步不是写代码,而是“想清楚要做什么”。我们的任务规划器接收用户描述后,先做一个“目标结构化”的操作。
举一个实际例子。用户说“帮我实现一个用户登录接口,包含密码加密和 token 校验”,这看起来已经是明确需求了,但智能体执行前还需要补充很多细节:这个接口是 RESTful 还是 GraphQL?密码加密用 bcrypt 还是 argon2?token 是 JWT 还是不透明字符串?数据库模型里用户表是什么结构?
早期版本我们让模型直接生成代码,结果生成的代码跟项目现有风格冲突严重。后来改成了强制规划器先输出“任务分解报告”,报告里包含:
- 任务目标重述
- 涉及的文件路径与模块范围
- 关键依赖与第三方库
- 风险点与需要用户确认的地方
- 按依赖关系排序的子任务列表
子任务会被翻译成智能体可以逐步执行的步骤。每个步骤都对应一个明确的“完成定义”,比如“在 auth 包中创建 token_util.py 并实现生成与校验函数”“在接口路由上注册 POST /login”。这一步是整个引擎里最体现“工程化”的地方,因为它把一个不确定的生成任务转换成了确定性的执行序列。
2.2 代码生成与编辑引擎:Diff 才是真正落地的能力
智能体生成的代码,最终要写进用户的代码库。如果只是把模型返回的整块文本直接覆盖文件,那这套系统根本没法在生产中使用。真实场景里,模型生成的代码经常需要跟已有代码共存、需要保留原有注释、需要微调某一行逻辑。
我们实现的编辑引擎核心是“结构化编辑”。智能体不直接输出“文件新内容”,而是输出一组编辑操作:插入、替换、删除、重命名。每个操作都附带具体位置定位。定位方式支持三种:按行号定位、按符号定位(比如函数名或类名)、按语法树节点定位(比如 if 块内的第一个断言语句)。
最稳的是语法树定位。因为按行号定位在文件变化后会失效,按符号定位在某些同名函数场景下会混乱,语法树节点定位只要代码库没被破坏性重构,基本都能准确命中。编辑引擎实际执行前会先做一次“干跑”,把操作应用到副本上并检查语法,全部通过后才写真实文件。
代码生成的质量控制,我们还用了“自校验循环”。模型写完代码后,智能体要自己尝试做静态检查:导入对应模块、调用关键函数、运行相关测试。如果测试失败,错误信息会反馈给模型,触发新一轮修改。实测下来,这个循环能让生成的代码可运行率提升非常明显,但也带来了 token 消耗和延迟上升的问题,所以我们在配置里加了“自校验轮数上限”,默认最多两轮。
2.3 工具调用与沙箱:智能体的手和脚必须安全可控
智能体光会写代码不行,它还要能读文件、搜代码、跑测试、查文档。我们把这些操作全部封装成工具,统一注册进能力层。工具的定义遵循 JSON Schema 规范,引擎不关心工具内部怎么实现,只遵循 schema 对入参出参做校验。
工具清单按场景动态加载:在 Python 项目中加载“运行 pytest”工具,在 JS 项目中加载“执行 npm test”工具。这叫“按需工具注入”,好处是让模型的决策空间更小、更精准,避免给它一堆用不上的工具造成误调用。
安全上最棘手的是“执行命令”类工具。智能体会读取用户需求生成 shell 命令,比如安装依赖、跑测试、格式化代码,但如果我们照单全收,风险极大。我们的方案是三层防护:
第一层,命令白名单。只允许执行预先审批过的命令前缀,例如“pytest”“ruff check”“npm run build”。第二层,强制沙箱。所有命令默认在容器或临时目录里跑,禁止访问仓库外路径。第三层,人工确认。高风险类工具必须经过用户在 UI 上的确认,确认超时则默认拒绝。
这套机制虽然限制了一部分自动化能力,但把安全事故发生的概率压到了最低。真实的项目里,宁可让智能体多问一次,也不要它自作主张删库。
2.4 上下文工程:仓库级上下文的管理与组织
AI 编程智能体的一个大难题是上下文窗口有限,而仓库代码量远远超出模型单次能处理的量。我们的做法是“分层上下文”。
第一层是“会话核心上下文”,包含用户需求、项目技术栈、当前任务规划结果,这些始终保留。第二层是“仓库索引上下文”,通过代码检索工具按需获取。第三层是“文件级上下文”,当智能体决定编辑某个文件时,才加载该文件完整内容。
上下文不是只靠“加载”,还有“淘汰”。我们实现了上下文管理模块,负责跟踪哪些内容已经被消耗、哪些已经过期。比如某文件被编辑过一次后,旧的版本内容就标记为过期,后续模型读取时必须重新加载新版本,避免基于旧代码做决策。
这里有一个特别实用的小技巧:把项目根目录下的 README、配置文件解析成“项目结构摘要”,代替给模型灌入大量文件。比如自动生成一个 JSON 树,里面列出包名、模块职责、依赖关系,再配合“路径到模块说明”的映射表。这样模型在开始规划时就有了全局地图,不需要一把梭读全部代码。
3. 跨平台适配的工程实现:一套核心,处处可用
3.1 接入层设计:编辑器、CLI、Web 的共性抽取
跨平台最怕的是“为了抽象而抽象”,抽象出大量假接口,用的时候还是要写 if-else。我们做接入层时坚持一条原则:每个平台适配器只做三件事——收集用户输入、渲染事件流、呈现最终结果。
以编辑器插件为例,适配器要做的是:
- 监听选中文本与光标上下文,作为用户输入的一部分
- 接收引擎推送的“编辑建议”,转化成编辑器可预览的 diff 视图
- 把工具调用的确认请求渲染成 UI 弹窗
而 CLI 端适配器做的事就完全不一样了:
- 从命令行参数和 stdin 读取任务描述
- 在终端上以文本流方式打印智能体的思考过程与工具执行结果
- 对需要交互确认的工具调用,降级为“--yes 自动同意”或“--ask 逐条确认”模式
相同的地方在哪?它们都消费同一种事件流、都发送同一种请求协议。接入层的人力成本其实不高,我们四个端加起来只花了一个人两周时间,同时跨端行为的统一性远超预期。
3.2 模型路由与降级:多后端切换的隐藏复杂性
模型路由听起来很简单:配置一个 base_url 和 api_key 就行。但真正做到生产级,大量细节都会冒出来。
第一个问题是流式输出的协议差异。不同模型提供商的 OpenAI 兼容接口细节并不完全一致,有些字段要映射、有些超参不支持。我们做了一个模型网关层,统一转换成内部事件流,把各家的差异消化在网关里面。核心参数只暴露几个可配置项:温度、最大 token 数、停止符、频率惩罚。
第二个问题是稳定性。某家模型服务偶尔会超时或返回异常,这在会话场景里用户还能忍,但在智能体执行长任务时很致命。我们实现了自动降级策略:模型调用失败一次就自动重试一次,还失败就切换到备选模型,并把切换事件记录下来,让用户能看到。个人经验是,备选模型千万不要只准备一家,至少要一家闭源商业模型加一家开源权重模型,因为某些任务上闭源模型强、某些任务上开源模型的响应速度和本地化能力反而更舒服。
第三个问题是代价控制。不同模型的成本差距极大,我们设置了“模型分级”策略:简单任务走轻量模型,复杂规划与代码生成走强力模型,重构类任务走具备长上下文能力的模型。这套策略在实践里约莫能省一半的 token 开销,效果显著。
3.3 插件机制与生态扩展:不只是“填个入口”
跨平台生态想健康发展,光靠主仓库里那点工具是不够的,它要允许第三方开发者扩展能力。我们的插件系统就是为此设计的。
插件本质上是一个“工具包”,声明自己提供哪些工具、依赖哪些前置条件。系统在启动时扫描插件目录,加载所有满足条件的工具注册到能力层。插件声明文件极其精简,只有三部分:名称与版本、依赖列表、工具定义集合。
这里有一个值得分享的经验:插件系统一定要做“能力冲突检测”。我们曾经遇到两个插件各自注册了一个“代码格式化工具”,名字一样, schema 也高度相似,结果命令分派时出现歧义,智能体随机调用其中一个,行为不可预测。后来加了一个互斥检测规则:同名字工具,后注册的必须报错,除非显式声明“override”。这个机制在生态变大以后几乎是必须的。
4. 实操记录:从零搭建一个跨平台编程智能体
4.1 核心代码骨架:Agent 主循环
理论的架构讲完了,落到代码上才是检验是否真懂。贴一段我们主循环的逻辑伪代码,展示了智能体引擎最核心的“思考-行动-观察”循环,这一段很短,却是整套系统的发动机:
async def agent_loop(session, max_steps=20): for step in range(max_steps): # 从上下文管理器读取最新状态 prompt = session.build_prompt() # 调用模型层获取下一步动作 action = await model_gateway.chat(session.model_config, prompt) if action.type == "finish": return action.result elif action.type == "tool_call": # 工具层执行前校验 schema 与安全策略 validator_result = await tool_registry.validate(action.tool_name, action.arguments) if validator_result.need_confirmation: confirmed = await session.request_user_confirm(action) if not confirmed: session.record_event("tool_rejected", reason="user_canceled") continue result = await tool_registry.execute(action.tool_name, action.arguments) # 关键:观察结果并记录,用于下一轮思考 session.add_observation(action.tool_call_id, result) elif action.type == "edit_code": # 编辑引擎执行 dry-run + 语法检查 edits = await edit_engine.apply(session.repo, action.edits) session.add_observation(action.tool_call_id, edits)这段主循环是整个引擎最小的完整闭包,就一个循环,三种动作。但生产环境中,这个循环周围围了一圈基础设施:上下文构建器、工具校验链、会话持久化、超时控制、可观测性埋点。
4.2 工程配置:一套配置适配多个平台
跨平台的工程实现里,配置格式的设计占了很大比重。我们最终采用了分层配置模型,从上到下依次覆盖:平台级默认配置、用户级配置、项目级配置、会话级配置。优先级高的配置覆盖低的。
举例说明。默认配置规定最大步数为 20,编辑器平台里用户改成了 50,但某个项目因为代码库太大,项目级配置文件又强制改回 20,会话级再叠加超时控制。这套覆盖机制让不同角色(平台维护者、用户、项目管理员)各管一段,互相不越权。
配置热更新也是生产必须。我们做了声明式配置监听,配置文件的 mtime 变化后自动重新加载,不需要重启服务。“改配置要重启”在单机工具里还能接受,在服务化部署的跨平台架构里完全不可行。
4.3 端侧集成:先跑通 CLI,再套编辑器壳子
我建议任何团队在做这类系统时都遵循一种开发顺序:先做 CLI 端,再做编辑器插件,最后做 Web 端。
CLI 端是最容易开发、最容易调试的切入点。把所有核心逻辑跑通了,再套壳几乎不需要改引擎。编辑器插件的前期开发中 90% 的调试其实都在 CLI 里完成。我们当时就靠 CLI 端做了几十轮回归测试,确认智能体行为稳定后才开始写编辑器 UI。
编辑器插件实现里最易出问题的环节是“流式 diff 渲染”。引擎推送一个编辑建议,客户端不能脑袋一热就全部接受、直接改文件,而应先渲染成预览 diff,用户确认后再应用。这条交互链路一定要服务端有“编辑会话”的概念与之对应。简单说,服务端要缓存待应用的编辑内容,在用户点击确认时,通过一个 api key 或 token 来精确导回要应用的变更,避免重复应用或应用错版本。
我自己见过太多项目在编辑器插件上翻车,因为图省事直接“全盘替换文件”,最终用户代码库一片混乱。这恰恰是体现一个跨平台智能体工程化程度的分水岭。
4.4 可观测性设计:智能体行为全过程留痕
智能体系统的调试难度比传统应用高出一个量级。传统应用逻辑是确定性的,出了问题可以断点调试;而智能体的决策依赖模型输出,同一输入可能走完全不同的路径。没有全链路日志,排查问题基本靠猜。
我们为引擎内置了一个事件槽机制,所有消息都以结构化日志落盘,每条日志都带 session_id、request_id、step_index。日志内容包含运行时信息、提示词摘要、模型返回原始数据、工具参数和执行结果。前端界面还有一个“上帝视角”面板,展示当前智能体正在想什么、调了什么工具、结果如何。这个功能在演示时非常抓眼球,在开发时则是救命稻草。
三个最常用到的排查字段是:
- 提示词长度:判断上下文是否异常膨胀
- 工具调用耗时:找出哪一步是性能瓶颈
- 模型返回的 raw text:观察是模型输出的 bug 还是我们解析 bug
注意:智能体项目里一定要在生产环境把“人审日志”做实。用户确认过什么、拒绝过什么,必须可检索、可审计,这既是功能需求,也是工程底线。
5. 落地过程中的坑与排查方法
5.1 上下文窗口溢出:不是只有“删历史”这一种解法
这个基本是每个做智能体的人都逃不掉的坑。我们的多轮长任务里,模型跑着跑着就报上下文超限。一开始我们的对策简单粗暴:截断最老的会话记录。但这招在遇到“早期决策影响后续所有步骤”的情况时,智能体会突然失忆,行为完全跑偏。
后来我们改成“关键信息抽帧”:当上下文快满时,不直接丢历史,而是对早期内容做一次摘要压缩,把团队决策、当前任务目标、已完成步骤列表等关键信息提取成结构化摘要,替换掉原始长文本。实测比简单截断效果好太多。
上下文溢出还有另一个隐藏来源:工具执行结果。就“读取文件内容”这种工具,如果文件特别大,其返回值会瞬间膨胀几万 token。我们在工具层加了一个“返回长度上限”,超出部分自动截断,只保留文件头尾与行数统计。
5.2 工具调用失败:失败不是罪,无响应才是
智能体调用工具失败是我们日志系统里出现频率最高的类型。我总结下来,失败原因基本分三类:
第一类,入参不对。模型按照 schema 传参数,但传了个不存在的路径。对策是在工具层做“路径纠偏”,如果路径不存在就自动搜索相似路径并提示可选项。
第二类,工具本身报错。比如代码编译失败、命令返回非零退码。这一类不应视为“系统错误”,而应该把错误输出返回给模型,让模型自己调整。我们的原则是:只要工具自身逻辑靠得住,把异常作为观察结果喂回去,反而能让智能体学会自我修正。
第三类,并发冲突。多个智能体同时编辑同一个文件,结果文件内容互相覆盖。我们的实现中引入了一个简单的按路径粒度加锁的机制,一个文件同时只允许一个智能体编辑,其他智能体需要排队等待或切换到只读模式。
另外有一个排查小技巧:在日志里检索“tool_not_found”比“解析错误”更能暴露系统问题。这类错误一旦发生,说明工具的注册生命周期有问题,而不是模型的问题,换句话说,这是引擎的 bug,请示模型也没用。
5.3 多智能体并行协作:resource contention 比预想的多
跨平台生态跑起来以后,马上会遇到多智能体并发请求同一代码仓库的情况。最典型的是:用户开着三个终端窗口,每个窗口起了一个智能体任务,它们同时想把不同功能模块写进同一个仓库。
并行带来的直接问题是 git 索引冲突、文件锁竞争、甚至模型请求被限流。我们的取舍是:仓库层面的协作,不做“全仓库锁”,而是做“轻量化分区锁”。即锁的粒度是文件、模块或指定路径前缀。例如,任务 A 锁了“auth 模块”,任务 B 可以放心编辑“payment 模块”,互不阻塞。
同时,我们在服务端加了“任务看板”,集中展示每个智能体任务占用的资源路径、当前状态与阻塞关系。这既服务了用户可见性需求,也简化了运维排查。
5.4 平台差异的兼容性演练
不同平台的文本编码、换行符、路径分隔符、环境变量注入方式千奇百怪。连“写文件”这种操作,编辑器插件可能期望写完之后立刻刷新底层的 language server 索引,CLI 可能期望输出增量状态,Web 端则要推送到远程仓库。
为避免“在 A 平台正常、在 B 平台报错”的尴尬,我们建立了一套“行为契约测试”:针对通用操作,比如读取文件、写入文件、运行命令、生成 diff,每个平台适配器都必须通过同一套基准测试,由 CI 在每次改动后自动跑。这套契约测试虽然前期增加了开发工作量,但它把跨平台适配从“口头约定”变成了“机器验证”。
写在最后的工程心得
如果把整套跨平台 AI 编程智能体生态的工程实践浓缩成一条经验,我会说:智能体的能力上限由模型决定,但系统的可靠性上限完全由架构决定。协议先行、线程清晰的分层、严格的安全边界与全面留痕的可观测性,这四根柱子撑住整个生态,缺一根都会在落地阶段加倍偿还。
我至今印象最深的一次教训是早期我们贪快,跳过协议抽象直接让编辑器插件“私有化”调引擎内部接口。上线三个版本迭代后,Web 端想接入时发现编辑器相关的逻辑早已渗进引擎深处,迫使我们花了一整个迭代来剥离开体。从那时候起,团队立了一条规矩:任何跨端能力,必须有协议层支持,没有协议层就先补协议层再写功能。
如果你正打算做一个 AI 编程智能体或者类似的智能体基础设施,你可以从最小的 CLI+单模型+单工具集起步,先把“思考-行动-观察”循环跑通。但请在架构上预留好协议层、插件层和可观测性的位置,否则生态变大时重构成本远超你的预期。这是我踩出来的路,希望你再走时能少绕几个弯。