Architektur

Architektur

CC-Relay ist ein hochperformanter, Multi-Provider HTTP-Proxy, der für LLM-Anwendungen entwickelt wurde. Er bietet intelligentes Routing, Caching von Thinking-Signaturen und nahtloses Failover zwischen Providern.

Systemübersicht

  graph TB
    subgraph "Client-Schicht"
        A[Claude Code]
        B[Eigener LLM-Client]
    end

    subgraph "CC-Relay Proxy"
        D[HTTP Server<br/>:8787]
        E[Middleware-Stack]
        F[Handler]
        G[Router]
        H[Signatur-Cache]
    end

    subgraph "Provider-Proxies"
        I[ProviderProxy<br/>Anthropic]
        J[ProviderProxy<br/>Z.AI]
        K[ProviderProxy<br/>Ollama]
    end

    subgraph "Backend-Provider"
        L[Anthropic API]
        M[Z.AI API]
        N[Ollama API]
    end

    A --> D
    B --> D
    D --> E
    E --> F
    F --> G
    F <--> H
    G --> I
    G --> J
    G --> K
    I --> L
    J --> M
    K --> N

    style A fill:#6366f1,stroke:#4f46e5,color:#fff
    style D fill:#ec4899,stroke:#db2777,color:#fff
    style F fill:#f59e0b,stroke:#d97706,color:#000
    style G fill:#10b981,stroke:#059669,color:#fff
    style H fill:#8b5cf6,stroke:#7c3aed,color:#fff

Kernkomponenten

1. Handler

Speicherort: internal/proxy/handler.go

Der Handler ist der zentrale Koordinator für die Anfrageverarbeitung:

type Handler struct {
    providerProxies map[string]*ProviderProxy  // Reverse-Proxies pro Provider
    defaultProvider providers.Provider          // Fallback für Single-Provider-Modus
    router          router.ProviderRouter       // Implementierung der Routing-Strategie
    healthTracker   *health.Tracker             // Circuit-Breaker-Tracking
    signatureCache  *SignatureCache             // Thinking-Signatur-Cache
    routingConfig   *config.RoutingConfig       // Modellbasierte Routing-Konfiguration
    providers       []router.ProviderInfo       // Verfügbare Provider
}

Verantwortlichkeiten:

  • Extrahieren des Modellnamens aus dem Request-Body
  • Erkennen von Thinking-Signaturen für Provider-Affinität
  • Auswählen des Providers über den Router
  • Delegieren an den entsprechenden ProviderProxy
  • Verarbeiten von Thinking-Blöcken und Cachen von Signaturen

2. ProviderProxy

Speicherort: internal/proxy/provider_proxy.go

Jeder Provider erhält einen dedizierten Reverse-Proxy mit vorkonfigurierter URL und Authentifizierung:

type ProviderProxy struct {
    Provider           providers.Provider
    Proxy              *httputil.ReverseProxy
    KeyPool            *keypool.KeyPool  // Für Multi-Key-Rotation
    APIKey             string            // Fallback-Einzelschlüssel
    targetURL          *url.URL          // Basis-URL des Providers
    modifyResponseHook ModifyResponseFunc
}

Hauptmerkmale:

  • URL-Parsing erfolgt einmalig bei der Initialisierung (nicht pro Anfrage)
  • Unterstützt transparente Authentifizierung (Weiterleitung von Client-Credentials) oder konfigurierte Authentifizierung
  • Automatische SSE-Header-Injektion für Streaming-Antworten
  • Key-Pool-Integration für Rate-Limit-Verteilung

3. Router

Speicherort: internal/router/

Der Router wählt aus, welcher Provider jede Anfrage bearbeitet:

StrategieBeschreibung
failoverPrioritätsbasiert mit automatischem Retry (Standard)
round_robinSequentielle Rotation
weighted_round_robinProportional nach Gewichtung
shuffleFaire Zufallsverteilung
model_basedRouting nach Modellnamen-Präfix

4. Signatur-Cache

Speicherort: internal/proxy/signature_cache.go

Cached Thinking-Block-Signaturen für Cross-Provider-Kompatibilität:

type SignatureCache struct {
    cache cache.Cache  // Ristretto-gestützter Cache
}

// Cache-Schlüsselformat: "sig:{modelGroup}:{textHash}"
// TTL: 3 Stunden (entspricht Claude API)

Anfragefluss

