Files
LLM_Proxy/docs/architecture-4plus1.md
Von bd7552f23b feat: 支持 Provider 多下游 target_formats 并收敛对外配置接口
- 将 Provider 下游配置由单个 target_format 扩展为多个 target_formats
- 保留旧字段 target_format 的启动期迁移逻辑,并标注为弃用兼容代码
- 收敛对外配置与接口返回,Provider 相关响应及 /v1/models 仅暴露 target_formats
- 前端 Provider 表单改为多选下游,支持互斥项自动替换
- 新建 Provider 默认选中 openai_chat、codex、claude_chat
- 更新示例配置、README、4+1 架构文档及相关测试
2026-04-06 09:46:28 +08:00

8.1 KiB

4+1 Architecture

1. Context

这个项目是一个协议翻译型 LLM Proxy。

它的目标不是“支持所有厂商的所有接口”,而是用一套干净的首版架构,稳定支持:

  • 上游协议族
    • openai_chat
    • openai_responses
    • claude_chat
    • codex
  • 下游协议面
    • POST /v1/chat/completions
    • POST /v1/responses
    • POST /v1/messages
    • GET /v1/models

这个版本不保留 Gemini / Antigravity。配置加载阶段会把旧字段 target_format 规范化为 target_formats,这段弃用兼容逻辑后续会删除;除此之外不再继续扩展旧字段兼容逻辑。

2. Logical View

2.1 Core Pipeline

系统按下面的统一链路工作:

downstream request
  -> controller
  -> provider lookup
  -> header_hook / request_guard
  -> translator.translate_request()
  -> executor
  -> decoder
  -> translator.translate_response()
  -> response_guard
  -> encoder
  -> downstream response

补充:hook 在 retry 场景下还可以读取上一轮失败摘要,用于做轻量级重试决策:

  • last_status_code
  • last_error_type

2.2 Major Components

  • ProxyController
    • 校验下游 route 和 target_formats
    • 根据 route family 解析本次请求命中的 target_format
    • 构造标准错误体
  • ProviderManager
    • 加载 provider 配置
    • 维护 provider/model -> provider 映射
  • AuthGroupManager
    • 加载 auth_groups
    • 选择 auth_entry
    • 持久化冷却、禁用与配额运行态
  • ProxyService
    • 组装整条代理链路
    • 使用本次解析出的 target_format 选择 translator 和 encoder
  • ExecutorRegistry
    • 负责 HTTP / WebSocket 上游连接
  • Decoder
    • 将上游流拆成统一事件
  • TranslatorRegistry
    • 负责 source_format -> target_format 协议适配
  • Encoder
    • 将统一 chunk 编码成下游协议
  • Hook
    • 只负责 header 和 guard

Hook 组件除了 header / guard,还会收到最小重试上下文:

  • retry
  • auth_group_name
  • auth_entry_id
  • last_status_code
  • last_error_type

2.3 Protocol Families

family 用途
openai_chat OpenAI Chat Completions 语义
openai_responses OpenAI Responses 语义
claude_chat Anthropic Messages 语义
codex OpenAI Responses 家族下的 Codex 特化语义

codex 的设计原则:

  • 共用 /v1/responses
  • 共用 Responses encoder
  • 保留请求规范化差异

3. Process View

3.1 Downstream Route Contract

target_formats 和 route family 强绑定:

route allowed target_formats
/v1/chat/completions openai_chat
/v1/responses openai_responses, codex
/v1/messages claude_chat

controller 会根据 route 在 target_formats 里解析出本次请求实际命中的 target_format。

  • 不匹配时,controller 直接返回 400,不会继续请求上游
  • 当前 openai_responses 和 codex 在同一个 provider 内互斥,因为它们共用 /v1/responses,但请求语义不同

GET /v1/models 除了模型 id,还会返回模型所属 provider 的:

  • source_format
  • target_formats
  • transport

3.2 Provider Runtime Contract

