Free Claude Code 通过清晰的模块划分和接口契约实现了一个可维护、可扩展的 Anthropic API 代理架构。核心设计是分离关注点:api 层处理路由与编排,providers 层适配不同后端,core 层提供共享协议工具,config 层统一管理配置。Provider 通过工厂模式注册,新增后端只需实现 BaseProvider 接口。消息系统采用平台无关的接口设计,支持 Discord 和 Telegram 等多平台扩展。

Free Claude Code 架构设计:一个 Anthropic 代理的模块化拆分思路

理解一个开源项目的架构,是进行二次开发和深度定制的前提。Free Claude Code 作为一个代理,其设计目标不仅是转发请求,更是提供一个稳定、可扩展的后端框架。本文将解析其模块化设计的思路,帮助你快速定位代码、理解数据流向,并掌握扩展方法。

模块总览与目录结构

项目的顶层目录结构体现了清晰的模块化意图。各模块职责单一,通过明确定义的接口进行交互。

free-claude-code/
├── server.py              # ASGI 入口,启动应用
├── api/                   # FastAPI 路由、服务层、模型路由、请求优化
├── core/                  # 共享 Anthropic 协议工具和 SSE 工具
├── providers/             # Provider 适配器、注册表、限流
├── messaging/             # Discord/Telegram 适配器、会话、语音
├── cli/                   # 包入口点和 Claude CLI 进程管理
├── config/                # 设置、Provider 目录、日志
└── tests/                 # 单元测试和契约测试
  • api/:是系统的入口和调度中心。它定义了 FastAPI 路由,处理来自 Claude Code 客户端的 HTTP 请求,负责请求的校验、优化尝试,并最终将请求路由到正确的 Provider。
  • providers/:负责与上游模型服务通信。每个子目录或模块(如 nvidia_nimopenrouter)是一个具体的 Provider 适配器,它们将统一的请求格式转换为特定后端(如 OpenAI Chat 或原生 Anthropic)所需的协议。
  • core/:存放被多个模块共同依赖的底层工具,主要是处理 Anthropic 消息协议和 SSE 流式传输的通用逻辑,确保协议的一致性和工具函数的复用。
  • messaging/:实现与即时通讯平台(如 Discord、Telegram)的集成,使用户可以通过聊天机器人与代理交互。它与 api/ 层协同工作,但自身保持平台中立。
  • config/:集中管理所有配置信息,包括 Provider 的描述符、API 密钥、环境变量等,是其他模块获取配置的源头。

模块间的依赖方向规则

模块之间并非自由引用,而是遵循严格的单向依赖规则,这是保证代码可维护性的关键。依赖方向如下图所示:

config → api, providers, messaging(配置被所有模块依赖)
core.anthropic → api, providers, messaging(协议工具被所有模块依赖)
providers → api(Provider 适配器被 API 层调用)
api → cli, messaging(API 层编排 CLI 和消息系统)

核心原则是:处于依赖链底层的模块(如 configcore)不应了解或依赖上层模块(如 apiproviders)。具体来说:

  1. providers/ 模块之间相互独立,不允许互相导入。
  2. api/messaging/ 模块只能通过 providers/registry.py 提供的工厂方法创建 Provider 实例,不能直接导入 providers/ 内部的具体实现类。
  3. 共享的协议工具被放置在中立的 core/ 模块,供 apiprovidersmessaging 平等使用。

这条规则并非纸上谈兵。项目通过 tests/contracts/test_import_boundaries.py 中的契约测试进行强制校验。该测试会分析所有模块的 import 语句,一旦发现违反上述方向的依赖(例如 messaging 模块导入了 api 的内容),测试就会失败。这为项目的长期健康提供了保障。

Provider 扩展机制

Provider 是代理的核心组件,负责与各种模型后端对接。其扩展机制设计得非常优雅,遵循“开闭原则”。

接口定义:BaseProvider

所有 Provider 必须实现 BaseProvider 抽象基类,它定义了与上层交互的契约:

class BaseProvider(ABC):
    async def cleanup(self) -> None: ...          # 资源清理
    async def list_model_ids(self) -> frozenset[str]: ...  # 列出支持的模型
    async def stream_response(self, request, ...) -> AsyncIterator[str]: ...  # 流式返回响应

stream_response 方法是核心,它接收一个标准化的请求对象,并以异步迭代器的形式返回 SSE 流数据。

注册新 Provider 的三步法

添加一个全新的后端(例如一个自建的兼容服务),只需遵循以下步骤:

第一步:定义描述符 (config/provider_catalog.py)

描述符定义了 Provider 的元数据,包括其唯一 ID、使用的传输协议、认证方式、默认 URL 和能力声明。例如 Ollama 的描述符:

"ollama": ProviderDescriptor(
    provider_id="ollama",
    transport_type="anthropic_messages",
    static_credential="ollama",
    default_base_url="http://localhost:11434",
    capabilities=("chat", “streaming”, “tools”, “thinking”, “local”),
)

第二步:实现适配器并创建工厂函数 (providers/your_provider.py & providers/registry.py)

创建具体的 Provider 类实现 BaseProvider 接口。然后在 registry.py 中编写一个工厂函数,该函数负责根据配置实例化你的 Provider。

def _create_ollama(config, settings):
    from providers.ollama import OllamaProvider  # 延迟导入
    return OllamaProvider(config)

第三步:完成注册