Multi-Provider-Routing

  sequenceDiagram
    participant Client
    participant Handler
    participant Router
    participant ModelFilter
    participant ProviderProxy
    participant Backend

    Client->>Handler: HTTP-Anfrage (mit model-Feld)
    Handler->>Handler: Modell aus Body extrahieren
    Handler->>Handler: Thinking-Signatur-Präsenz prüfen
    Handler->>ModelFilter: FilterProvidersByModel(model, providers, mapping)
    ModelFilter->>ModelFilter: Längste-Präfix-Übereinstimmung gegen modelMapping
    ModelFilter->>Router: Gefilterte Provider-Liste zurückgeben
    Handler->>Router: Provider auswählen (Failover/Round-Robin auf gefilterter Liste)
    Router->>Router: Routing-Strategie auf gefilterte Provider anwenden
    Router->>Handler: Ausgewählte ProviderInfo zurückgeben

    Handler->>Handler: ProviderProxy für ausgewählten Provider abrufen
    Handler->>ProviderProxy: Anfrage mit Auth/Headers vorbereiten
    ProviderProxy->>ProviderProxy: Transparenten vs. konfigurierten Auth-Modus bestimmen
    ProviderProxy->>Backend: Anfrage an Provider-Ziel-URL weiterleiten
    Backend->>ProviderProxy: Antwort (mit Signatur-Headern)
    ProviderProxy->>Handler: Antwort mit Signatur-Info
    Handler->>Handler: Signatur cachen wenn Thinking vorhanden
    Handler->>Client: Antwort

Verarbeitung von Thinking-Signaturen

Wenn erweitertes Thinking aktiviert ist, geben Provider signierte Thinking-Blöcke zurück. Diese Signaturen müssen vom selben Provider bei nachfolgenden Turns validiert werden. CC-Relay löst Cross-Provider-Signatur-Probleme durch Caching:

  sequenceDiagram
    participant Request as Anfrage
    participant Handler
    participant SignatureCache as Signatur-Cache
    participant Backend
    participant ResponseStream as Antwort-Stream (SSE)

    Request->>Handler: HTTP mit Thinking-Blöcken
    Handler->>Handler: HasThinkingSignature-Prüfung
    Handler->>Handler: ProcessRequestThinking
    Handler->>SignatureCache: Get(modelGroup, thinkingText)
    SignatureCache-->>Handler: Gecachte Signatur oder leer
    Handler->>Handler: Unsignierte Blöcke entfernen / Gecachte Signatur anwenden
    Handler->>Backend: Bereinigte Anfrage weiterleiten

    Backend->>ResponseStream: Streaming-Antwort (thinking_delta-Events)
    ResponseStream->>Handler: thinking_delta-Event
    Handler->>Handler: Thinking-Text akkumulieren
    ResponseStream->>Handler: signature_delta-Event
    Handler->>SignatureCache: Set(modelGroup, thinking_text, signature)
    SignatureCache-->>Handler: Gecacht
    Handler->>ResponseStream: Signatur mit modelGroup-Präfix transformieren
    ResponseStream->>Request: SSE-Event mit präfixierter Signatur zurückgeben

Modellgruppen für Signatur-Sharing:

Modell-PatternGruppeSignaturen geteilt
claude-*claudeJa, über alle Claude-Modelle
gpt-*gptJa, über alle GPT-Modelle
gemini-*geminiJa, verwendet Sentinel-Wert
AndereExakter NameKein Sharing

SSE-Streaming-Ablauf

  sequenceDiagram
    participant Client
    participant Proxy
    participant Provider

    Client->>Proxy: POST /v1/messages (stream=true)
    Proxy->>Provider: Anfrage weiterleiten

    Provider-->>Proxy: event: message_start
    Proxy-->>Client: event: message_start

    Provider-->>Proxy: event: content_block_start
    Proxy-->>Client: event: content_block_start

    loop Content-Streaming
        Provider-->>Proxy: event: content_block_delta
        Proxy-->>Client: event: content_block_delta
    end

    Provider-->>Proxy: event: content_block_stop
    Proxy-->>Client: event: content_block_stop

    Provider-->>Proxy: event: message_delta
    Proxy-->>Client: event: message_delta

    Provider-->>Proxy: event: message_stop
    Proxy-->>Client: event: message_stop

Erforderliche SSE-Header:

Content-Type: text/event-stream
Cache-Control: no-cache, no-transform
X-Accel-Buffering: no
Connection: keep-alive

Middleware-Stack

Speicherort: internal/proxy/middleware.go

MiddlewareZweck
RequestIDMiddlewareGeneriert/extrahiert X-Request-ID für Tracing
LoggingMiddlewareProtokolliert Anfrage/Antwort mit Timing
AuthMiddlewareValidiert x-api-key-Header
MultiAuthMiddlewareUnterstützt API-Key- und Bearer-Token-Auth

Provider-Schnittstelle

Speicherort: internal/providers/provider.go

type Provider interface {
    Name() string
    BaseURL() string
    Owner() string
    Authenticate(req *http.Request, key string) error
    ForwardHeaders(originalHeaders http.Header) http.Header
    SupportsStreaming() bool
    SupportsTransparentAuth() bool
    ListModels() []Model
    GetModelMapping() map[string]string
    MapModel(requestModel string) string
}

