B02 - Inférence locale
| Groupe | Technologie | Statut | Service | Phase de la feuille de route | Contrats |
|---|---|---|---|---|---|
| B - Gestion des LLM et Model Gateway | vLLM | open source intégré | deploy/vllm | 4 | model-manifest |
Rôle et responsabilités
Moteur d'inférence GPU local servant des modèles open weight ; un upstream de LiteLLM.
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
Les serveurs vLLM se déclarent comme tout autre fournisseur LiteLLM : POST /v1/providers avec provider_type: vllm, le point d'accès (api_base) et une clé facultative, exactement comme Ollama ou LM Studio. POST /v1/providers/{id}/models/discover lit les modèles servis depuis GET {api_base}/models, POST /v1/models/import enregistre ceux choisis et POST /v1/litellm/sync les publie dans LiteLLM. Les serveurs eux-mêmes tournent sur les nœuds GPU (B13, chart Helm vllm.models). Seul LiteLLM appelle vLLM (HTTP compatible OpenAI, synchrone et streaming). KEDA ajuste les réplicas selon la profondeur de file.
Composant open source et partie développée
Open source : vLLM (PagedAttention, batching continu, parallélisme tensoriel). Développé : enregistrement du fournisseur, découverte et import des modèles, manifestes de modèles, placement GPU, règles de souveraineté (données restreintes servies uniquement ici).
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_providers, eai.llm_models, eai.gpu_samples, eai.audit_events.
Endpoints API
GET /v1/providers/catalogPOST /v1/providersPOST /v1/providers/{id}/models/discoverPOST /v1/models/import
POST /v1/providers/7fcf4489-04d6-4992-9187-0ccca12986e6/models/discover HTTP/1.1
Host: <control-plane-host>
X-API-Key: eai_xxx_secret
Accept-Language: fr
{"provider_id": "7fcf4489-04d6-4992-9187-0ccca12986e6", "provider_type": "vllm", "kind": "openai", "mode": "api", "count": 1, "registered_count": 0, "items": [{"provider_model_id": "meta-llama/Llama-3.1-8B-Instruct", "model_id": "meta-llama-llama-3.1-8b-instruct", "display_name": "meta-llama/Llama-3.1-8B-Instruct", "category": "text", "context_window": 131072, "registered": false, "registered_model_id": null, "hints_applied": []}]}
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.providers.discover_models("7fcf4489-04d6-4992-9187-0ccca12986e6")
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.providers.discoverModels("7fcf4489-04d6-4992-9187-0ccca12986e6");
Java (enterprise-ai-java):
EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.build();
var items = client.providers().discoverModels("7fcf4489-04d6-4992-9187-0ccca12986e6");
Go (enterprise-ai-go):
client, _ := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")))
items, err := client.Providers.DiscoverModels(ctx, "7fcf4489-04d6-4992-9187-0ccca12986e6")
Configuration
- Environnement :
EAI_DATABASE_URL,EAI_OTEL_EXPORTER_OTLP_ENDPOINT. - Valeurs Helm :
controlPlane.enabled,controlPlane.replicas,controlPlane.resources,controlPlane.config.
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.
Observabilité
- Métriques :
eai_local_inference_requests_total,eai_local_inference_errors_total,eai_local_inference_latency_seconds(Prometheus). - Traces : spans OpenTelemetry
eai.providers.*avec propagation W3Ctraceparent, spans IA envoyés à Opik. - Actions d'audit :
provider.create,provider.discover,model.import,litellm.sync,policy.evaluate.
Aspects FinOps
Le coût est du temps GPU et non des tokens : les échantillons DCGM (POST /v1/finops/gpu/samples) sont imputés par tenant via GET /v1/finops/gpu/allocation et les politiques d'imputation ; une grille interne le convertit en coût équivalent par token.
Modes de défaillance et résilience
- Réplicas sans état derrière l'API Gateway, PostgreSQL HA pour l'état ; la sonde
GET /readyzretire les pods défaillants. - Système externe indisponible : échec fermé pour les décisions de sécurité, code d'erreur explicite, aucun contournement silencieux.
Adoption autonome (client d'un client)
Un client d'un client peut adopter cette brique seule avec le sous-client SDK Python client.providers (TS eai.providers, Java client.providers(), Go client.Providers). 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 tenant ne peut ni lire ni modifier les ressources d'un autre tenant (test RLS).
- Chaque mutation produit un événement d'audit vérifiable par
GET /v1/audit/verify. - Les erreurs sont renvoyées en anglais et en français selon
Accept-Language.