Provider 公共运行时字段只有:

  • name
  • api
  • transport
  • source_format
  • target_formats
  • api_key
  • auth_group
  • proxy
  • timeout_seconds
  • max_retries
  • verify_ssl
  • model_list
  • hook

其中:

  • source_format
    • 上游真实协议
  • target_formats
    • 当前 provider 对下游暴露的协议集合
  • transport
    • HTTP 或 WebSocket

没有公共 stream_format 字段。

Hook 运行时上下文还会暴露最小重试状态:

  • retry
  • auth_group_name
  • auth_entry_id
  • last_status_code
  • last_error_type

其中 last_error_type 使用 HookErrorType 枚举,当前值为:

  • TIMEOUT
  • CONNECTION_ERROR
  • WEBSOCKET_ERROR
  • TRANSPORT_ERROR

3.3 Internal Stream Detection

流式识别完全是内部实现细节:

  • transport = websocket
    • 按 WebSocket JSON 消息处理
  • HTTP Content-Type = text/event-stream
    • 按 SSE JSON 处理
  • HTTP Content-Type 含 ndjson/jsonl
    • 按 NDJSON 处理
  • 其他
    • 按非流式处理
  • 如果请求声明为流式,但首块看起来像 SSE
    • 触发首块探测兜底

这层能力保留在 executor / decoder 中,不暴露给用户配置。

4. Development View

4.1 Directory Responsibilities

  • src/presentation/
    • HTTP route、管理页面、API controller
  • src/services/
    • 代理主流程和业务服务
  • src/config/
    • 配置加载、schema、provider runtime
  • src/executors/
    • transport executor
  • src/proxy_core/
    • decoder、encoder、shared contracts
  • src/translators/
    • protocol translators
  • src/hooks/
    • hook contracts

4.2 Key Files

5. Physical View

部署上是单体服务:

  • 一个 Flask 应用
  • 一个配置文件
  • 多个 provider 指向多个真实上游
  • 下游统一接入这个代理
Client / Agent / IDE
        |
        v
    LLM Proxy
        |
        +--> OpenAI Chat upstream
        +--> OpenAI Responses upstream
        +--> Claude Messages upstream
        +--> Codex upstream

6. Scenarios

6.1 OpenAI Chat Downstream -> Responses Upstream

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Translator
    participant Executor

    Client->>Controller: POST /v1/chat/completions
    Controller->>Service: proxy_request()
    Service->>Translator: openai_responses -> openai_chat
    Translator-->>Service: translated upstream request
    Service->>Executor: execute HTTP request
    Executor-->>Service: stream events
    Service->>Translator: translate stream events
    Translator-->>Service: openai_chat chunks
    Service-->>Controller: SSE response
    Controller-->>Client: chat.completion.chunk stream

6.2 Claude Downstream -> OpenAI Chat Upstream

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Translator
    participant Executor

    Client->>Controller: POST /v1/messages
    Controller->>Service: proxy_request()
    Service->>Translator: openai_chat -> claude_chat
    Translator-->>Service: upstream chat request
    Service->>Executor: execute HTTP request
    Executor-->>Service: chat SSE stream
    Service->>Translator: translate to claude events
    Translator-->>Service: message_start/content_block_delta/message_stop
    Service-->>Client: Claude-style SSE

7. Design Decisions

7.1 Why only four protocol families

因为项目当前的目标客户端只需要:

  • OpenCode
  • Codex
  • Claude Code
  • Cherry Studio

Gemini / Antigravity 这类协议面会显著增加配置复杂度,但对当前目标收益很低,因此本版直接移除。

7.2 Why no public stream_format

因为流格式判断应该是代理内部责任,而不是用户负担。

用户只需要清楚:

  • 上游是什么协议
  • 下游要暴露成什么协议集合
  • 上游通过 HTTP 还是 WebSocket 连接

上游到底是 SSE、NDJSON 还是非流式,由 executor / decoder 自动判断。

7.3 Why keep codex separate

因为它虽然共用 Responses 路由和编码器,但请求语义仍有差异;把它保留成独立 family,能让配置和 translator 逻辑更清晰。