Implementierte Provider:

ProviderTypFunktionen
AnthropicProvideranthropicNatives Format, volle Funktionsunterstützung
ZAIProviderzaiAnthropic-kompatibel, GLM-Modelle
OllamaProviderollamaLokale Modelle, kein Prompt-Caching

Authentifizierungsmodi

Transparente Authentifizierung

Wenn der Client Credentials bereitstellt und der Provider es unterstützt:

  • Authorization- oder x-api-key-Header des Clients werden unverändert weitergeleitet
  • CC-Relay agiert als reiner Proxy

Konfigurierte Authentifizierung

Bei Verwendung von CC-Relays verwalteten Schlüsseln:

  • Client-Credentials werden entfernt
  • CC-Relay injiziert konfigurierten API-Schlüssel
  • Unterstützt Key-Pool-Rotation für Rate-Limit-Verteilung
  graph TD
    A[Anfrage eingehend] --> B{Hat Client Auth?}
    B -->|Ja| C{Provider unterstützt<br/>transparente Auth?}
    B -->|Nein| D[Konfigurierten Key verwenden]
    C -->|Ja| E[Client Auth weiterleiten]
    C -->|Nein| D
    D --> F{Key Pool verfügbar?}
    F -->|Ja| G[Key aus Pool wählen]
    F -->|Nein| H[Einzelnen API Key verwenden]
    E --> I[An Provider weiterleiten]
    G --> I
    H --> I

Health-Tracking & Circuit Breaker

Speicherort: internal/health/

CC-Relay verfolgt Provider-Gesundheit und implementiert Circuit-Breaker-Muster:

StatusVerhalten
CLOSEDNormalbetrieb, Anfragen fließen durch
OPENProvider als ungesund markiert, Anfragen scheitern schnell
HALF-OPENPrüfung mit begrenzten Anfragen nach Abkühlung

Auslöser für OPEN-Status:

  • HTTP 429 (Rate-limitiert)
  • HTTP 5xx (Server-Fehler)
  • Verbindungs-Timeouts
  • Aufeinanderfolgende Fehler überschreiten Schwellenwert

Verzeichnisstruktur

cc-relay/
├── cmd/cc-relay/           # CLI-Einstiegspunkt
│   ├── main.go             # Root-Befehl
│   ├── serve.go            # Serve-Befehl
│   └── di/                 # Dependency Injection
│       └── providers.go    # Service-Verdrahtung
├── internal/
│   ├── config/             # Konfiguration laden
│   ├── providers/          # Provider-Implementierungen
│   │   ├── provider.go     # Provider-Schnittstelle
│   │   ├── base.go         # Basis-Provider
│   │   ├── anthropic.go    # Anthropic-Provider
│   │   ├── zai.go          # Z.AI-Provider
│   │   └── ollama.go       # Ollama-Provider
│   ├── proxy/              # HTTP-Proxy-Server
│   │   ├── handler.go      # Haupt-Request-Handler
│   │   ├── provider_proxy.go # Pro-Provider-Proxy
│   │   ├── thinking.go     # Thinking-Block-Verarbeitung
│   │   ├── signature_cache.go # Signatur-Caching
│   │   ├── sse.go          # SSE-Hilfsfunktionen
│   │   └── middleware.go   # Middleware-Kette
│   ├── router/             # Routing-Strategien
│   │   ├── router.go       # Router-Schnittstelle
│   │   ├── failover.go     # Failover-Strategie
│   │   ├── round_robin.go  # Round-Robin-Strategie
│   │   └── model_filter.go # Modellbasierte Filterung
│   ├── health/             # Health-Tracking
│   │   └── tracker.go      # Circuit Breaker
│   ├── keypool/            # API-Key-Pooling
│   │   └── keypool.go      # Key-Rotation
│   └── cache/              # Caching-Schicht
│       └── cache.go        # Ristretto-Wrapper
└── docs-site/              # Dokumentation

Leistungsüberlegungen

Verbindungshandling

  • Connection Pooling: HTTP-Verbindungen zu Backends werden wiederverwendet
  • HTTP/2-Unterstützung: Multiplexed Requests wo unterstützt
  • Sofortiges Flushing: SSE-Events werden ohne Pufferung geflusht

Nebenläufigkeit

  • Goroutine pro Anfrage: Leichtgewichtige Go-Nebenläufigkeit
  • Context-Propagierung: Korrektes Timeout und Abbruch
  • Thread-sicheres Caching: Ristretto bietet nebenläufigen Zugriff

Speicher

  • Streaming-Antworten: Keine Pufferung von Response-Bodies
  • Signatur-Cache: Begrenzte Größe mit LRU-Eviction
  • Request-Body-Wiederherstellung: Effizientes Body-Neulesen

Nächste Schritte