Files
ocp/PLAN-v4.md
T
taodengandClaude Opus 4.6 1f1b3871da 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>
2026-03-25 08:02:53 +10:00

440 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 死了上层不死。**