ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Coding Agent 架构设计:从 Agent Loop 到插件系统,拆解一个 AI 编程助手的核心模块

2026/8/7 16:37:29 拓冰建站 浏览量
Coding Agent 架构设计:从 Agent Loop 到插件系统,拆解一个 AI 编程助手的核心模块 Coding Agent 架构设计从 Agent Loop 到插件系统拆解一个 AI 编程助手的核心模块1. 概述Coding Agent 的本质不复杂–一个在问模型和执行工具之间循环的 while 循环。但把这个循环变成每天能用的开发工具需要一层层加上会话管理、上下文压缩、资源加载、插件系统等模块。现代 Coding Agent 的架构核心思想是三层分离模型适配层屏蔽不同 LLM 的差异、内核层Agent Loop 运行时、产品层会话/压缩/资源等循环之外的事。各层独立、松耦合通过接口或协议通信可以独立替换实现。理解了 Coding Agent 的构建原理就掌握了理解其他业务 Agent 的一把钥匙–客服、数据分析、工作流编排等业务 Agent追根溯源都是 Coding Agent 的泛化变种。2. Agent Loop一切的核心Agent Loop 是什么一个 while 循环在问模型和执行工具之间来回切换直到模型给出最终答案。它不关心模型是 OpenAI 还是 Anthropic不关心工具是读文件还是跑命令只关心两件事模型要不要调工具如果要调完继续问如果不要结束。核心逻辑简化后while (turn max_turns) { // 1. 调模型 assistant model.complete(messages, tools) // 2. 检查是否有工具调用 if (assistant.toolCalls().len 0) break // 3. 逐个执行工具结果 append 回 messages for (tool_call in assistant.toolCalls()) { result tool_registry.execute(tool_call.name, tool_call.args) messages.append(result) } }三个关键设计点max_turns防止模型陷入无限工具循环的安全阀。模型可能反复调用工具却始终不给出最终答案上限是必要的。工具结果 append 回 messages模型需要看到工具执行的结果才能决定下一步做什么。如果不把结果回写模型就像蒙着眼睛干活。工具定义传进 complete()模型需要提前知道有哪些工具可用、每个工具的参数是什么才能决定是否调用。Agent Loop 还需要处理错误重试。LLM 调用和工具调用都有失败的可能需要区分错误类型可重试的错误网络超时、临时限流自动重试不可重试的错误参数校验失败、权限不足直接抛出。Agent Loop 只关心循环不关心消息从哪里来、执行结果存到哪里。这些循环之外的事由产品层封装。3. 模型适配层统一不同 LLM Provider 的差异模型适配层的职责只有一句话把不同 LLM Provider 的 API 差异封装在一个统一接口后面。Agent Loop 只认这个接口不关心背后是 OpenAI 还是 Anthropic。统一接口需要处理的关键差异差异点OpenAIAnthropic请求体格式messages[] 数组content[] 数组工具调用结构tool_calls[] 独立字段content[] 中的 tool_use block流式协议data: 行event: 行适配层把这些差异统一成 Message、Tool、AssistantMessageEvent 等内部数据结构。上层 Agent Loop 不需要知道这是 Anthropic 的 tool_use 还是 OpenAI 的 function call只关心统一后的 toolCall 内容块。LLM 生成一个回答可能需要几秒甚至十几秒如果等全部生成完再返回用户只能干等。解决方案是流式输出SSE模型适配器在收到 SSE 的每个 chunk 时调用 stream_callback 回调Agent Loop 收到回调后立即通过 EventBus 发射事件Client 收到事件后实时追加到终端显示。4. 工具系统Agent 的手和脚模型适配层让 Agent Loop 可以调任何模型工具系统让 Agent Loop 可以做任何事。工具的定义包含四个要素name工具名、description描述告诉模型这个工具能做什么、parametersJSON Schema描述参数结构、execute执行函数。工具注册表是一个 HashMap按工具名存储支持三个核心操作register注册新工具、definitions返回所有工具定义发送给模型、execute按名称查找并执行。模型在每次调用 complete() 时注册表会把所有可用工具的 JSON Schema 编码后传给模型模型根据描述决定是否调用某个工具。内置工具通常包括read读文件、write写文件、edit精确修改、bash执行命令、grep搜索、glob文件匹配。扩展工具通过插件系统动态注册。5. 产品层循环之外的事写一个 Agent Loop 不难难的是把它变成每天能用的开发工具。产品层负责这些麻烦但关键的事情会话管理、上下文压缩、资源加载。5.1 会话管理Session没有 SessionAgent 每次对话都是失忆的。存储格式采用 JSONL每行一个独立 JSON 对象。第一行是会话头id、创建时间、工作目录后续每行是一条消息。{id:sess_001,created_at:1717234567,cwd:/project,model:gpt-4o} {id:1,parent_id:null,timestamp:1,role:user,content:帮我读 README.md} {id:2,parent_id:1,timestamp:2,role:assistant,content:我来帮你读...}每条消息带 parent_id构成树状对话结构支持分支 fork 和回滚–走错方向时可以回到之前的节点重新开一条分支。会话恢复时逐行解析损坏的行跳过不崩溃。这是工程上的健壮性设计–JSONL 文件可能因为进程崩溃而出现半行写入解析时遇到损坏行应该跳过而不是整体失败。5.2 上下文压缩CompactionLLM 有上下文窗口限制一个 Coding Agent 的对话可能持续几十轮累积数千 Token。如果不做处理早期消息会被窗口截断模型忘记了之前的上下文。压缩策略当 Token 超过阈值时把旧消息压缩成一条摘要保留最近 N 条消息不变。压缩前: [消息1] [消息2] [消息3] ... [消息N-10] [消息N-9] ... [消息N] 压缩后: [摘要: 之前讨论的要点] [消息N-9] [消息N-8] ... [消息N]核心设计要点Token 预算不引入精确 tokenizer用字符数除以 4 近似估算。压缩决策不需要精确到个位数 Token够用就行。触发条件估算的消息 Token 总量超过配置的 max_tokens 时触发。摘要生成把旧消息拼接成文本调用模型生成摘要替换掉原始消息。重试循环压缩后重新执行 Agent Loop如果仍然超限继续压缩。默认阈值 100K Token保留最近 10 条消息摘要目标长度 500 Token。5.3 资源加载ResourcesResources 从文件系统加载项目规则和技能格式化后注入 system prompt让模型知道它有哪些工具和能力可用。三类资源的加载顺序和优先级项目规则优先级从高到低当前目录的 AGENTS.md - 当前目录的 CLAUDE.md - 全局配置目录的 AGENTS.md - 全局配置目录的 CLAUDE.md技能项目优先同名冲突时项目级覆盖全局级当前目录的 .agent/skills/ - 当前目录的 .agents/skills/ - 全局配置目录的 skills/数据结构每个 Skill 包含 name、description、filePath、sourceglobal 或 project、contentSKILL.md 文件携带 YAML frontmatter解析后构建成 XML 结构注入到系统提示词中告诉模型当前有哪些技能可用available_skillsskillnamebasedpyright/namedescriptionPython static type checking/descriptionlocation/path/to/skill/SKILL.md/location/skill/available_skills模型看到技能列表后如果判断当前任务需要某个技能就会主动读取该 SKILL.md 的完整内容。这就是渐进式披露的实践–初始只给目录用到时再加载详情。6. 事件系统与插件机制Agent Loop 在跑但外界怎么知道它跑到了哪一步事件系统就是答案。Agent Loop 每做一件事–开始一轮、生成一个 Token、调一个工具–就往 EventBus 上发一个事件。谁关心这个事件谁就注册回调。EventBus 有三个回调槽agent / session / compaction插件安装时把原回调保存下来换成自己的 dispatch 包装函数。执行时先执行原回调写 socket 流式返回给客户端再遍历所有已注册的插件逐个调用对应的 hook 函数。插件支持的 Hook 类型Hook触发时机可做操作on_tool_start执行工具前拦截危险命令、修改参数on_tool_end工具执行后修改执行结果、标记错误已处理on_contextLLM 调用前注入系统指令on_agent_startAgent 启动时阻止启动on_session_before_compact手动压缩前阻止压缩插件的优势在于即插即用、动态加载。一个 bash-guard 插件可以在 on_tool_start 时拦截rm -rf这样的危险命令在 on_context 时注入使用 bash 时注意安全的系统指令不需要修改核心代码。7. 网络层与客户端通信网络层定义了 Server 与 Client 之间如何通信。核心设计TCP JSON line 协议流式通信 全双工。TCP 天生适合这个场景–客户端发一条消息服务端把思考过程、工具调用、最终回答一条条推送给客户端全双工意味着客户端可以随时发消息比如中断请求服务端也可以随时推送事件。语言无关是网络层的核心价值。Server 不关心 Client 用什么语言实现只要遵循 JSON line 协议就行。这意味着引擎层可以用追求性能的语言如 Zig实现客户端可以用生态丰富的语言如 Python实现终端交互 UI各取所长。8. 总结Coding Agent 的核心架构可以归纳为六个模块Agent Loop核心循环在问模型和执行工具之间切换模型适配层统一不同 LLM Provider 的 API 差异工具系统注册表管理工具支持动态扩展产品层会话管理JSONL 树结构、上下文压缩摘要 重试、资源加载规则 技能事件系统EventBus 驱动支持插件动态加载网络层TCP JSON line语言无关各层之间通过接口或协议解耦可以独立替换实现。理解了这套架构模式就掌握了理解其他业务 Agent 的一把钥匙。