Proxy API LLM

Proxy API LLM : Unifier Claude, Gemini et Codex avec CCX

Référence pratique RubyAvancé

Proxy API LLM : Unifier Claude, Gemini et Codex avec CCX

Gérer des payloads JSON différents pour Claude, Gemini et Codex est un cauchemar de maintenance. Le Proxy API LLM résout ce problème en imposant un schéma unique et prévisible.

La fragmentation des formats (messages vs contents) multiplie la complexité de vos clients d’au moins 300%. Un Proxy API LLM centralise la logique de transformation et réduit la dette technique.

Après lecture, vous saurez implémenter un middleware capable de router et transformer des requêtes vers n’importe quel fournisseur d’IA.

Proxy API LLM

🛠️ Prérequis

Environnement Linux (Debian/Ubuntu recommandé) et runtime Ruby moderne.

  • Ruby 3.3.0 ou supérieur
  • Bundler 2.5+
  • Docker 24.0+ pour le déploiement
  • Clés API actives (Anthropic, Google AI, OpenAI)

📚 Comprendre Proxy API LLM

Le Proxy API LLM repose sur le pattern Adapter. L’objectif est de masquer les spécificités des fournisseurs derrière une interface commune.

Structure du flux :
Client (Standard JSON) -> CCX Proxy (Transformation) -> Provider API (Format spécifique) -> CCX Proxy (Re-formatage) -> Client.

Comparaison de complexité :
Sans proxy : N fournisseurs = N implémentations clients.
Avec Proxy API LLM : N fournisseurs = 1 implémentation client + N adaptateurs légers.

En Ruby, on utilise la duck typing pour traiter chaque adaptateur de la même manière, tant qu’ils répondent à la méthode transform.

💎 Le code — Proxy API LLM

Ruby
require 'sinatra'
require 'faraday'
require 'json'

# Le cœur du Proxy API LLM
class CCXProxy < Sinatra::Base
  configure do
    set :show_exceptions, false
    # Configuration des adaptateurs
    set :adapters, {
      'claude' => ClaudeAdapter.new,
      'gemini' => GeminiAdapter.new
    }
  end

  post '/v1/chat/completions' do
    content_type :json
    payload = JSON.parse(request.body.read)
    provider = request.env['HTTP_X_PROVIDER'] || 'claude'
    
    adapter = settings.adapters[provider]
    return halt 400, { error: 'Provider not supported' }.to_json unless adapter

    # Transformation du payload standard vers le format cible
    target_payload = adapter.transform(payload)
    
    # Appel au fournisseur via Faraday
    response = execute_request(provider, target_payload)
    
    # Re-transformation vers le format standard de sortie
    adapter.reformat(response.body)
  end

  private

  def execute_request(provider, payload)
    # On utilise Faraday pour la gestion des timeouts et des retries
    conn = Faraday.new(url: provider_url(provider)) do |f|
      f.request :json
      f.response :json
      f.adapter Faraday.default_adapter
      f.options.timeout = 30 # Timeout strict pour éviter l'engorgement
    end

    conn.post('', payload)
  end

  def provider_url(provider)
    # Mapping des endpoints selon le fournisseur
    { 'claude' => 'https://api.anthropic.com/v1/messages', 
      'gemint' => 'https://generativelanguage.googleapis.com/v1beta/...' }[provider]
  end
end

📖 Explication

Dans code_source, l’utilisation de Sinatra::Base permet d’isoler le proxy dans un module réutilisable, contrairement à un script Sinatra global. Le choix de Faraday est crucial : il permet d’ajouter des middlewares de retry et de logging de manière transparente, ce que Net::HTTP rendrait laborieux.

Le transform de l’adaptateur Claude utilise dig pour éviter les erreurs de type NoMethodError sur des clés nil. C’est le principe de moindre étonnement : on gère l’absence de paramètres par des valeurs par défaut (|| 1024).

Attention au piège classique : ne jamais transmettre l’intégralité du payload original sans filtrage. Cela pourrait exposer des paramètres non supportés par le fournisseur cible, provoquant des erreurs 400 inintelligibles.

Documentation officielle Ruby

🔄 Second exemple

