feat: Phase 1 — modular backend architecture (v4.0-alpha)

Internal refactor: extract BackendAdapter, ModelRegistry, AgentRouter,
SessionManager, and StatsCollector as standalone modules. Claude CLI
logic encapsulated in ClaudeCliAdapter.

External API completely unchanged — all existing endpoints behave
identically. Legacy request path (callClaude/callClaudeStreaming)
untouched. v4 modules load in parallel for validation.

New endpoints:
  GET /backends  — backend health and status
  GET /routing   — agent routing table

Architecture:
  unified-chunk.mjs    — streaming protocol (delta/done/error)
  backends/base.mjs    — BackendAdapter interface
  backends/claude-cli.mjs — Claude CLI adapter (core)
  model-registry.mjs   — model → backend mapping (Layer 1)
  agent-router.mjs     — agent policy routing (Layer 2)
  session-manager.mjs  — conversation session management
  stats-collector.mjs  — per-model/backend/agent metrics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 08:02:53 +10:00
co-authored by Claude Opus 4.6
parent f6f4f68ecd
commit 1f1b3871da
9 changed files with 1639 additions and 6 deletions
+439
View File
@@ -0,0 +1,439 @@
# OCP v4.0 开发计划 — Agent 成本控制器
## 背景
OCP 当前架构:`Tool → OCP (localhost:3456) → Claude CLI → Anthropic`
核心风险:Claude CLI proxy 属于灰色地带,Anthropic 随时可能封堵。
核心资产(不依赖 CLI 的部分):
- OpenAI 兼容 API 层
- Session 管理(会话隔离、TTL、复用)
- Usage 监控(plan limits、per-model stats、实时查询)
- 运行时调参(timeout、并发、prompt 限制)
- `/ocp` 命令集(Telegram/Discord/CLI
- 多实例并发控制
## 定位
> OCP is a cost-control and model-routing layer for AI agents, optimized for subscription-based usage.
**不是 proxy,不是 gateway,不是 LiteLLM clone。**
**是:让 Agent 用模型这件事变得可控 — 尤其是成本。**
核心差异点:OCP 有 agent 上下文,知道是谁在请求、在做什么任务、已经花了多少。LiteLLM 只看到 HTTP 请求。
## 目标
将 OCP 从 "Claude CLI proxy" 重构为 "Agent 成本控制器",支持可扩展的多后端,保留全部管理能力。
---
## ⚠️ 第零原则:不动现有功能
OCP 作为 Claude CLI proxy 是当前的核心价值和用户吸引力。所有 v4 重构必须遵守:
- **外部 API 不变** — `/v1/chat/completions``/usage``/health``/settings` 签名不变
- **行为不变** — 现有用户升级后零感知,所有 session/timeout/并发逻辑保持原样
- **逐步替换** — 先抽模块,跑稳了再切;任何时候回归测试不过就回滚
- **新 backend 是增量** — 加 OpenAI/Ollama 是新能力,不改动 Claude CLI 路径
Phase 1 的定义就是:**拆完之后跟没拆一样。** 拆出问题就说明拆法有误。
---
## 架构关键决策(三轮 Design Review 定稿)
### 决策 1: 路由分两层 — Model Registry + Agent Policy
**三轮讨论结论:**
- 第一轮建议"删掉 model routing" → 错误,非 agent 客户端(Cline/Aider)只带 model 名
- 反驳:model-to-backend mapping 是基础设施,不是 routing rule
- 校准:物理拆成两个模块,不要混在一起
**最终设计 — 两层分离:**
```
🧱 Layer 1: Model Registry(基础设施层)
model-registry.mjs
静态映射,无条件,必须存在
"claude-sonnet-4-6" → claude-cli backend
"gpt-4o" → openai backend
"qwen3" → ollama backend
🧠 Layer 2: Agent Policy(策略层)
agent-router.mjs
动态决策,可覆盖,可选
agent "coder" → override to claude-cli
agent "interpreter" → override to ollama
```
**路由决策顺序(写死):**
```
1. Agent policy override → 有 agent identity 且有配置?用它
2. Model registry → 模型属于哪个 backend?路由过去
3. Default backend → 都没匹配?全局默认
4. Fallback chain → 上面选的挂了?降级
```
### 决策 2: 统一 Streaming 协议
**所有 adapter 必须输出统一的内部 chunk 格式:**
```javascript
{
type: "delta" | "done" | "error",
content?: string,
model: string,
backend: string,
usage?: { promptTokens: number, completionTokens: number }
}
```
server.mjs 只处理统一协议 → 转换为 OpenAI SSE 输出。
### 决策 3: 成本标记 — actual / estimated / free
```javascript
{
cost: {
value: 0.23,
type: "actual" | "estimated" | "free"
}
}
```
| Backend | cost type | 来源 |
|---------|-----------|------|
| OpenAI API | actual | API 返回 usage |
| Claude CLI | estimated | prompt chars × 估价 |
| Ollama | free | 本地运行 |
`/ocp cost` 输出明确标注类型,不误导用户。
### 决策 4: Fallback — 只在 First-Byte 前触发
```javascript
const allowFallback = !hasStartedStreaming;
```
Phase 3 严格限制:已输出 ≥1 个 delta → 不 fallback,直接报错。
flag 预留,未来扩展 streaming fallback 只改判断条件。
### 决策 5: Agent Identity — 结构预留,实现从简
**优先级:**
```
1. x-ocp-agent header → OpenClaw 自动附加
2. Session metadata → 首次请求绑定,后续复用
3. (future) Inferred → 按使用模式推断(预留接口)
4. default → 兜底
```
**现在只实现 1 + 2 + 4。** 但接口预留 inferred 扩展点。
为什么重要:未来 Cline coding mode 和 chat mode 可能需要不同路由,如果没有 identity hook 以后很难加。
---
## Backend Adapter 设计
### 接口
```javascript
class BackendAdapter {
get id() // "claude-cli" | "openai-api" | "ollama"
get displayName()
get models()
get costType() // "actual" | "estimated" | "free"
get tier() // "core" | "community"
async healthCheck() { ok, message, latencyMs }
async *chatCompletion(request) AsyncGenerator<UnifiedChunk>
async initialize(config)
async shutdown()
}
```
### Backend Tier 体系
OCP provides a pluggable backend adapter interface. Core adapters are maintained officially; community adapters are contributed by users.
```
/ocp backends 输出示例:
claude-cli ✓ healthy 12ms (core)
openai ✓ healthy 89ms (core)
ollama ✓ healthy 5ms (core)
deepseek ✓ healthy 120ms (community)
```
架构不限制 backend 数量。我们主动维护 3 个 core adapterClaude CLI、OpenAI 兼容、Ollama),接口开放,社区可贡献更多。
OpenAI 兼容的服务(DeepSeek、Groq、Together 等)直接复用 `OpenAiApiAdapter`,只需换 baseUrl + apiKey,不算新 adapter。
---
## 分阶段实施
### Phase 1: 后端抽象 + Model Registryv4.0-alpha
**目标:** 不改变外部行为,内部重构
- [ ] 定义 `BackendAdapter` 接口 + `UnifiedChunk` 类型
- [ ] `model-registry.mjs` — 模型到 backend 的静态映射
- `getBackendForModel(model) → backendId`
- [ ] `agent-router.mjs` — 路由决策引擎
- `resolveBackend({ agent, model }) → backendId`
- 实现决策顺序:agent override → model registry → default → fallback
- [ ] 将 Claude CLI 逻辑封装为 `ClaudeCliAdapter`
- stdout → UnifiedChunk 转换
- costType = "estimated"
- [ ] 抽取 `SessionManager`(从 server.mjs
- [ ] 抽取 `StatsCollector`per-agent, per-backend
- [ ] Config v4 格式(带 version + validation):
```json
{
"version": "4.0",
"backends": {
"claude-cli": {
"type": "claude-cli",
"tier": "core",
"enabled": true,
"models": ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
"maxConcurrent": 4,
"timeout": { "firstByte": 120000, "overall": 300000 },
"estimatedCostPerMTok": { "input": 3.0, "output": 15.0 }
}
},
"agents": {
"default": { "preferred": "claude-cli", "fallback": null }
}
}
```
- [ ] 启动时 config validation
- [ ] `/ocp backends` 命令
- [ ] 所有现有测试通过,行为不变
**验收标准:** 现有用户升级后零感知变化
### Phase 2: OpenAI API 后端(v4.0-beta
**目标:** 第二个 backend,验证 adapter 抽象
- [ ] `OpenAiApiAdapter`
- SSE → UnifiedChunk
- costType = "actual"
- 支持任何 OpenAI 兼容端点(baseUrl + apiKey
- [ ] Model registry 自动注册新 backend 的 models
- [ ] Agent routing 生效:
```json
{
"agents": {
"default": { "preferred": "claude-cli", "fallback": "openai" },
"interpreter": { "preferred": "openai/gpt-4o-mini", "fallback": null }
}
}
```
- [ ] Non-streaming fallback`allowFallback = !hasStartedStreaming`
- [ ] Stats 按 backend + agent 分组
**验收标准:** claude-cli 挂了 → default agent 自动 fallback 到 openai
### Phase 3: Ollama + Agent Routing 稳定化(v4.0-rc
**目标:** 摆脱付费依赖,agent routing 跑稳
- [ ] `OllamaAdapter`
- 自动发现模型
- costType = "free"
- [ ] Agent routing 完整覆盖所有 agent
```json
{
"agents": {
"main": { "preferred": "claude-cli/opus", "fallback": "openai" },
"tech_geek": { "preferred": "claude-cli/sonnet", "fallback": "ollama/qwen3" },
"interpreter": { "preferred": "ollama/qwen3", "fallback": null }
}
}
```
- [ ] `/ocp routing` — 显示每个 agent 当前走哪个 backend
- [ ] 端到端测试:Claude CLI 下线 → agent 自动 fallback → 恢复后自动回切
**验收标准:** 三个 backend 同时运行,每个 agent 走不同路径,稳定无错
### Phase 4: Budget + Cost Controlv4.1
**目标:** 核心差异化 — agent-aware 成本控制
**前置条件:** Phase 3 routing 稳定(先确定流量怎么走 → 再控制花多少钱)
**安全原则:** 没有显式配置 budget 的 agent → 行为等同于 v3(不限制、不降级、不告警)。Budget 是 opt-in,不是 opt-out。
- [ ] Per-agent 预算:
```json
{
"agents": {
"main": {
"preferred": "claude-cli/opus",
"fallback": "openai",
"dailyBudget": { "limit": 3.00, "onExhausted": "downgrade" }
}
},
"budget": {
"global": { "daily": 10.00, "onExhausted": "warn" }
}
}
```
- [ ] 超预算行为:downgrade | block | warn
- [ ] `/ocp cost` — per-agent 成本(标注 actual/estimated/free
- [ ] Telegram 告警(预算 80%、backend 连续失败)
### Phase 5: Anthropic 官方 APIv4.2,等时机)
- [ ] `AnthropicApiAdapter`(官方 SDK
- [ ] 迁移指南:一行配置切换
- [ ] costType = "actual"
---
## 文件结构(目标)
```
claude-proxy/
├── server.mjs → HTTP 层(统一 chunk → SSE 输出)
├── model-registry.mjs → 模型注册表(model → backend 映射)
├── agent-router.mjs → Agent 路由引擎(策略层)
├── session-manager.mjs → 会话管理
├── stats-collector.mjs → 统计 + 成本追踪
├── config.mjs → 配置加载 + 版本验证
├── unified-chunk.mjs → UnifiedChunk 类型定义
├── backends/
│ ├── base.mjs → BackendAdapter 接口
│ ├── claude-cli.mjs → Claude CLIcore
│ ├── openai-api.mjs → OpenAI 兼容(core
│ └── ollama.mjs → Ollama 本地(core
├── setup.mjs → 安装向导
└── ocp-plugin/ → Gateway 命令插件
```
---
## 配置完整示例(目标状态)
```json
{
"version": "4.0",
"port": 3456,
"backends": {
"claude-cli": {
"type": "claude-cli",
"tier": "core",
"enabled": true,
"models": ["claude-sonnet-4-6", "claude-opus-4-6"],
"maxConcurrent": 4,
"timeout": { "firstByte": 120000, "overall": 300000 },
"estimatedCostPerMTok": { "input": 3.0, "output": 15.0 }
},
"openai": {
"type": "openai-api",
"tier": "core",
"enabled": true,
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "${OPENROUTER_API_KEY}",
"models": ["anthropic/claude-3.5-sonnet", "google/gemini-2.0-flash"],
"maxConcurrent": 10,
"timeout": { "firstByte": 30000, "overall": 120000 }
},
"ollama": {
"type": "ollama",
"tier": "core",
"enabled": true,
"baseUrl": "http://localhost:11434",
"models": "auto"
}
},
"agents": {
"default": {
"preferred": "claude-cli",
"fallback": "openai"
},
"main": {
"preferred": "claude-cli/claude-opus-4-6",
"fallback": "openai",
"dailyBudget": { "limit": 3.00, "onExhausted": "downgrade" }
},
"tech_geek": {
"preferred": "claude-cli/claude-sonnet-4-6",
"fallback": "ollama/qwen3",
"dailyBudget": { "limit": 2.00, "onExhausted": "fallback" }
},
"interpreter": {
"preferred": "ollama/qwen3",
"fallback": null
}
},
"fallback": {
"onlyBeforeFirstByte": true,
"triggers": ["timeout", "rate-limit", "connection-error"],
"maxRetries": 1
},
"budget": {
"global": { "daily": 10.00, "onExhausted": "warn" },
"alerts": { "threshold": 0.8, "channel": "telegram" }
},
"sessions": { "ttl": 3600000, "maxPromptChars": 150000 },
"monitoring": { "retainHours": 72 }
}
```
---
## 风险与应对
| 风险 | 应对 |
|------|------|
| Claude CLI 被封 | 切换 backend,上层不变 |
| Anthropic 出订阅 API | 实现 anthropic-api adapter,成本控制层继续有价值 |
| LiteLLM 竞争 | 不竞争。OCP 做 agent-aware 成本控制,赛道不同 |
| 成本数据不准 | cost type 标记 actual/estimated/free |
| Streaming fallback 拼接 | Phase 3 只做 non-streaming fallback |
| 过度工程 | 只维护 3 个 core backendphase 严格拆分 |
## 优先级
| Phase | 时间 | 内容 | 交付物 |
|-------|------|------|--------|
| 1 | 本周 | 重构:adapter 接口 + model registry + agent router | 行为不变,架构就位 |
| 2 | 1-2 周 | OpenAI backend | 两个 backend 跑通 |
| 3 | 2-3 周 | Ollama + Agent routing 稳定化 | 三 backend + per-agent 路由 |
| 4 | 按需 | Budget + Cost control | 成本可控 |
| 5 | 等时机 | Anthropic 官方 API | 逃生通道 |
## 成功标准
1. Claude CLI 被封的那天,改一行配置,所有 agent 继续运行
2. `/ocp cost` 每个 agent 花了多少(actual/estimated/free
3. 翻译 agent 自动用 Ollama$0),coding agent 用 Claude
4. 超预算自动降级,不需人工干预
5. 新 backend 只需实现 BackendAdapter 接口 + 一个文件
## 设计审核记录
| 轮次 | 审核方 | 关键结论 |
|------|--------|----------|
| 第一轮 | AI-A | 定位从 proxy 转向成本控制器;识别 5 个 P0 问题 |
| 第二轮 | Claude(本项目开发者) | 反驳:不能删 model routing(基础设施层);backend 数量是运营决策不是架构约束;budget 拆到 Phase 4 |
| 第三轮 | AI-A | 校准:model registry 与 routing 物理拆开两个模块;agent identity 结构预留 inferred 扩展点;backend 加 tier 标记 |
## 一句话总结
**短期靠套利活(Claude CLI proxy),长期靠控制力活(Agent 成本控制器)。两条腿走路,CLI 死了上层不死。**