Aller au contenu principal

B01 - Proxy LLM unifié

GroupeTechnologieStatutServicePhase de la feuille de routeContrats
B - Gestion des LLM et Model GatewayLiteLLM Proxyopen source intégréllm2model-manifest, usage-event

Retour à l'index - English

Rôle et responsabilités​

Gateway unifiée compatible OpenAI vers les modèles locaux et distants, derrière le Model Gateway EAI.

LiteLLM ou vLLM. vLLM (brique B02) est un moteur d'inférence local : il charge les modèles open weight sur nos GPU et les sert. LiteLLM (brique B01) est la gateway unifiée : il traduit un appel compatible OpenAI vers n'importe quel upstream, local (vLLM, Ollama, LM Studio) ou distant (OpenAI, Bedrock, Vertex...). vLLM est un upstream de LiteLLM, jamais son remplaçant, et LiteLLM n'exécute jamais de modèle lui-même.

Position dans l'architecture et interactions​

Chemin synchrone : le Runtime d'agents (C01), la Capability Gateway, la Knowledge Gateway et les clients SDK appellent POST /v1/gateway/* sur le Model Gateway du Control Plane. Séquence : authentification -> Policy Enforcement (A07) -> DLP (E02) -> Capability Resolver (B06) et politique d'effort (B07) -> plan de contexte (B08) -> estimation et réservation de quota (H03, H01) -> Router (B05) -> LiteLLM (B01) -> upstream (vLLM B02 ou provider distant) -> Token Accounting (B09) -> Usage Event et audit. Le streaming utilise SSE. Chemins asynchrones : les jobs batch et la génération vidéo passent par des adaptateurs spécialisés et des workflows Temporal ; les Usage Events sont aussi émis sur NATS pour l'analytique (ClickHouse).

Composant open source et partie développée​

Open source : LiteLLM Proxy (routage, traduction vers les providers, repli, reprises). Développé : le Model Gateway EAI placé devant (politique, DLP, quotas, coûts, audit), le générateur de configuration LiteLLM à partir des registres (GET /v1/litellm/config, POST /v1/litellm/sync), l'injection des identifiants depuis OpenBao et les adaptateurs spécialisés.

LiteLLM reste interne : il ne reçoit que les providers et les modèles poussés par la plateforme. Les clés, les accès et les quotas des équipes sont gérés par la plateforme (adhésions, clés d'API, quotas et correspondances de groupes d'annuaire de A03), jamais sous forme de clés virtuelles ou d'équipes LiteLLM.

Providers supportés​

Id providerProviderChemin via LiteLLMChamps de configuration admin
openaiOpenAInatifapi_key, organization, base_url
anthropicAnthropicnatifapi_key, base_url
google_ai_studioGoogle AI Studio / Gemininatif (gemini/)api_key
vertex_aiVertex AInatif (vertex_ai/)project, location, compte de service JSON (ou workload identity)
azure_openaiAzure OpenAInatif (azure/)endpoint, deployment name, api_version, api_key ou Entra ID
azure_ai_foundryAzure AI Foundryadaptateur choisi selon le type d'endpoint (style OpenAI ou inférence de modèle)endpoint, api_key ou Entra ID, api_version
aws_bedrockAWS Bedrocknatif (bedrock/)aws_region_name, aws_access_key_id + aws_secret_access_key, ou rôle IAM (aws_role_arn / IRSA), ou jeton porteur de l'API Bedrock (aws_bearer_token)
mistralMistralnatifapi_key
cohereCoherenatif (chat, embed, rerank)api_key
deepseekDeepSeeknatifapi_key
groqGroqnatifapi_key
together_aiTogether AInatifapi_key
fireworks_aiFireworks AInatifapi_key
openrouterOpenRouternatifapi_key
ollamaOllamanatifbase_url
vllmvLLM (local, brique B02)compatible OpenAI (hosted_vllm/)base_url, api_key optionnel
qwenQwen (DashScope)compatible OpenAIapi_key, base_url
zaiZ.AI (GLM)compatible OpenAIapi_key, base_url
minimaxMiniMaxcompatible OpenAIapi_key, base_url
lm_studioLM Studiocompatible OpenAI (lm_studio/)base_url
openai_codexOpenAI Codexcompatible OpenAI, compte OAuthOAuth token (délégué)
github_copilotGitHub Copilotnatif (github_copilot/)OAuth device token (délégué)
huggingfaceHugging Facenatif (huggingface/)api_key, endpoint (Inference Endpoints)
customCompatible OpenAI personnaliséopenai/ avec base_url personnalisébase_url, api_key, headers
custom_anthropicCompatible Anthropic personnaliséanthropic/ avec base_url personnalisébase_url, api_key