Ruby
class ClaudeAdapter
  # Transforme le format OpenAI-like vers le format Anthropic
  def transform(payload)
    {
      model: payload['model'],
      messages: payload['messages'], # Claude utilise déjà un format proche
      max_tokens: payload.dig('max_tokens') || 1024,
      temperature: payload.dig('temperature') || 0.7
    }
  end

  # Re-formate la réponse Anthropic vers le format standard
  def reformat(body)
    {
      id: "chatcmpl-\#{Time.now.to_i}",
      choices: [{
        message: { role: 'assistant', content: body.dig('content', 0, 'text') },
        finish_reason: 'stop'
      }]
    }.to_json
  end
end

▶️ Exemple d’utilisation

Test du Proxy API LLM via cURL pour simuler un client OpenAI vers Claude.


curl -X POST http://localhost:4567/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "X-Provider: claude" \
  -d '{
    "model": "claude-3-5-sonnet",
    "messages": [{"role": "user", "content": "Bonjour !"}],
    "temperature": 0.5
  }'

# Sortie attendue :
{
  "id": "chatcmpl-1715678400",
  "choices": [
    {
      "message": { "role": "assistant", "content": "Bonjour ! Comment puis-je vous aider ?" },
      "finish_reason": "stop"
    }
  ]
}

🚀 Cas d’usage avancés

1. Observabilité avec OpenTelemetry : Intégrez un middleware pour tracer le temps de latence par fournisseur. Si Gemini est 200ms plus lent que Claude, vous devez le savoir pour ajuster vos timeouts.

2. Injection de contexte (RAG) : Le Proxy API LLM peut intercepter la requête pour injecter des données issues d’une base vectorielle avant l’envoi au fournisseur.

3. Filtrage de contenu (Guardrails) : Implémentez une regex ou un appel à un modèle léger (type BERT) dans le proxy pour bloquer les prompts malveillants (Prompt Injection) avant qu’ils n’atteignent le modèle principal.

✅ Bonnes pratiques

Pour un Proxy API LLM de production, respectez ces règles :

  • Immutabilité : Ne modifiez jamais l’objet payload original ; créez une copie transformée.
  • Idempotence : Assurez-vous que les retries ne causent pas de doubles facturations (bien que rare sur les LLM, crucial pour les outils liés).
  • Logging structuré : Utilisez JSON pour vos logs afin de faciliter l’analyse avec ELK ou Loki.
  • Sécurité : Validez systématiquement la taille du payload pour éviter les attaques DoS par mémoire.
  • Isolation : Chaque adaptateur doit être testé unitairement avec des mocks de réponses HTTP.
Points clés

  • Le Proxy API LLM unifie les interfaces de Claude, Gemini et Codex.
  • Utilisation du pattern Adapter pour la maintenance.
  • Transformation bidirectionnelle des payloads (Request/Response).
  • Gestion des timeouts via Faraday pour la résilience.
  • Implémentation de stratégies de fallback automatique.
  • Centralisation de la logique de rotation des clés API.
  • Réduction de la complexité client de 300% à 0%.
  • Possibilité d'injecter des couches de sécurité (Guardrails).

❓ Questions fréquentes

Est-ce que le proxy ajoute de la latence ?

Oui, environ 5 à 15ms selon la complexité des transformations. Ce coût est négligeable face au temps de génération du LLM.

Peut-on gérer le streaming avec ce proxy ?

C’est plus complexe. Il faut utiliser les Server-Sent Events (SSE) et transmettre les chunks sans les bufferiser.

Comment gérer les coûts avec le proxy ?

Le proxy est l’endroit idéal pour ajouter un middleware de comptabilité (usage de tokens par utilisateur).

Dois-je utiliser Docker pour ce projet ?

C’est fortement recommandé pour isoler les dépendances Ruby et gérer les variables d’environnement de manière propre.

📚 Sur le même blog

🔗 Le même sujet sur nos autres blogs

📝 Conclusion

Le Proxy API LLM est un composant d’infrastructure indispensable dès que votre application utilise plus d’un fournisseur d’IA. Il transforme une fragmentation chaotique en une interface stable et prévisible. Pour aller plus loin, explorez l’intégration de Prometheus pour monitorer le taux d’erreur par adaptateur. Consultez la documentation Ruby officielle pour approfondir la manipulation des flux HTTP. Un proxy bien conçu est celui qui disparaît de votre vue.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *