AI Coding Platform 六层架构设计:对标 Claude Code 的企业落地之路
2024 年,Claude Code / Cursor / Devin 把 AI 编程推到新高度。但企业不能直接用------安全、成本、定制化都是硬约束。本文记录我们如何设计一个企业级 AI 原生编程平台,以及每一层为什么存在。
一、企业为什么不能直接用 Claude Code
先说结论:Claude Code 是优秀的产品,但企业场景有三个硬伤。
| 维度 | Claude Code | 企业需求 |
|---|---|---|
| 代码安全 | 代码发送到 Anthropic 服务器 | 代码不能出内网 |
| 模型选择 | 只能用 Claude | 需要对接内部模型 / 国产模型 |
| 成本管控 | 按 Token 计费,不可控 | 需要配额、审批、成本归因 |
| 流程集成 | 独立工具 | 需要对接内部 GitLab、飞书、Jenkins |
| 权限管控 | 本地文件系统全部可访问 | 需要限制 Agent 可操作范围 |
所以我们的目标不是"复制 Claude Code",而是"在企业约束下提供同等能力"。
二、六层架构总览
yaml
┌─────────────────────────────────────────────────────────────────┐
│ Layer 1: Web 工作台 │
│ React + Monaco Editor,三栏布局(对话/文件树/编辑器+预览) │
├─────────────────────────────────────────────────────────────────┤
│ Layer 2: Copilot Service (:8003) │
│ 对话治理 · 智能路由 · 配额管控 · SSE 适配 · 沙箱代理 │
├─────────────────────────────────────────────────────────────────┤
│ Layer 3: Gateway (:7080) │
│ JWT/API Key 鉴权 · 路由分发 · SSE 长连接透传 │
├─────────────────────────────────────────────────────────────────┤
│ Layer 4: Manager (:7081) │
│ Agent/Tool/Model/Workflow 配置管理 · 版本控制 │
├─────────────────────────────────────────────────────────────────┤
│ Layer 5: Runtime (:7082) │
│ ReAct Loop · DAG 调度 · Multi-Agent · 上下文压缩 · 工具执行 │
├─────────────────────────────────────────────────────────────────┤
│ Layer 6: Infrastructure │
│ Docker Sandbox · Model Gateway · RAG · MCP Servers · PostgreSQL │
└─────────────────────────────────────────────────────────────────┘
为什么是六层而不是三层
最初方案是三层(前端 / BFF / Agent 执行)。实际跑起来发现:
- BFF 和 Copilot 职责重叠 --- Copilot 本身已经做了路由、编排、配额,再加 BFF 变成三层转发
- Manager 和 Runtime 不能合并 --- 改 Agent 配置(低频)和执行 Agent(高频高资源)混在一起,互相影响
- Infrastructure 必须独立 --- 沙箱、模型网关、RAG 都是有状态服务,生命周期与上层不同
每一层存在的理由都是"职责不同导致运维需求不同"。
三、Layer 2 深入:Copilot 为什么是核心
3.1 Copilot 不是 BFF,是"智能入口层"
Copilot 做的事情远超一个普通 BFF:
python
class CopilotOrchestrator:
"""Copilot 核心编排逻辑"""
async def handle_message(self, user_msg: str, session: Session):
# 阶段 1:配额检查
if not await self.quota_manager.check(session.tenant_id):
yield QuotaExceededEvent()
return
# 阶段 2:四阶段智能路由
route = await self.routing_engine.decide(
message=user_msg,
history=session.history,
app_type=session.app_type # coding_platform
)
# 阶段 3:按路由结果分发
match route.decision:
case "simple_qa":
# 简单问题 Copilot 自己回答,不走 Runtime
async for chunk in self.direct_answer(user_msg):
yield chunk
case "coding":
# 编程任务转发到 Runtime
async for event in self.forward_to_runtime(route.agent_id, user_msg):
yield self.adapt_sse_format(event)
case "prd":
# 需求任务走 PRD Agent
async for event in self.forward_to_runtime(route.prd_agent_id, user_msg):
yield chunk
3.2 四阶段路由决策
路由不是简单的关键词匹配,而是四阶段逐步收敛:
| 阶段 | 做什么 | 示例 |
|---|---|---|
| 1. 会话状态检查 | 是否有进行中的任务需要恢复 | 上次 coding 中断了→resume |
| 2. 意图分类 | LLM 判断属于哪类任务 | "帮我写个登录页面"→coding |
| 3. Agent 匹配 | 按意图选择最合适的 Agent | coding → Coding Agent |
| 4. 执行模式决策 | 单 Agent / Coordinator / Workflow | 简单需求→单 Agent |
为什么 Copilot 做路由而不是 Runtime? 因为路由是"决策",Runtime 是"执行"。决策需要看全局(配额、历史、用户偏好),执行只需要看当前任务。
3.3 Trade-off:多一跳值不值
Copilot 和 Runtime 之间多了一次 HTTP 调用,增加约 5ms 延迟。
换来的收益:
- Copilot 可以拦截危险请求(不到 Runtime 就终止)
- Copilot 可以做 SSE 格式适配(Runtime 输出的原始事件 → 前端友好格式)
- Copilot 可以合并多个 Runtime 的结果(未来多 Agent 并行场景)
- Copilot 挂了不影响 Runtime 正在执行的任务(反向也成立)
对于一次 AI 编程交互(通常 10-60 秒),5ms 可以忽略。
四、三阶段解耦编排
4.1 为什么不用一个 Agent 搞定全部
最初尝试过"全能 Coding Agent"------一个 Agent 负责从理解需求到写代码到测试。问题:
- Prompt 膨胀 --- 系统提示词超过 8K tokens,什么都想让它做,什么都做不好
- 无法迭代 --- 用户说"需求不对"要从头来,之前的代码工作全白费
- 不可控 --- 用户不知道 Agent 当前在做什么,是在分析需求还是在写代码
4.2 三阶段设计
arduino
用户输入需求
│
▼
┌──────────────────┐
│ PRD Agent │ "帮我把需求说清楚"
│ · 澄清模糊点 │
│ · 输出结构化 PRD │ ← 用户可以在这反复迭代
│ · 用户确认后传递 │
└────────┬─────────┘
│ 定稿的 PRD
▼
┌──────────────────┐
│ Spec Agent │ "帮我出技术方案"
│ · 生成 spec.md │
│ · 生成 plan.md │ ← 用户可以在这反复迭代
│ · 生成 tasks.md │
└────────┬─────────┘
│ 三文档
▼
┌──────────────────┐
│ Coding Agent │ "按方案写代码"
│ · 按 task 逐个实现│
│ · 编译验证 │ ← 报错自动修复
│ · 测试通过 │
└──────────────────┘
4.3 关键设计:编排权归 Copilot
三个 Agent 之间没有直接调用关系。流转由 Copilot 控制:
python
# Copilot 阶段机(简化)
class PhaseStateMachine:
async def transition(self, session, user_msg):
current_phase = session.phase # prd / spec / coding / debug
if current_phase == "prd" and self.is_prd_confirmed(session):
# PRD 确认后,自动进入 Spec 阶段
session.phase = "spec"
prd_bundle = await self.prd_client.get_bundle(session.id)
return SpecAgentRequest(prd=prd_bundle)
elif current_phase == "spec" and self.is_spec_approved(session):
# Spec 三文档确认后,进入 Coding 阶段
session.phase = "coding"
spec_docs = await self.spec_client.get_docs(session.id)
return CodingAgentRequest(spec=spec_docs)
为什么不用固定 DAG 串联? 因为用户需要在任意阶段"退回去"------说了半天发现需求不对,要回到 PRD 阶段重新来。固定 DAG 做不到这种灵活性。
五、四级上下文压缩
AI 编程最大的成本杀手不是模型调用次数,而是上下文长度。一次复杂的编程对话可能累积 100K+ tokens 的历史。
5.1 四级管道
yaml
Token 占用增长
│
│ ┌─── 阈值 1 (60%) ──→ Level 1: Snip Compact
│ │ 找到 snip 标记,删除标记前的消息
│ │ 零 API 调用,最轻量
│ │
│ ├─── 阈值 2 (75%) ──→ Level 2: Micro Compact
│ │ 利用 API cache editing,
│ │ 删除特定工具调用的结果(保留结构)
│ │
│ ├─── 阈值 3 (85%) ──→ Level 3: Context Collapse
│ │ 将多轮工具调用折叠为摘要,
│ │ 原始消息保留(读时投影)
│ │
│ └─── 阈值 4 (95%) ──→ Level 4: Auto Compact
│ LLM 生成对话摘要替换全部历史,
│ 最重但也是最后防线
▼
5.2 设计原则:渐进降级
- 能不调 API 就不调 --- Level 1/2 零额外 Token 消耗
- 能保留结构就保留 --- Level 3 折叠而非删除,需要时可展开
- 最后才做全量摘要 --- Level 4 不可逆,信息必然丢失
5.3 实测数据
| 场景 | 无压缩 | 有压缩 | 节省 |
|---|---|---|---|
| 10 轮简单对话 | 12K tokens | 12K(未触发) | 0% |
| 30 轮编程会话 | 85K tokens | 38K tokens | 55% |
| 50 轮复杂重构 | 180K tokens | 72K tokens | 60% |
六、五层安全防御
6.1 为什么 AI Agent 比传统 API 更危险
传统 API:参数固定,行为可预测。 AI Agent:模型决定调什么工具、传什么参数,行为不可预测。
一个真实案例:Coding Agent 在修复 bug 时,决定"清理测试数据",执行了 rm -rf /tmp/test_*。恰好 /tmp/test_data_production_backup 也匹配了这个模式。
6.2 五层设计
bash
请求进入
│
▼ L1: 静态黑名单
│ rm -rf / | dd if=/dev/zero | mkfs | > /etc/passwd
│ → 直接拒绝,不经过模型判断
│
▼ L2: 工具白名单(per-Agent)
│ Coding Agent 只能用: read_file, write_file, run_command, search
│ 不能用: deploy, delete_branch, send_message
│
▼ L3: 人机协同模式
│ auto: 低风险操作自动执行
│ ask: 中风险操作弹确认
│ strict: 所有操作都需确认
│
▼ L4: 风险分类器
│ 基于命令内容自动判断风险等级
│ write_file("/src/...") → 低风险 → auto
│ run_command("git push --force") → 高风险 → ask
│
▼ L5: 审计日志
所有工具调用记录:who/what/when/result
支持事后审查与回溯
6.3 SSE 中断确认的实现
当 L3/L4 判定需要用户确认时:
css
Runtime ──SSE──→ Copilot ──SSE──→ Frontend
│
[弹出确认框]
│
用户点击"允许"
│
Frontend ──POST──→ Copilot ──POST──→ Runtime
│
[继续执行]
Runtime 在等待确认期间不释放会话,用 asyncio.Event 挂起当前执行流:
python
class PermissionGate:
async def request_confirmation(self, action: ToolAction) -> bool:
# 发送确认请求事件
await self.sse_writer.write(ConfirmationRequestEvent(action=action))
# 挂起等待,最多 5 分钟
try:
result = await asyncio.wait_for(self.confirmation_event.wait(), timeout=300)
return result.approved
except asyncio.TimeoutError:
return False # 超时视为拒绝(Fail Closed)
七、Docker 沙箱执行
7.1 为什么需要沙箱
Coding Agent 需要真实执行代码(编译、运行测试、启动预览),但不能直接操作宿主机。
7.2 沙箱生命周期
创建 ──→ 运行 ──→ 暂停 ──→ 恢复 ──→ 销毁
│ │
│ N分钟无活动 │ 用户回来
└──────────────┘
- 创建:从模板镜像启动(node-20 / python-312),<3s
- 暂停 :
docker pause,释放 CPU 但保留内存状态 - 恢复 :
docker unpause,<1s,用户无感 - 销毁:TTL 到期(默认 30min 无活动)自动回收
7.3 资源限制
yaml
# 每个沙箱的资源限制
resources:
cpu: "2" # 最多 2 核
memory: "2Gi" # 最多 2GB 内存
disk: "5Gi" # 最多 5GB 磁盘
network: "bridge" # 隔离网络,只能访问内网白名单
pids: 256 # 最多 256 个进程(防 fork bomb)
八、回到起点:5ms 换来了什么
六层架构的总网络开销:前端→Copilot→Gateway→Runtime,约 12-15ms。
对比一次 LLM 调用的延迟(3-30 秒),这 15ms 占比 < 0.5%。
但换来的是:
- 任何一层挂了,其他层不受影响
- Copilot 可以独立迭代路由逻辑(不碰 Runtime)
- Runtime 可以独立扩缩容(不影响 Manager)
- 安全层可以拦截在 Copilot(不需要到 Runtime 才发现问题)
- 未来可以接入多个 Runtime(不同语言的沙箱、不同模型的 Agent)
架构决策的核心不是"怎么做最快",而是"怎么做最可持续"。
下一篇:双层知识图谱如何让 Bug 定位从 50K Token 降到 15K。