B01 - Proxy LLM unifié
| Groupe | Technologie | Statut | Service | Phase de la feuille de route | Contrats |
|---|---|---|---|---|---|
| B - Gestion des LLM et Model Gateway | LiteLLM Proxy | open source intégré | llm | 2 | model-manifest, usage-event |
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 provider | Provider | Chemin via LiteLLM | Champs de configuration admin |
|---|---|---|---|
openai | OpenAI | natif | api_key, organization, base_url |
anthropic | Anthropic | natif | api_key, base_url |
google_ai_studio | Google AI Studio / Gemini | natif (gemini/) | api_key |
vertex_ai | Vertex AI | natif (vertex_ai/) | project, location, compte de service JSON (ou workload identity) |
azure_openai | Azure OpenAI | natif (azure/) | endpoint, deployment name, api_version, api_key ou Entra ID |
azure_ai_foundry | Azure AI Foundry | adaptateur choisi selon le type d'endpoint (style OpenAI ou inférence de modèle) | endpoint, api_key ou Entra ID, api_version |
aws_bedrock | AWS Bedrock | natif (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) |
mistral | Mistral | natif | api_key |
cohere | Cohere | natif (chat, embed, rerank) | api_key |
deepseek | DeepSeek | natif | api_key |
groq | Groq | natif | api_key |
together_ai | Together AI | natif | api_key |
fireworks_ai | Fireworks AI | natif | api_key |
openrouter | OpenRouter | natif | api_key |
ollama | Ollama | natif | base_url |
vllm | vLLM (local, brique B02) | compatible OpenAI (hosted_vllm/) | base_url, api_key optionnel |
qwen | Qwen (DashScope) | compatible OpenAI | api_key, base_url |
zai | Z.AI (GLM) | compatible OpenAI | api_key, base_url |
minimax | MiniMax | compatible OpenAI | api_key, base_url |
lm_studio | LM Studio | compatible OpenAI (lm_studio/) | base_url |
openai_codex | OpenAI Codex | compatible OpenAI, compte OAuth | OAuth token (délégué) |
github_copilot | GitHub Copilot | natif (github_copilot/) | OAuth device token (délégué) |
huggingface | Hugging Face | natif (huggingface/) | api_key, endpoint (Inference Endpoints) |
custom | Compatible OpenAI personnalisé | openai/ avec base_url personnalisé | base_url, api_key, headers |
custom_anthropic | Compatible 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/completionsPOST /v1/gateway/embeddingsPOST /v1/gateway/rerankGET /v1/litellm/configPOST /v1/litellm/syncGET /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_eventschaî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ôle | Application |
|---|---|
| Listes d'autorisation provider / modèle / région | Par tenant et projet dans eai.policies ; le Router ne sélectionne que des triplets (provider, modèle, région) autorisés |
| Classification des données | eai.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) |
| DLP | La DLP Gateway (E02) inspecte puis masque ou tokenise les prompts avant tout appel distant |
| Isolation des secrets | Identifiants en écriture seule, stockés dans OpenBao, injectés dans LiteLLM lors de la synchronisation, jamais renvoyés par l'API ni journalisés |
| TLS | TLS 1.2+ vers chaque provider, mTLS SPIFFE dans le cluster |
| Rétention chez le provider | Le 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 repli | Les 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ût | Estimation 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 exposition | Prompts et réponses journalisés uniquement selon la politique, masqués, jamais avec des identifiants |
| Kill switch | POST /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 W3Ctraceparent, 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/syncest 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
- Un appel à un modèle bloqué renvoie 403
policy_deniedet un événement d'audit. - Un appel en streaming émet des fragments SSE et un Usage Event final avec coût mesuré.
- Une requête restricted n'est routée que vers un modèle vLLM.
GET /v1/litellm/configne contient jamais de secret.- 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.