将工厂函数加入 PROVIDER_FACTORIES 字典。系统启动时,会自动校验 PROVIDER_DESCRIPTORS(来自 config)、PROVIDER_FACTORIES(来自 registry)和 SUPPORTED_PROVIDER_IDS(来自 providers/__init__.py)三者中的 Provider ID 完全一致,确保配置、工厂和支持列表的同步。

两种传输类型

根据上游服务的 API 兼容性,项目定义了两种传输类型:

传输类型 适用 Provider 说明
openai_chat NVIDIA NIM 等 需要进行协议转换:将 OpenAI Chat Completion 的请求/响应格式与 Anthropic 的 Messages SSE 格式进行双向翻译。
anthropic_messages OpenRouter, DeepSeek, Ollama, LM Studio 原生支持 Anthropic Messages API,代理层可以直接透传请求和响应流,效率更高。

模型路由

当 Claude Code CLI 发起请求时,会携带一个模型名称(如 claude-opus-4-20250514)。ModelRouter(位于 api/ 目录)的职责就是将这个名称映射到具体 Provider 上的一个模型。

路由逻辑如下:

  1. 解析模型名中的层级关键词(如 opussonnet)。
  2. 根据关键词查找对应的环境变量(如 MODEL_OPUS)。
  3. 环境变量的值格式为 provider_id/model_name(例如 nvidia_nim/moonshotai/kimi-k2.5)。
  4. 如果未找到特定模型的环境变量,则 fallback 到通用的 MODEL 环境变量。

这个设计允许你灵活地将 Claude Code 的不同模型请求,路由到完全不同、甚至来自不同供应商的后端模型上,实现成本、性能和质量的权衡。具体的多模型配置可以参考多模型路由一文。

消息系统设计

为了让代理能力能通过多种渠道暴露,项目设计了一个平台无关的消息系统。其核心是定义了一套 MessagingPlatform 接口。

MessagingPlatform(接口)
├── DiscordPlatform(Discord 实现)
└── TelegramPlatform(Telegram 实现)

ClaudeMessageHandler 是核心的业务处理器,它完全不了解 Discord 或 Telegram 的 API 细节,只通过 MessagingPlatform 接口发送和接收消息。这种设计使得添加新的聊天平台(如 Slack)只需实现该接口即可,无需改动核心逻辑。

消息处理采用树状队列模型来管理对话流:

  • 一条用户消息是树的根节点。
  • AI 的回复和该消息的后续交互是子节点。
  • 每个消息节点有状态(PENDING -> IN_PROGRESS -> COMPLETED / ERROR)。
  • 同一个对话树(即同一个话题)内的消息严格按顺序处理,确保上下文连贯。
  • 不同对话树的消息可以并行处理,提高吞吐量。

请求优化层

在请求被转发到远程 Provider 之前,会经过一个优化层(位于 api/optimization_handlers.py)。这个优化链会检查请求内容是否匹配某些特定模式(例如,固定的系统提示词、特定的消息结构)。

请求进入 → try_optimizations() → 命中优化规则 → 本地直接生成响应
                                  → 未命中 → 转发到远程 Provider

如果匹配成功,代理将直接在本地生成响应,完全避免网络请求和 Token 消耗。这对于处理一些高频、固定的指令(如初始化对话、特定工具调用)非常有效。这一机制的详细设计可以在请求优化机制中找到。

二次开发指南

基于以上架构,进行二次开发有两条主要路径。

添加新 Provider

  1. 定义元数据:在 config/provider_catalog.py 中为你的新 Provider 添加一个 ProviderDescriptor
  2. 实现适配器:在 providers/ 下创建新模块(例如 my_provider.py),编写一个继承自 BaseProvider 的类,实现其抽象方法。
  3. 注册工厂:在 providers/registry.py_create_* 函数区域添加一个工厂函数,并在 PROVIDER_FACTORIES 字典中完成注册。
  4. 添加测试:在 tests/ 目录下为你的 Provider 添加单元测试和/或契约测试,确保其行为符合接口约定。

添加新消息平台

  1. 实现接口:参考 messaging/discord.pymessaging/telegram.py 的实现,创建一个新的模块,实现 MessagingPlatform 接口的所有方法。
  2. 集成到运行时:在 api/runtime.py 中添加代码,在启动时根据配置初始化并注册你的新平台。
  3. 配置环境变量:在 config/ 中添加你的平台所需的环境变量定义(如 API Token、频道 ID 等)。

FAQ

Q: 为什么 Provider 使用工厂模式而不是直接实例化? A: 工厂模式实现了延迟导入。只有当配置中实际声明了某个 Provider 时,它的代码和第三方依赖(如 discord.py)才会被导入。这避免了因为用户不使用某个 Provider 却被迫安装其依赖库的问题,降低了启动时的依赖冲突风险。

Q: core/ 模块里具体放了什么? A: core/ 模块包含 Anthropic 协议相关的底层工具,例如:SSE 流式事件的解析器、消息内容的 Token 数估算函数、标准错误消息格式化工具,以及用于断言流数据符合协议规范的 stream contract 断言函数。这些工具被 apiprovidersmessaging 模块共同依赖,因此放在中立的 core 位置。

Q: 用于强制依赖方向的契约测试是如何工作的? A: tests/contracts/test_import_boundaries.py 使用 Python 的 ast 模块静态分析项目中每个 .py 文件的 import 语句。它预先定义了一套允许的导入规则(例如,messaging 不得导入 api)。测试运行时会扫描所有源文件,一旦发现违反这些规则的导入,测试就会立即失败,从而在代码提交前就能阻断不良的模块耦合。