diff --git a/docs/framework.md b/docs/framework.md new file mode 100644 index 0000000..8b7adb9 --- /dev/null +++ b/docs/framework.md @@ -0,0 +1,567 @@ +# coding-proxy 架构设计与工程方案 + +## 1. 项目概述 + +### 1.1 项目动机与背景 + +Claude Code 作为日常 AI 编程助手,其底层依赖 Anthropic Messages API (`/v1/messages`)。在实际使用过程中,以下场景时有发生: + +- **限流 (Rate Limiting)**:高频请求触发 `429 rate_limit_error`,导致短时间内无法继续使用 +- **配额耗尽 (Usage Cap)**:月度/日度配额用尽后返回 `403` 错误,含 "usage cap" 等提示 +- **服务过载 (Overloaded)**:Anthropic 服务端高峰期返回 `503 overloaded_error` +- **网络波动**:国际链路不稳定导致连接超时 + +与此同时,智谱 (Zhipu) 提供了与 Anthropic 兼容的 GLM API 接口(`/api/anthropic`),为构建备用通道提供了可能。 + +**coding-proxy 的核心诉求**:在主 API 不可用时自动、无缝地切换到备用后端,对 Claude Code 客户端完全透明,用户无需手动干预。 + +### 1.2 设计目标 + +| 目标 | 说明 | +|------|------| +| **透明代理** | 对 Claude Code 完全透明,客户端无需修改任何协议或配置(仅需指定代理地址) | +| **自动故障转移** | 检测到主后端异常时自动切换备用后端,并在主后端恢复后自动切回 | +| **可观测性** | Token 用量持久化追踪、后端状态实时查询、熔断器状态监控 | +| **可扩展性** | 易于添加新后端实现、新模型映射规则、新故障转移策略 | +| **轻量部署** | 单进程运行,仅依赖 SQLite(无外部数据库/消息队列),适合本地开发环境 | + +--- + +## 2. 系统架构总览 + +### 2.1 架构分层图 + +``` +┌─────────────────────────────────────────────────┐ +│ Claude Code 客户端 │ +└────────────────────┬────────────────────────────┘ + │ HTTP POST /v1/messages +┌────────────────────▼────────────────────────────┐ +│ FastAPI Server (server/app.py) │ +│ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ RequestRouter (routing/router.py) │ │ +│ │ │ │ +│ │ ┌──────────────────┐ ┌───────────────────┐ │ │ +│ │ │ CircuitBreaker │ │ TokenLogger │ │ │ +│ │ │ (circuit_breaker │ │ (logging/db.py) │ │ │ +│ │ │ .py) │ │ │ │ │ +│ │ └──────────────────┘ └───────────────────┘ │ │ +│ └────────┬──────────────────────┬──────────────┘ │ +│ │ Primary │ Fallback │ +│ ┌────────▼─────────┐ ┌────────▼──────────────┐ │ +│ │ AnthropicBackend │ │ ZhipuBackend │ │ +│ │ (backends/ │ │ (backends/zhipu.py) │ │ +│ │ anthropic.py) │ │ + ModelMapper │ │ +│ └────────┬─────────┘ └────────┬──────────────┘ │ +└───────────┼──────────────────────┼────────────────┘ + │ │ + ┌────────▼─────────┐ ┌────────▼──────────────┐ + │ Anthropic API │ │ 智谱 GLM API │ + │ (官方) │ │ (Anthropic 兼容接口) │ + └──────────────────┘ └───────────────────────┘ +``` + +### 2.2 模块职责一览 + +| 模块 | 路径 | 职责 | +|------|------|------| +| **server** | `server/app.py` | 应用工厂、HTTP 端点定义、生命周期管理 | +| **backends** | `backends/` | 后端抽象基类与具体实现(Anthropic、Zhipu) | +| **routing** | `routing/` | 请求路由、熔断器状态机、模型名称映射 | +| **config** | `config/` | Pydantic 配置模型定义与 YAML 加载 | +| **logging** | `logging/` | Token 用量 SQLite 持久化与统计查询 | +| **cli** | `cli.py` | Typer 命令行入口(start、status、usage、reset) | + +### 2.3 技术选型 + +| 技术 | 选型理由 | +|------|---------| +| **Python 3.13+** | 原生 async/await 成熟、类型提示完善、生态丰富 | +| **FastAPI** | 原生异步、`StreamingResponse` 支持 SSE、自动 OpenAPI 文档 | +| **httpx** | 同时支持同步/异步、流式请求、完整的 HTTP 客户端功能 | +| **Pydantic v2** | 配置校验与类型安全、性能显著优于 v1 | +| **aiosqlite** | 异步 SQLite 访问、WAL 模式支持并发读写 | +| **Typer + Rich** | 现代化 CLI 体验、类型安全的参数声明、美观的终端输出 | +| **UV** | 极速包管理器、lockfile 确保可复现构建 | + +--- + +## 3. 设计模式详解 + +### 3.1 Template Method(模板方法模式) + +> **经典出处**:GoF《Design Patterns: Elements of Reusable Object-Oriented Software》— 定义算法骨架,将某些步骤延迟到子类实现。 + +**应用位置**:`backends/base.py` — `BaseBackend` 抽象基类 + +**设计要点**: + +`BaseBackend` 定义了请求处理的算法骨架,将差异化的逻辑延迟到子类: + +``` +BaseBackend(模板) +├── send_message() ← 固定流程:prepare → send → parse response +├── send_message_stream() ← 固定流程:prepare → stream → yield chunks +├── _get_client() ← 公共逻辑:惰性初始化 httpx.AsyncClient +├── close() ← 公共逻辑:关闭 HTTP 客户端 +│ +├── _prepare_request() ← 【抽象】子类实现请求转换 +├── get_name() ← 【抽象】子类返回后端标识 +└── should_trigger_failover() ← 【抽象】子类定义故障转移条件 +``` + +两个具体子类的差异化实现: + +| 方法 | AnthropicBackend | ZhipuBackend | +|------|-----------------|--------------| +| `_prepare_request()` | 过滤 hop-by-hop 头,透传 OAuth token | 映射模型名称,替换为 API Key 认证 | +| `should_trigger_failover()` | 检查 status_codes + error_types + message_patterns | 始终返回 `False`(最终后端) | + +### 3.2 Circuit Breaker(熔断器模式) + +> **经典出处**:Martin Fowler "CircuitBreaker" (2014);Michael Nygard《Release It! Design and Deploy Production-Ready Software》第 5 章 — 通过快速失败防止级联故障。 + +**应用位置**:`routing/circuit_breaker.py` — `CircuitBreaker` 类 + +**状态机**: + +``` + 连续 N 次失败 + ┌────────┐ ──────────────────→ ┌────────┐ + │ CLOSED │ │ OPEN │ ◄──┐ + │ (正常) │ │ (熔断) │ │ + └────────┘ └───┬────┘ │ + ▲ │ │ + │ 连续 M 次成功 超时恢复 │ │ 失败(退避×2) + │ ▼ │ + │ ┌──────────┐ │ + └───────────────────────│HALF_OPEN │─────┘ + │ (试探) │ + └──────────┘ +``` + +**状态转换条件**: + +| 转换 | 条件 | 默认值 | +|------|------|--------| +| CLOSED → OPEN | 连续失败次数 ≥ `failure_threshold` | 3 次 | +| OPEN → HALF_OPEN | 距上次失败 ≥ `recovery_timeout_seconds` | 300 秒 | +| HALF_OPEN → CLOSED | 连续成功次数 ≥ `success_threshold` | 2 次 | +| HALF_OPEN → OPEN | 任意一次失败 | — | + +**指数退避 (Exponential Backoff)**:每次从 HALF_OPEN 回退到 OPEN 时,恢复等待时间翻倍(`recovery_timeout *= 2`),上限为 `max_recovery_seconds`(默认 3600 秒)。避免对仍未恢复的后端频繁重试。 + +**线程安全**:所有状态变更通过 `threading.Lock` 保护,确保并发请求下状态一致。 + +### 3.3 Strategy(策略模式) + +> **经典出处**:GoF《Design Patterns》— 定义一系列算法,将它们封装起来并使它们可互换。 + +**应用位置**:`routing/model_mapper.py` — `ModelMapper` 类 + +**设计要点**: + +ModelMapper 采用三级匹配策略链,按优先级依次尝试: + +``` +输入模型名称 + │ + ▼ + 1. 精确匹配(Exact Match) + │ pattern == model(无通配符、非正则) + ├─ 命中 → 返回 target + │ + ▼ + 2. 模式匹配(Regex / Glob Match) + │ is_regex=true → re.fullmatch() + │ 含 * → fnmatch.fnmatch() + ├─ 命中 → 返回 target + │ + ▼ + 3. 默认值(Default Fallback) + └─ 返回 "glm-5.1" +``` + +**默认映射规则**: + +| 模式 | 目标 | 类型 | +|------|------|------| +| `claude-sonnet-.*` | `glm-5.1` | 正则 | +| `claude-opus-.*` | `glm-5.1` | 正则 | +| `claude-haiku-.*` | `glm-4.5-air` | 正则 | +| `claude-.*` | `glm-5.1` | 正则(兜底) | + +正则表达式在 `__init__` 时预编译(`re.compile()`),`map()` 调用时直接使用编译后的对象,避免重复编译开销。 + +### 3.4 Factory Method(工厂方法模式) + +> **经典出处**:GoF《Design Patterns》— 定义创建对象的接口,由子类决定实例化哪个类。 + +**应用位置**:`server/app.py` — `create_app()` 函数 + +**组装顺序**: + +``` +create_app(config) + │ + ├─ 1. TokenLogger(config.db_path) + ├─ 2. ModelMapper(config.model_mapping) + ├─ 3. AnthropicBackend(config.primary, config.failover) + ├─ 4. ZhipuBackend(config.fallback, config.failover, mapper) + ├─ 5. CircuitBreaker(config.circuit_breaker.*) + ├─ 6. RequestRouter(primary, fallback, cb, token_logger) + │ + └─ 7. FastAPI(lifespan=lifespan) + ├─ app.state.router = router + ├─ app.state.token_logger = token_logger + └─ app.state.config = config +``` + +通过 `lifespan` 异步上下文管理器管理应用生命周期: +- **启动**:初始化 TokenLogger(创建数据库表) +- **关闭**:关闭 RequestRouter(释放 HTTP 客户端)和 TokenLogger(关闭数据库连接) + +### 3.5 Proxy(代理模式) + +> **经典出处**:GoF《Design Patterns》— 为其他对象提供一种代理以控制对这个对象的访问。 + +**应用位置**:整体架构 + +coding-proxy 本身即是一个代理服务: + +- 对外暴露与 Anthropic Messages API 完全兼容的 `POST /v1/messages` 接口 +- Claude Code 客户端只需将 `ANTHROPIC_BASE_URL` 指向代理地址 +- 代理在幕后完成后端选择、故障转移、模型映射、用量记录等增值逻辑 +- 支持流式(SSE `text/event-stream`)和非流式(JSON)两种响应模式 + +--- + +## 4. 请求生命周期 + +### 4.1 完整请求流程 + +``` +Client POST /v1/messages + │ + ▼ + app.messages() 解析 body + headers + │ + ├─ stream=true ──→ route_stream() + └─ stream=false ─→ route_message() + │ + ▼ + CircuitBreaker.can_execute()? + │ + ┌───────┴───────┐ + │ Yes │ No (OPEN) + ▼ │ + Primary Backend │ + │ │ + ┌────┴────┐ │ + │ 成功 │ 失败 │ + │ │ │ + │ record │ should │ + │ success │ failover?│ + │ │ │ + │ ┌────┴───┐ │ + │ │Yes │No │ + │ │ │ │ + │ │record │return │ + │ │failure│error │ + │ │ │ │ + │ ▼ ▼ │ + │ ┌─────────────┐ │ + │ │ │◄──┘ + │ │ Fallback │ + │ │ Backend │ + │ │ │ + │ └──────┬──────┘ + │ │ + ▼ ▼ + TokenLogger.log() + │ + ▼ + Response → Client +``` + +### 4.2 流式请求处理 + +流式请求使用 `StreamingResponse` + 异步生成器 `_stream_proxy()`: + +1. `RequestRouter.route_stream()` 返回 `AsyncIterator[tuple[bytes, str]]` +2. 每个 SSE chunk 通过 `_parse_usage_from_chunk()` 提取 Token 用量: + - `message_start` 事件:提取 `input_tokens`、`cache_creation_input_tokens`、`cache_read_input_tokens`、`request_id` + - `message_delta` 事件:提取 `output_tokens` +3. chunk 原样透传给客户端 +4. 流结束后记录完整用量到 TokenLogger + +**故障转移时**:清空已收集的 usage 数据(`usage.clear()`),从 Fallback Backend 重新开始流式传输。 + +### 4.3 非流式请求处理 + +非流式请求直接调用 `send_message()` 获取完整响应: + +1. 主后端返回成功(`status_code < 400`)→ 记录 success → 返回 +2. 主后端返回错误 → 检查 `should_trigger_failover()`: + - 是 → `record_failure()` → 尝试 Fallback + - 否 → 直接返回错误响应 +3. 捕获 `httpx.TimeoutException` / `httpx.ConnectError` → 触发故障转移 + +### 4.4 故障转移判定逻辑 + +故障转移的判定在 `AnthropicBackend.should_trigger_failover()` 中实现,依据三层条件(可通过配置文件自定义): + +| 层级 | 条件 | 默认值 | +|------|------|--------| +| HTTP 状态码 | `status_code in failover.status_codes` | `[429, 403, 503, 500]` | +| 错误类型 | `error.type in failover.error_types` | `["rate_limit_error", "overloaded_error", "api_error"]` | +| 错误消息 | `pattern in error.message`(不区分大小写) | `["quota", "limit exceeded", "usage cap", "capacity"]` | + +**特殊规则**:对于 429 和 503 状态码,即使无法解析响应体(body),也会强制触发故障转移。 + +**ZhipuBackend** 作为最终备选后端,`should_trigger_failover()` 始终返回 `False`。 + +--- + +## 5. 模块详细设计 + +### 5.1 backends/ — 后端模块 + +**数据结构**: + +```python +@dataclass +class UsageInfo: + input_tokens: int = 0 + output_tokens: int = 0 + cache_creation_tokens: int = 0 + cache_read_tokens: int = 0 + request_id: str = "" + +@dataclass +class BackendResponse: + status_code: int = 200 + usage: UsageInfo = field(default_factory=UsageInfo) + is_streaming: bool = False + raw_body: bytes = b"{}" + error_type: str | None = None + error_message: str | None = None +``` + +**AnthropicBackend**: +- 过滤 hop-by-hop 头(`host`、`content-length`、`transfer-encoding`、`connection`) +- 透传客户端的 OAuth token(`authorization` 头) +- 不修改请求体 + +**ZhipuBackend**: +- 浅拷贝请求体(`{**request_body}`),避免修改原始数据 +- 调用 `ModelMapper.map()` 映射模型名称 +- 替换认证头为 `x-api-key` +- 保持 `anthropic-version` 头兼容 + +**HTTP 客户端管理**: +- 惰性初始化 `httpx.AsyncClient`(首次调用时创建) +- 自动检测并重建已关闭的客户端(`is_closed` 检查) +- `close()` 方法释放连接资源 + +### 5.2 routing/ — 路由模块 + +**RequestRouter**: +- 持有四个依赖:`primary`、`fallback`、`circuit_breaker`、`token_logger` +- `route_message()` → 非流式路由,返回 `BackendResponse` +- `route_stream()` → 流式路由,yield `(chunk, backend_name)` +- `_record_usage()` → 统一记录用量到 TokenLogger + +**CircuitBreaker 参数**: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `failure_threshold` | int | 3 | 触发 OPEN 的连续失败次数 | +| `recovery_timeout_seconds` | int | 300 | OPEN → HALF_OPEN 等待秒数 | +| `success_threshold` | int | 2 | HALF_OPEN → CLOSED 所需连续成功次数 | +| `max_recovery_seconds` | int | 3600 | 指数退避上限秒数 | + +### 5.3 config/ — 配置模块 + +**配置搜索优先级**: + +``` +CLI --config 参数(显式指定) + ↓ 未指定时 +./config.yaml(项目根目录) + ↓ 不存在时 +~/.coding-proxy/config.yaml(用户目录) + ↓ 不存在时 +Pydantic 默认值 +``` + +**环境变量展开**: +- 语法:`${VARIABLE_NAME}` +- 实现:正则 `\$\{([^}]+)\}` 匹配,递归处理 dict/list/str +- 未定义的变量保留原始 `${VAR}` 文本 +- 适用场景:API Key 等敏感信息不宜写入配置文件 + +### 5.4 logging/ — 日志模块 + +**usage_log 表结构**: + +| 列名 | 类型 | 说明 | +|------|------|------| +| `id` | INTEGER PK | 自增主键 | +| `ts` | TEXT | 时间戳(ISO 8601 格式,UTC) | +| `backend` | TEXT | 后端标识(`"anthropic"` / `"zhipu"`) | +| `model_requested` | TEXT | 客户端请求的模型名称 | +| `model_served` | TEXT | 实际使用的模型名称 | +| `input_tokens` | INTEGER | 输入 Token 数 | +| `output_tokens` | INTEGER | 输出 Token 数 | +| `cache_creation_tokens` | INTEGER | 缓存创建 Token 数 | +| `cache_read_tokens` | INTEGER | 缓存读取 Token 数 | +| `duration_ms` | INTEGER | 请求耗时(毫秒) | +| `success` | BOOLEAN | 是否成功 | +| `failover` | BOOLEAN | 是否经过故障转移 | +| `request_id` | TEXT | Anthropic 请求 ID | + +**索引**:`idx_usage_ts`(时间戳)、`idx_usage_backend`(后端名) + +**SQLite 优化**:WAL (Write-Ahead Logging) 模式,支持读写并发而不互相阻塞。 + +### 5.5 server/ — 服务模块 + +**API 端点**: + +| 端点 | 方法 | 说明 | +|------|------|------| +| `/v1/messages` | POST | 代理 Anthropic Messages API(流式 + 非流式) | +| `/health` | GET | 健康检查,返回 `{"status": "ok"}` | +| `/api/status` | GET | 熔断器状态、主/备后端信息 | +| `/api/reset` | POST | 手动重置熔断器为 CLOSED | + +--- + +## 6. 配置系统设计 + +### 6.1 完整配置字段参考 + +**server — 服务器配置** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `host` | str | `"127.0.0.1"` | 监听地址 | +| `port` | int | `8046` | 监听端口 | + +**primary — 主后端(Anthropic)配置** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `enabled` | bool | `true` | 是否启用 | +| `base_url` | str | `"https://api.anthropic.com"` | API 基础地址 | +| `timeout_ms` | int | `300000` | 请求超时(毫秒),默认 5 分钟 | + +**fallback — 备选后端(智谱)配置** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `enabled` | bool | `true` | 是否启用 | +| `base_url` | str | `"https://open.bigmodel.cn/api/anthropic"` | 智谱 Anthropic 兼容接口地址 | +| `api_key` | str | `""` | 智谱 API Key,支持 `${ENV_VAR}` 引用 | +| `timeout_ms` | int | `3000000` | 请求超时(毫秒),默认 50 分钟 | + +**circuit_breaker — 熔断器配置** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `failure_threshold` | int | `3` | 触发熔断的连续失败次数 | +| `recovery_timeout_seconds` | int | `300` | 熔断后恢复等待时间(秒) | +| `success_threshold` | int | `2` | 半开状态恢复所需连续成功次数 | +| `max_recovery_seconds` | int | `3600` | 指数退避最大恢复时间(秒) | + +**failover — 故障转移触发条件** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `status_codes` | list[int] | `[429, 403, 503, 500]` | 触发转移的 HTTP 状态码 | +| `error_types` | list[str] | `["rate_limit_error", "overloaded_error", "api_error"]` | 触发转移的错误类型 | +| `error_message_patterns` | list[str] | `["quota", "limit exceeded", "usage cap", "capacity"]` | 触发转移的消息关键词 | + +**model_mapping — 模型名称映射规则** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `pattern` | str | 匹配模式(支持精确匹配、glob 通配符、正则表达式) | +| `target` | str | 目标模型名称 | +| `is_regex` | bool | 是否为正则表达式(默认 `false`) | + +**database — 数据库配置** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `path` | str | `"~/.coding-proxy/usage.db"` | SQLite 数据库文件路径 | + +**logging — 日志配置** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `level` | str | `"INFO"` | 日志级别 | +| `file` | str \| null | `null` | 日志文件路径(null 输出到控制台) | + +--- + +## 7. 可扩展性设计 + +### 7.1 添加新后端 + +1. 在 `backends/` 下创建新模块,继承 `BaseBackend` +2. 实现三个抽象方法: + - `get_name()` — 返回后端标识字符串 + - `_prepare_request()` — 转换请求体和请求头 + - `should_trigger_failover()` — 定义何时触发故障转移 +3. 在 `config/schema.py` 中添加对应的配置模型 +4. 在 `server/app.py` 的 `create_app()` 中实例化并注册 + +### 7.2 添加新映射规则 + +在配置文件的 `model_mapping` 节添加规则即可,无需修改代码: + +```yaml +model_mapping: + - pattern: "claude-sonnet-4-6" # 精确匹配 + target: "glm-5.1" + - pattern: "claude-haiku-.*" # 正则匹配 + target: "glm-4.5-air" + is_regex: true + - pattern: "claude-*" # Glob 通配符 + target: "glm-5.1" +``` + +匹配优先级确保精确匹配不会被通配符覆盖。 + +### 7.3 自定义故障转移策略 + +通过配置文件调整 `failover` 节的三个字段即可自定义触发条件: + +- 增减 `status_codes` 控制哪些 HTTP 状态码触发切换 +- 增减 `error_types` 控制哪些 Anthropic 错误类型触发切换 +- 增减 `error_message_patterns` 控制哪些错误消息关键词触发切换 + +如需更复杂的策略,可重写子类的 `should_trigger_failover()` 方法。 + +--- + +## 8. 测试策略 + +### 8.1 单元测试覆盖 + +| 测试文件 | 覆盖范围 | +|---------|---------| +| `test_circuit_breaker.py` | 状态转换(CLOSED→OPEN→HALF_OPEN→CLOSED)、恢复超时、指数退避、手动重置 | +| `test_model_mapper.py` | 精确匹配、正则匹配、Glob 匹配、默认回退、空规则集 | +| `test_backends.py` | 请求头过滤、模型映射、故障转移判断、数据类默认值 | +| `test_config_loader.py` | 配置文件搜索优先级、环境变量展开、缺失文件处理 | + +### 8.2 测试工具 + +- **pytest** (>=9.0) — 测试框架 +- **pytest-asyncio** (>=1.3) — 异步测试支持 +- **monkeypatch** — 环境变量和工作目录隔离 +- **tmp_path** — 临时文件测试 diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 0000000..8006e2e --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,695 @@ +# coding-proxy 用户操作指引 + +## 1. 简介 + +### 1.1 什么是 coding-proxy + +coding-proxy 是一个面向 Claude Code 的多后端智能代理服务。它在 Claude Code 和 API 后端之间充当透明代理,具备以下核心能力: + +- **自动故障转移**:Anthropic API 不可用时自动切换到智谱 GLM 后端,恢复后自动切回 +- **模型名称映射**:自动将 Claude 模型名转换为对应的 GLM 模型名 +- **Token 用量追踪**:记录每次请求的 Token 消耗、后端选择、响应时间等指标 +- **熔断器保护**:智能检测后端健康状态,避免反复请求失败的后端 + +### 1.2 工作原理 + +``` +Claude Code ──→ coding-proxy ──→ Anthropic API(主后端) + │ + └──→ 智谱 GLM API(故障时自动切换) +``` + +正常情况下,coding-proxy 将请求透传到 Anthropic API。当检测到限流、配额耗尽或服务过载等错误时,自动切换到智谱 GLM 备用后端。后端恢复后,代理会自动尝试切回主后端。整个过程对用户透明,无需手动干预。 + +--- + +## 2. 快速开始 + +### 2.1 环境要求 + +- **Python** >= 3.13 +- **UV** 包管理器(推荐)或 pip +- **智谱 API Key**:从 [open.bigmodel.cn](https://open.bigmodel.cn) 获取 +- **Claude Code** 已安装并可用 + +### 2.2 安装 + +```bash +# 方式一:使用 UV(推荐) +uv sync + +# 方式二:使用 pip +pip install -e . +``` + +安装完成后,`coding-proxy` 命令即可使用。 + +### 2.3 最小配置 + +```bash +# 复制配置模板到项目根目录 +cp config.example.yaml config.yaml +``` + +设置智谱 API Key(二选一): + +**方式一:环境变量(推荐)** + +```bash +export ZHIPU_API_KEY="your-api-key-here" +``` + +配置文件中使用 `${ZHIPU_API_KEY}` 引用,代理启动时自动替换。 + +**方式二:直接写入配置文件** + +编辑 `config.yaml`,将 `fallback.api_key` 设为实际的 API Key: + +```yaml +fallback: + api_key: "your-api-key-here" +``` + +> **安全提示**:`config.yaml` 已在 `.gitignore` 中,不会被提交到版本库。推荐使用环境变量方式避免密钥泄露。 + +### 2.4 启动服务 + +```bash +# 使用默认配置启动 +coding-proxy start + +# 指定端口 +coding-proxy start --port 8080 + +# 指定配置文件 +coding-proxy start --config /path/to/config.yaml + +# 自定义监听地址和端口 +coding-proxy start --host 0.0.0.0 --port 8046 +``` + +启动成功后会看到类似输出: + +``` +INFO: coding-proxy started: host=127.0.0.1 port=8046 +INFO: Uvicorn running on http://127.0.0.1:8046 +``` + +### 2.5 验证服务 + +```bash +# 健康检查 +curl http://127.0.0.1:8046/health +# 期望返回: {"status":"ok"} + +# 查看代理状态 +coding-proxy status +``` + +### 2.6 配置 Claude Code + +将 Claude Code 的 API 端点指向 coding-proxy: + +```bash +export ANTHROPIC_BASE_URL=http://127.0.0.1:8046 +``` + +设置后,Claude Code 发出的所有 API 请求将经过 coding-proxy 代理。 + +--- + +## 3. 配置详解 + +### 3.1 配置文件位置 + +配置文件按以下优先级加载(找到第一个即停止): + +1. `--config` 参数指定的路径(最高优先级) +2. `./config.yaml`(项目根目录) +3. `~/.coding-proxy/config.yaml`(用户主目录) +4. 内置默认值(无需配置文件也可启动) + +### 3.2 完整配置示例 + +```yaml +# 服务器配置 +server: + host: "127.0.0.1" # 监听地址 + port: 8046 # 监听端口 + +# 主后端 — Anthropic 官方 API +primary: + enabled: true + base_url: "https://api.anthropic.com" + timeout_ms: 300000 # 5 分钟 + +# 备选后端 — 智谱 GLM(Anthropic 兼容接口) +fallback: + enabled: true + base_url: "https://open.bigmodel.cn/api/anthropic" + api_key: "${ZHIPU_API_KEY}" # 通过环境变量注入 + timeout_ms: 3000000 # 50 分钟 + +# 熔断器配置 +circuit_breaker: + failure_threshold: 3 # 连续 3 次失败后触发熔断 + recovery_timeout_seconds: 300 # 熔断后等待 5 分钟尝试恢复 + success_threshold: 2 # 连续 2 次成功后关闭熔断 + max_recovery_seconds: 3600 # 指数退避上限 1 小时 + +# 故障转移触发条件 +failover: + status_codes: [429, 403, 503, 500] + error_types: + - "rate_limit_error" + - "overloaded_error" + - "api_error" + error_message_patterns: + - "quota" + - "limit exceeded" + - "usage cap" + - "capacity" + +# 模型名称映射(Claude → GLM) +model_mapping: + - pattern: "claude-sonnet-.*" + target: "glm-5.1" + is_regex: true + - pattern: "claude-opus-.*" + target: "glm-5.1" + is_regex: true + - pattern: "claude-haiku-.*" + target: "glm-4.5-air" + is_regex: true + +# 用量数据库 +database: + path: "~/.coding-proxy/usage.db" + +# 日志配置 +logging: + level: "INFO" +``` + +### 3.3 各配置节说明 + +#### server — 服务器 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `host` | string | `"127.0.0.1"` | 监听地址。设为 `"0.0.0.0"` 可接受外部连接 | +| `port` | int | `8046` | 监听端口 | + +#### primary — 主后端(Anthropic) + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `enabled` | bool | `true` | 是否启用主后端 | +| `base_url` | string | `"https://api.anthropic.com"` | Anthropic API 地址 | +| `timeout_ms` | int | `300000` | 请求超时,默认 5 分钟(300 秒) | + +#### fallback — 备选后端(智谱) + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `enabled` | bool | `true` | 是否启用备选后端 | +| `base_url` | string | `"https://open.bigmodel.cn/api/anthropic"` | 智谱 Anthropic 兼容接口地址 | +| `api_key` | string | `""` | 智谱 API Key,支持 `${ENV_VAR}` 引用 | +| `timeout_ms` | int | `3000000` | 请求超时,默认 50 分钟 | + +#### circuit_breaker — 熔断器 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `failure_threshold` | int | `3` | 连续失败多少次后触发熔断(切换到备选后端) | +| `recovery_timeout_seconds` | int | `300` | 熔断后等待多久尝试恢复(秒) | +| `success_threshold` | int | `2` | 恢复测试阶段需要连续成功多少次才关闭熔断 | +| `max_recovery_seconds` | int | `3600` | 指数退避的最大等待时间(秒) | + +**指数退避机制**:如果恢复测试失败,等待时间会翻倍(300s → 600s → 1200s → ...),直到达到 `max_recovery_seconds` 上限。 + +#### failover — 故障转移条件 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `status_codes` | list[int] | `[429, 403, 503, 500]` | 触发故障转移的 HTTP 状态码 | +| `error_types` | list[str] | `["rate_limit_error", "overloaded_error", "api_error"]` | 触发故障转移的 Anthropic 错误类型 | +| `error_message_patterns` | list[str] | `["quota", "limit exceeded", "usage cap", "capacity"]` | 触发故障转移的错误消息关键词(不区分大小写) | + +#### model_mapping — 模型映射规则 + +每条规则包含: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `pattern` | string | 匹配模式 | +| `target` | string | 目标模型名称 | +| `is_regex` | bool | 是否为正则表达式(默认 `false`) | + +**匹配优先级**:精确匹配 > 正则/通配符匹配 > 默认值 (`glm-5.1`) + +#### database — 数据库 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `path` | string | `"~/.coding-proxy/usage.db"` | SQLite 数据库文件路径,支持 `~` 展开 | + +#### logging — 日志 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `level` | string | `"INFO"` | 日志级别(DEBUG / INFO / WARNING / ERROR) | +| `file` | string \| null | `null` | 日志文件路径,`null` 表示输出到控制台 | + +### 3.4 环境变量引用 + +配置文件中可使用 `${VARIABLE_NAME}` 语法引用环境变量: + +```yaml +fallback: + api_key: "${ZHIPU_API_KEY}" +``` + +启动时,`${ZHIPU_API_KEY}` 会被替换为环境变量 `ZHIPU_API_KEY` 的值。如果环境变量未设置,保留原始文本 `${ZHIPU_API_KEY}`。 + +--- + +## 4. CLI 命令参考 + +### 4.1 coding-proxy start + +启动代理服务。 + +```bash +coding-proxy start [OPTIONS] +``` + +| 参数 | 缩写 | 说明 | +|------|------|------| +| `--config` | `-c` | 配置文件路径 | +| `--port` | `-p` | 监听端口(覆盖配置文件) | +| `--host` | `-h` | 监听地址(覆盖配置文件) | + +**示例**: + +```bash +# 默认配置启动 +coding-proxy start + +# 自定义端口和配置 +coding-proxy start -p 9000 -c ~/my-config.yaml +``` + +### 4.2 coding-proxy status + +查看当前代理状态和熔断器信息。 + +```bash +coding-proxy status [OPTIONS] +``` + +| 参数 | 缩写 | 说明 | +|------|------|------| +| `--port` | `-p` | 代理服务端口(默认 8046) | + +**输出示例**: + +``` +熔断器状态: closed +主后端: anthropic +备选后端: zhipu +连续失败次数: 0 +恢复超时(s): 300 +``` + +**熔断器状态说明**: + +| 状态 | 含义 | +|------|------| +| `closed` | 正常运行,使用主后端(Anthropic) | +| `open` | 熔断中,使用备选后端(智谱) | +| `half_open` | 恢复测试中,尝试使用主后端 | + +### 4.3 coding-proxy usage + +查看 Token 使用统计。 + +```bash +coding-proxy usage [OPTIONS] +``` + +| 参数 | 缩写 | 说明 | +|------|------|------| +| `--days` | `-d` | 统计天数(默认 7) | +| `--backend` | `-b` | 过滤指定后端(如 `anthropic` 或 `zhipu`) | +| `--db` | — | 数据库文件路径 | + +**示例**: + +```bash +# 查看最近 7 天统计 +coding-proxy usage + +# 查看最近 30 天 Anthropic 后端统计 +coding-proxy usage -d 30 -b anthropic +``` + +### 4.4 coding-proxy reset + +手动重置熔断器为 CLOSED 状态(恢复使用主后端)。 + +```bash +coding-proxy reset [OPTIONS] +``` + +| 参数 | 缩写 | 说明 | +|------|------|------| +| `--port` | `-p` | 代理服务端口(默认 8046) | + +**使用场景**:确认 Anthropic API 已恢复正常后,手动强制切回主后端,无需等待自动恢复超时。 + +### 4.5 HTTP API 端点 + +除 CLI 命令外,coding-proxy 还提供以下 HTTP 端点: + +#### POST /v1/messages + +代理 Anthropic Messages API,支持流式和非流式请求。 + +```bash +curl -X POST http://127.0.0.1:8046/v1/messages \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -d '{"model":"claude-sonnet-4-20250514","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}' +``` + +#### GET /health + +健康检查。 + +```bash +curl http://127.0.0.1:8046/health +# {"status":"ok"} +``` + +#### GET /api/status + +查询熔断器状态和后端信息。 + +```bash +curl http://127.0.0.1:8046/api/status +# {"circuit_breaker":{"state":"closed","failure_count":0,...},"primary":"anthropic","fallback":"zhipu"} +``` + +#### POST /api/reset + +手动重置熔断器。 + +```bash +curl -X POST http://127.0.0.1:8046/api/reset +# {"status":"ok","circuit_breaker":{"state":"closed",...}} +``` + +--- + +## 5. Claude Code 集成指南 + +### 5.1 配置 Claude Code 使用代理 + +启动 coding-proxy 后,设置环境变量让 Claude Code 通过代理发送请求: + +```bash +export ANTHROPIC_BASE_URL=http://127.0.0.1:8046 +``` + +Claude Code 使用的 OAuth token 会被代理透传到 Anthropic API,无需额外配置认证信息。 + +### 5.2 验证集成 + +1. 确保 coding-proxy 正在运行:`coding-proxy status` +2. 使用 Claude Code 发送一条消息 +3. 查看 coding-proxy 的终端日志,确认请求经过代理 +4. 使用 `coding-proxy usage` 查看是否有新的用量记录 + +### 5.3 日常使用流程 + +1. **启动代理**:`coding-proxy start`(可使用 `nohup` 或 `tmux` 后台运行) +2. **正常使用 Claude Code**:代理在后台透明工作 +3. **定期查看用量**:`coding-proxy usage` 了解 Token 消耗和后端分布 +4. **按需手动干预**:`coding-proxy reset` 在确认主后端恢复后强制切回 + +--- + +## 6. 常见使用场景 + +### 6.1 Anthropic API 限流自动切换 + +**现象**:Claude Code 响应变慢或提示 "rate limited" + +**代理行为**: +1. 检测到 Anthropic 返回 `429 rate_limit_error` +2. 熔断器记录失败,达到阈值后切换到 OPEN 状态 +3. 后续请求自动路由到智谱 GLM 后端 +4. 等待恢复超时后自动尝试切回 Anthropic + +**用户操作**:无需干预,代理自动处理。 + +### 6.2 配额耗尽后使用备用后端 + +**现象**:Anthropic 返回 `403` 错误,消息含 "usage cap" 或 "quota" + +**代理行为**: +1. 识别错误消息中的关键词("quota"、"usage cap") +2. 触发故障转移到智谱后端 +3. Claude Code 继续正常工作(使用 GLM 模型) + +**用户操作**:无需干预。可通过 `coding-proxy usage` 查看哪些请求走了备用后端。 + +### 6.3 手动切换后端 + +**场景**:确认 Anthropic API 已恢复,希望立即切回而不等待自动恢复。 + +```bash +# 重置熔断器 +coding-proxy reset + +# 确认状态 +coding-proxy status +# 熔断器状态: closed ← 已恢复使用主后端 +``` + +### 6.4 仅使用智谱后端 + +如果希望跳过 Anthropic API,始终使用智谱后端,可将熔断器阈值设为极低值: + +```yaml +circuit_breaker: + failure_threshold: 1 # 首次失败即切换 +``` + +或者将故障转移状态码设为空列表以完全禁用故障转移: + +```yaml +failover: + status_codes: [] +``` + +--- + +## 7. 监控与运维 + +### 7.1 日志查看 + +代理服务日志默认输出到控制台,包含以下关键事件: + +| 事件 | 日志级别 | 示例 | +|------|---------|------| +| 熔断器状态转换 | INFO/WARNING | `Circuit breaker: CLOSED → OPEN (3 consecutive failures)` | +| 故障转移触发 | WARNING | `Primary error 429, failing over` | +| 恢复成功 | INFO | `Circuit breaker: HALF_OPEN → CLOSED (recovered)` | +| 连接错误 | WARNING | `Primary connection error: ConnectTimeout` | + +可通过配置调整日志级别: + +```yaml +logging: + level: "DEBUG" # 查看详细的模型映射和路由决策 + file: "/var/log/coding-proxy.log" # 输出到文件 +``` + +### 7.2 用量统计 + +```bash +# 查看最近 7 天统计 +coding-proxy usage + +# 查看最近 30 天,仅 Anthropic 后端 +coding-proxy usage -d 30 -b anthropic + +# 查看最近 30 天,仅智谱后端 +coding-proxy usage -d 30 -b zhipu +``` + +统计字段说明: + +| 字段 | 说明 | +|------|------| +| 日期 | 统计日期 | +| 后端 | `anthropic` 或 `zhipu` | +| 请求数 | 当日总请求数 | +| 输入 Token | 当日总输入 Token 数 | +| 输出 Token | 当日总输出 Token 数 | +| 故障转移次数 | 当日经过故障转移的请求数 | +| 平均耗时 | 当日请求平均响应时间(毫秒) | + +### 7.3 健康检查 + +**基础检查**: + +```bash +curl http://127.0.0.1:8046/health +``` + +**详细状态**: + +```bash +curl http://127.0.0.1:8046/api/status +``` + +返回示例: + +```json +{ + "circuit_breaker": { + "state": "closed", + "failure_count": 0, + "success_count": 0, + "current_recovery_seconds": 300, + "last_failure_time": null + }, + "primary": "anthropic", + "fallback": "zhipu" +} +``` + +### 7.4 数据库维护 + +用量数据库位于 `~/.coding-proxy/usage.db`(可通过配置修改)。 + +- 数据库采用 SQLite WAL 模式,支持读写并发 +- 当前版本不自动清理历史数据 +- 如需清理,可直接删除数据库文件(重启后自动重建) + +--- + +## 8. 故障排查 + +### 8.1 代理服务无法启动 + +**端口占用**: + +```bash +lsof -i :8046 +# 如有进程占用,先停止或更换端口 +coding-proxy start --port 8080 +``` + +**配置文件语法错误**: + +检查 YAML 格式是否正确(缩进、冒号后的空格等)。常见错误: + +```yaml +# 错误:冒号后缺少空格 +port:8046 + +# 正确 +port: 8046 +``` + +**Python 版本不满足**: + +```bash +python --version +# 需要 Python >= 3.13 +``` + +### 8.2 Claude Code 无法连接代理 + +1. 确认代理服务正在运行: + +```bash +coding-proxy status +# 如果提示 "代理服务未运行",先启动服务 +``` + +2. 确认环境变量设置正确: + +```bash +echo $ANTHROPIC_BASE_URL +# 应输出: http://127.0.0.1:8046 +``` + +3. 确认代理端口与环境变量一致 + +### 8.3 频繁触发故障转移 + +如果发现频繁在主备后端之间切换: + +1. 检查 Anthropic API 状态(是否正在经历服务波动) +2. 适当调高 `failure_threshold`(如从 3 改为 5) +3. 适当调高 `recovery_timeout_seconds`(给主后端更多恢复时间) +4. 查看日志确认触发原因(状态码、错误类型、错误消息) + +### 8.4 智谱后端返回错误 + +1. **API Key 错误**:确认 `ZHIPU_API_KEY` 环境变量已正确设置 +2. **模型不存在**:检查 `model_mapping` 规则,确认目标模型名称有效 +3. **网络问题**:确认可以访问 `open.bigmodel.cn` + +### 8.5 Token 用量不记录 + +1. 确认数据库路径目录可写: + +```bash +ls -la ~/.coding-proxy/ +``` + +2. 如果目录不存在,代理会在启动时自动创建 +3. 流式请求的用量提取依赖 SSE 事件格式,如果后端返回非标准格式可能无法正确解析 + +--- + +## 9. 常见问题 (FAQ) + +**Q: coding-proxy 支持哪些 Claude Code 版本?** + +A: 支持所有使用 Anthropic Messages API (`/v1/messages`) 的 Claude Code 版本。 + +**Q: 代理会影响响应速度吗?** + +A: 代理层自身开销极小,不影响实际使用体验。主要延迟来源于上游 API。 + +**Q: 智谱 GLM 的响应与 Anthropic Claude 完全一致吗?** + +A: 智谱提供 Anthropic 兼容接口,响应格式一致,但底层模型不同,生成内容和能力可能存在差异。 + +**Q: 如何防止 API Key 泄露?** + +A: 推荐通过环境变量注入(`${ZHIPU_API_KEY}`),`config.yaml` 已在 `.gitignore` 中,不会被提交到版本库。 + +**Q: 可以同时运行多个 coding-proxy 实例吗?** + +A: 可以,使用不同端口即可。每个实例可使用独立的配置文件和数据库。 + +**Q: 熔断器的指数退避是什么意思?** + +A: 每次恢复测试失败后,等待时间翻倍(300s → 600s → 1200s → 2400s → 3600s),上限为 `max_recovery_seconds`。这样可以避免对仍未恢复的后端进行频繁的无效重试。 + +**Q: 如何完全禁用故障转移?** + +A: 将 `failover.status_codes` 设为空列表 `[]`,代理将不再自动切换后端。 + +**Q: 数据库文件会无限增长吗?** + +A: 当前版本不自动清理历史数据。数据库文件大小取决于请求频率,日常使用增长较慢。如需清理,可删除 `usage.db` 文件,重启后自动重建。