mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
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>
440 lines
14 KiB
Markdown
440 lines
14 KiB
Markdown
# 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 adapter(Claude CLI、OpenAI 兼容、Ollama),接口开放,社区可贡献更多。
|
||
|
||
OpenAI 兼容的服务(DeepSeek、Groq、Together 等)直接复用 `OpenAiApiAdapter`,只需换 baseUrl + apiKey,不算新 adapter。
|
||
|
||
---
|
||
|
||
## 分阶段实施
|
||
|
||
### Phase 1: 后端抽象 + Model Registry(v4.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 Control(v4.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 官方 API(v4.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 CLI(core)
|
||
│ ├── 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 backend,phase 严格拆分 |
|
||
|
||
## 优先级
|
||
|
||
| 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 死了上层不死。**
|