Limites de compatibilité fonctionnelle. LiteLLM normalise bien le chat, les embeddings et le rerank, mais ne garantit pas une compatibilité fonctionnelle universelle. La génération vidéo, la voix temps réel en duplex, la génération et retouche d'images et les API batch diffèrent fortement d'un provider à l'autre. Elles sont servies par des adaptateurs spécialisés listés par GET /v1/adapters, enregistrés dans le même Model Registry, sous les mêmes politiques, les mêmes quotas et le même système de coûts (Usage Events, grilles tarifaires). Une capacité non supportée par l'adaptateur choisi est rejetée explicitement.

Modèle de données​

Tables (schéma eai, chaque table tenant porte tenant_id uuid NOT NULL avec une politique RLS sur current_setting('app.tenant_id', true)::uuid) : eai.llm_models, eai.llm_providers, eai.usage_events, eai.audit_events.

Endpoints API​

  • POST /v1/gateway/chat/completions
  • POST /v1/gateway/embeddings
  • POST /v1/gateway/rerank
  • GET /v1/litellm/config
  • POST /v1/litellm/sync
  • GET /v1/adapters
POST /v1/gateway/chat/completions HTTP/1.1
Host: <control-plane-host>
X-API-Key: eai_xxx_secret
Accept-Language: fr
Content-Type: application/json

{"model": "claude-sonnet-eu", "messages": [{"role": "user", "content": "Bonjour"}], "eai": {"effort": "medium", "data_classification": "internal", "purpose": "support", "region_allow": ["eu"]}}
{"id": "chatcmpl-...", "choices": [{"message": {"role": "assistant", "content": "Bonjour !"}}], "usage": {"prompt_tokens": 12, "completion_tokens": 4}}

Les erreurs utilisent l'enveloppe commune {"error": {"code", "message", "message_i18n", "details", "request_id"}} ; les listes sont paginées avec limit et cursor.

Utilisation des SDK​

Python (enterprise_ai):

from enterprise_ai import EnterpriseAI

client = EnterpriseAI(base_url="<control-plane-url>", api_key="eai_xxx_secret")
items = client.models.list()

TypeScript (@enterprise-ai/sdk):

import { EnterpriseAI } from "@enterprise-ai/sdk";

const eai = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY });
const items = await eai.models.list();

Java (enterprise-ai-java):

EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.build();
var items = client.models().list();

Go (enterprise-ai-go):

client, _ := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")))
items, err := client.Models.List(ctx)

Appel chat avec l'extension eai (Python) : client.gateway.chat(model="claude-sonnet-eu", messages=[...], eai={"effort": "medium", "data_classification": "internal"}).

Configuration​

  • Environnement : EAI_DATABASE_URL, EAI_OTEL_EXPORTER_OTLP_ENDPOINT.

  • Valeurs Helm : controlPlane.enabled, controlPlane.replicas, controlPlane.resources, controlPlane.config, litellm.enabled, litellm.config.

  • EAI_LITELLM_URL, EAI_LITELLM_MASTER_KEY (depuis OpenBao).

  • Helm : litellm.enabled, litellm.replicas, litellm.config.fromControlPlane: true, gateway.adapters.video.enabled.

Contrôles de sécurité​

  • Authentification par JWT OIDC, clé API tenant avec scopes, ou mTLS SPIFFE ; le tenant est résolu depuis l'identifiant, jamais depuis le corps.
  • Autorisation par décisions OpenFGA et OPA ; chaque mutation et décision écrit une ligne eai.audit_events chaînée par hash.
  • Les secrets ne sont jamais renvoyés par l'API ; TLS partout ; les politiques réseau Cilium restreignent qui peut joindre le service.
