Servir l'inférence
GuideExploitationDepuis la version 0.1.0
Sur cette page
Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la capacité côté produit : guide produit ; l'appeler : guide développeur.
Objectif : servir la surface OpenAI-compatible (/v1/chat/completions, /v1/models,
/v1/embeddings depuis 1.5.0) par le
même plan de données que l'API - même identité, même débit, mêmes politiques, même trace -,
routée par politique vers des backends, sous budget en tokens par tenant.
Commande - une section ai:. Elle ne demande aucun contrat : la passerelle déclare
elle-même ses opérations (ai.chatCompletions, ai.listModels, ai.embeddings), servies sur
chaque hôte.
ai:
security: # les identités acceptées, une suffit - comme `security` au contrat
- oidc: { scopes: [inference] }
- api_key: {} # une clé liée à ai.chatCompletions (voir Clés d'API)
sensitive_header: X-Sensitive # "true" épingle la requête sur sensitive_backend
default_backend: eu-provider
sensitive_backend: on-prem # interne ; injoignable, ou aucun backend interne : refus, jamais un externe
backends:
eu-provider:
url: https://api.provider.example/v1
api_key: "file:/etc/flexway/secrets/provider-key" # une référence, relue à chaque appel
default_model: mistral-small-3.2-24b # la passerelle choisit le modèle
external: true
on-prem:
url: http://vllm.inference.svc:8000/v1
default_model: mistral-7b-instruct
budget:
store: "${COUNTERS_URL}" # redis://..., partagé par les répliques
monthly_tokens: 40000000 # par tenant, 0 pour aucun
daily_tokens: 2000000
overrides: # la première qui correspond gagne
- { match: { tenant: acme, agent: batch-summaries }, backend: on-prem }Quatre identités possibles, chacune une alternative de ai.security :
| Alternative | L'appelant présente | Tenant |
|---|---|---|
trusted_headers |
Les en-têtes qu'un ingress de maillage a écrits (auth.trusted_headers), crus d'un pair de server.trusted_proxies seulement |
L'en-tête tenant ; subject nomme l'agent |
oidc |
Un jeton de l'un des émetteurs, avec les portées ou rôles déclarés | La claim tenant_claim |
api_key |
Authorization: ApiKey <clé>, la clé liée à ai.chatCompletions |
Celui de la clé |
mtls |
Un certificat client déclaré, lié à l'opération | Celui du certificat |
Derrière un ingress de maillage qui a déjà authentifié l'appelant (les noms d'en-têtes sont ceux du déploiement, jamais du produit) :
server:
trusted_proxies: [10.42.0.0/16] # l'ingress, et lui seul : de quiconque d'autre, l'en-tête est une forgerie
auth:
trusted_headers: { tenant: X-Mesh-Tenant, subject: X-Mesh-Agent }
ai:
security: [ { trusted_headers: {} } ]Coût, plafond souple et repli
Chaque backend porte son prix par million de tokens ; le budget en euros s'ajoute à celui en tokens - le premier atteint décide (exemple complet : Coût, plafond souple et repli) :
ai:
backends:
eu-large:
# ...
price: { input_per_mtok: 2.00, output_per_mtok: 6.00 } # euros par million de tokens
fallback: [eu-small, on-prem] # dans l'ordre, avant tout octet renvoyé au client
budget:
monthly_eur: 50 # par tenant, sur le mois calendaire
soft: { at: 0.8, backend: eu-small }
overrides:
- { match: { tenant: acme }, monthly_eur: 500 }| Mécanisme | Comportement |
|---|---|
| Coût | Pré-réservé (entrée estimée à la taille du corps / 4, sortie à max_tokens), réconcilié sur l'usage réel au prix du backend qui a servi ; un appel en échec ou refusé rend sa réservation, jetons et euros ; porté par l'audit, la métrique flexway.inference.cost (euros, par tenant et backend) |
| Plafond souple | Au-delà de at x budget : en-tête Flexway-Budget: soft, métrique, journal ; l'appel part sur soft.backend - sauf une requête sensible, qui reste épinglée |
| Plafond dur | Une pré-réservation qui dépasserait le budget en euros (ou en tokens) : 429 budget_exceeded, avant tout appel |
| Repli | Sur erreur de connexion, 5xx, délai ou circuit ouvert : le suivant de fallback, chaque backend une fois ; jamais sur un 4xx (dont le 429 du fournisseur), ni après le premier octet d'un flux, ni vers un externe pour une requête sensible. Réponse marquée Flexway-Degraded: backend-fallback |
Un prix est obligatoire pour chaque backend dès qu'un budget en euros existe : un coût que personne ne sait calculer ne se budgète pas.
0est un prix - celui d'un backend interne.
Vérif :
| Appel | Réponse |
|---|---|
| Identité acceptée, budget restant | relayé tel que le backend l'a écrit ; le backend reçoit le corps du client avec le modèle de la politique, et sa propre clé - jamais l'identité de l'appelant |
stream: true |
les évènements arrivent au fil de l'eau ; le budget est réconcilié sur l'usage final |
| Aucune identité, ou une identité sans tenant | 401 tenant_required |
| Budget du tenant épuisé (complétion ou flux) | 429 budget_exceeded, avant tout appel au backend |
X-Sensitive: true sans backend interne |
403 sensitive_no_internal_backend - jamais un backend externe |
| Backend en panne, sans repli disponible | 502 backend_unavailable ; pendant un flux, dernier évènement {"error":{"type":"backend_unavailable"}} |
Refus de la passerelle sur /v1/* (portée, débit, route inconnue) |
enveloppe OpenAI, type OpenAI (permission_error, rate_limit_error...) et code de la passerelle |
Tenant au-delà de soft.at de son budget en euros |
Flexway-Budget: soft ; servi par soft.backend |
Backend en panne avec fallback |
servi par le suivant, Flexway-Degraded: backend-fallback |
| Politique qui nomme un backend non déclaré | refusée au chargement, la clé nommée |
Débit :
/v1/*suit la chaîne de politiques comme toute route - une politiquerate-limitpropre, attachée àroutes: { "/v1/**": [...] }, la borne à part (exemple 12.17). Derrière un ingress, le seau est celui du tenant et de l'agent qu'il atteste, jamais l'adresse de l'ingress.Migrer depuis
config/policy.yaml- plus lu depuis0.1.0: sansFLEXWAY_CONFIG, la passerelle refuse de démarrer. La correspondance, à appliquer une fois à la main :
policy.yamlflexway.yamldefault_backend,backends.<n>.default_model,externalai.default_backend,ai.backends.<n>.*backends.<n>.base_url_envai.backends.<n>.url- l'URL elle-mêmebackends.<n>.api_key_env: VARai.backends.<n>.api_key: "${VAR}"oufile:/...pii_guard.sensitive_backendai.sensitive_backendbudget.*_tokens_defaultai.budget.*_tokens;REDIS_URL->ai.budget.storeoverridesai.overrides, même formeEn-têtes injectés par l'ingress auth.trusted_headers+server.trusted_proxies+ai.security: [ { trusted_headers: {} } ]
Plusieurs backends : images, embeddings, relais et modèles spécialisés
Depuis 1.5.0, un backend déclare où il est (kind), ce
qu'il sert (capabilities), les modèles qu'un appelant peut nommer (models), comment la
passerelle s'authentifie (auth) et qui mesure le coût (metering). Une configuration
écrite avant reste valide telle quelle : kind: openai, capabilities: [chat], api_key vaut
auth: { scheme: bearer }, metering: local.
Les trois genres :
ai:
default_backend: platform
sensitive_backend: local-vision
backends:
platform: # une autre Flexway : elle mesure, elle plafonne
kind: relay
url: https://llm.platform.example/v1
auth: { scheme: token_file, path: /var/run/flexway/peer-token } # relu à chaque appel
identity_headers: { tenant: X-Tenant, agent: X-Agent, sensitive: X-Sensitive }
metering: upstream
external: false # l'exploitant le déclare : le sensible peut y aller
capabilities: [chat, vision, embeddings]
provider-direct: # un fournisseur en direct : la clé est ici
kind: openai
url: https://api.provider.example/v1
auth: { scheme: header, header: X-Api-Key, key: "${PROVIDER_KEY}" }
external: true
capabilities: [chat, vision, embeddings]
models: [model-a, model-vision-b, embed-c] # servis s'ils sont demandés, sinon default_model
default_model: model-a
price: { input_per_mtok: 0.15, output_per_mtok: 0.35 }
local-vision: # un modèle du cluster
url: http://vision.models.svc:8080/v1
default_model: vision-small
auth: { scheme: none }
capabilities: [vision] # une image ; le texte aussi
faces: # un modèle spécialisé : l'amont d'un contrat gRPC
kind: grpc
upstream: models # déclaré dans upstreams:, protocol: grpc
capabilities: [vision]
overrides:
- { match: { agent: screen-reader }, backend: provider-direct }
- { match: { capability: embeddings }, backend: provider-direct } # route seulement, jamais un budget
vision:
max_image_bytes: 8MiB # une image, décodée (défaut)
allowed_origins: ["https://cdn.example"] # sinon images en ligne (data:) seulement| Clé | Valeurs | Effet |
|---|---|---|
kind |
openai (défaut), relay, grpc |
Une API compatible OpenAI ; une autre Flexway ; l'amont d'un modèle spécialisé gRPC, jamais une cible de /v1/* |
capabilities |
chat (défaut), vision, embeddings ; model (grpc) |
Une image ne va qu'à un backend vision, des embeddings qu'à un backend embeddings, sinon 400 capability_unavailable |
models |
liste | Le modèle demandé est servi s'il y figure, sinon default_model ; l'audit garde le modèle servi. Un relay sans default_model transmet le modèle demandé |
auth.scheme |
none, bearer (key), header (header, key), token_file (path) |
La clé est une référence (${VAR}, file:), relue à chaque appel ; token_file relit un jeton court (identité de charge de travail) |
metering |
local (défaut), upstream |
Voir ci-dessous |
external |
booléen | Une requête sensible n'y va jamais ; un relay est externe sauf external: false |
identity_headers |
tenant (obligatoire), agent, sensitive |
Les noms sous lesquels un relay reçoit l'identité de l'appel - ceux que l'autre Flexway déclare dans auth.trusted_headers et ai.sensitive_header |
tls |
comme un amont | Le mTLS vers un relay |
match.capability (dérogation) |
chat, vision, embeddings |
Choisit le backend des appels de cette capacité, après la dérogation du tenant et de l'agent ; jamais un budget |
Mesure - qui compte le coût :
metering |
Jetons | Euros | Refus de l'amont | Séries |
|---|---|---|---|---|
local |
Réservés, réconciliés sur l'usage | Au prix déclaré | Un 4xx du fournisseur : 502 backend_unavailable |
metered_by="local" |
upstream |
Plafond local toujours appliqué, sur l'usage que l'amont rend | Au prix déclaré s'il y en a un ; sans prix, l'amont fait foi | Relayé tel quel, 429 budget_exceeded compris |
metered_by="upstream" |
Une vue de consommation n'additionne que
metered_by!="upstream"- jamais="local": une série d'avant1.5.0n'a pas l'étiquette -, un appel relayé étant comptélocalpar la Flexway qui le paie. La console montre la part de l'amont à côté, sous « Mesuré en amont » (Lire avec la console).flexway validateavertit d'unrelaymesuré localement.
Côté plateforme - la Flexway qui reçoit le relais croit les en-têtes d'identité de ce pair seulement :
server:
trusted_proxies: [10.42.0.0/16] # les Flexway des produits
auth:
trusted_headers: { tenant: X-Tenant, subject: X-Agent }
ai:
security: [ { trusted_headers: {} } ]
sensitive_header: X-SensitiveImage et embeddings :
# une image en ligne, vers un backend vision
curl -s http://localhost:8080/v1/chat/completions -H "Authorization: Bearer $TOKEN" \
-d '{"model":"model-vision-b","messages":[{"role":"user","content":[{"type":"text","text":"Que montre cet écran ?"},{"type":"image_url","image_url":{"url":"data:image/png;base64,iVBORw0..."}}]}]}'
# des embeddings
curl -s http://localhost:8080/v1/embeddings -H "Authorization: Bearer $TOKEN" -d '{"model":"embed-c","input":["un","deux"]}'Modèle spécialisé gRPC - une méthode du contrat du produit, mesurée par l'option ai :
rpc Classify(Image) returns (Labels) {
option (flexway.v1.operation) = { ai: { unit: "call", price_per_1k: 0.02, capability: "vision" } };
}Chaque appel réserve son prix dans le budget en euros du tenant (ai.budget.monthly_eur et les
dérogations), avant l'amont ; unit: byte compte les octets de la requête et de la réponse au
passage. Un amont en panne ne coûte rien.
Vérif :
| Appel | Réponse |
|---|---|
Image vers un backend vision |
servie ; le corps tel quel, le modèle admis |
Image sans backend vision choisi, ou embeddings sans backend embeddings |
400 capability_unavailable, avant tout backend |
Image au-delà de max_image_bytes |
413 payload_too_large |
Image par URL d'une origine non déclarée, partie input_audio |
400 invalid_request |
| Relais : tenant au-delà du budget de la plateforme | 429 budget_exceeded de la plateforme, relayé tel quel |
| Relais : tenant au-delà du plafond local | 429 budget_exceeded, la plateforme jamais appelée |
| Méthode gRPC mesurée au-delà du budget | RESOURCE_EXHAUSTED, raison budget_exceeded ; l'amont jamais appelé |
Méthode gRPC mesurée sans backend grpc qui nomme son amont |
refusée au chargement |
Disponible depuis 0.1.0 ; plusieurs backends depuis 1.5.0.