ContrôleApplication
Listes d'autorisation provider / modèle / régionPar tenant et projet dans eai.policies ; le Router ne sélectionne que des triplets (provider, modèle, région) autorisés
Classification des donnéeseai.data_classification de la requête doit être <= data_classification_max du modèle ; les données très sensibles (restricted, secret) restent sur des modèles locaux (vLLM)
DLPLa DLP Gateway (E02) inspecte puis masque ou tokenise les prompts avant tout appel distant
Isolation des secretsIdentifiants en écriture seule, stockés dans OpenBao, injectés dans LiteLLM lors de la synchronisation, jamais renvoyés par l'API ni journalisés
TLSTLS 1.2+ vers chaque provider, mTLS SPIFFE dans le cluster
Rétention chez le providerLe registre consigne les conditions de rétention / de non-conservation des données (zero data retention) ; les modèles sans conditions acceptables sont restreints
Politique de repliLes chaînes de repli sont filtrées par les mêmes listes : jamais de région, de modèle ou de classification interdits
Garde-fous de coûtEstimation et réservation de quota avant exécution (POST /v1/gateway/estimate, POST /v1/quotas/reserve), coupure du streaming pendant l'exécution quand le budget est atteint
Journalisation sans expositionPrompts et réponses journalisés uniquement selon la politique, masqués, jamais avec des identifiants
Kill switchPOST /v1/providers/{id}/disable et POST /v1/models/{model_id}/disable prennent effet dès l'appel suivant

Les modèles distants sont autorisés pour certains usages (données publiques ou internes, finalités approuvées) tandis que les données très sensibles restent sur des modèles locaux.

Observabilité​

  • Métriques : eai_unified_llm_proxy_requests_total, eai_unified_llm_proxy_errors_total, eai_unified_llm_proxy_latency_seconds (Prometheus).
  • Traces : spans OpenTelemetry eai.gateway.* avec propagation W3C traceparent, spans IA envoyés à Opik.
  • Actions d'audit : model.invoke, policy.evaluate.

Aspects FinOps​

Chaque appel est estimé avant exécution (X-EAI-Cost-Estimated), mesuré après (X-EAI-Cost-Measured) puis facturé par le rapprochement (B11). Les agents ne doivent jamais appeler LiteLLM directement : les politiques Cilium n'autorisent que le Control Plane à le joindre, aucun appel n'échappe donc à la comptabilité.

Modes de défaillance et résilience​

  • Upstream 429/5xx : reprises avec backoff, puis Provider Failover (B14) dans les chaînes autorisées.
  • LiteLLM indisponible : la gateway renvoie 503 upstream_unavailable ; plusieurs réplicas et un PodDisruptionBudget.
  • Dérive de configuration : POST /v1/litellm/sync est idempotent et la configuration générée est versionnée.

Adoption autonome (client d'un client)​

Un client d'un client peut adopter cette brique seule avec le sous-client SDK Python client.gateway (TS eai.gateway, Java client.gateway(), Go client.Gateway). Déploiement minimal : Control Plane API + PostgreSQL + Keycloak (ou clés API), le Control Plane charge tous les domaines de services/control-plane/src/eai/domains ; les composants optionnels du chart se coupent dans deploy/helm/enterprise-ai/values.yaml (vllm.enabled, qdrant.enabled, nats.enabled, workers.enabled).

Tests d'acceptation​

  1. Un appel à un modèle bloqué renvoie 403 policy_denied et un événement d'audit.
  2. Un appel en streaming émet des fragments SSE et un Usage Event final avec coût mesuré.
  3. Une requête restricted n'est routée que vers un modèle vLLM.
  4. GET /v1/litellm/config ne contient jamais de secret.
  5. Une connexion directe d'un pod agent vers LiteLLM est refusée par Cilium.

Proxy LiteLLM réel (LLM_-001)​

bash tools/litellm/setup.sh crée le venv séparé /tmp/toolchains/litellm-venv avec les paquets de l'image LiteLLM de Navin : le verrou à empreintes deploy/litellm/requirements.lock (litellm 1.106.5 sans son extra proxy, qui exige le paquet propriétaire litellm-enterprise), installé par pip install --require-hashes --no-deps (pip --require-virtualenv, versions résolues dans resolved-versions.txt) ; un venv qui contient déjà litellm-enterprise est recréé. tests/test_llm_litellm_proxy_real.py démarre le vrai proxy sur un port libre avec la configuration générée par litellm_config.py, devant un service amont local compatible OpenAI, puis appelle /v1/gateway/chat/completions avec EAI_LITELLM_URL : réponse, usage, événement d'usage et audit model.invoke sont vérifiés. Sans venv, le test échoue avec un message clair.