Retour à l'accueil

Langfuse et JWT pour LLM : solution proxy pour les systèmes corporatifs

L'article décrit une solution pour intégrer Langfuse avec des systèmes LLM qui nécessitent des tokens JWT dynamiques. Une architecture de service proxy sur FastAPI avec mise en cache des tokens et préservation de la compatibilité OpenAI est proposée. La solution préserve la fonctionnalité de Langfuse tout en respectant les politiques de sécurité corporatives.

Solution au problème de l'autorisation dynamique de Langfuse avec LLM via JWT
Advertisement 728x90

# Intégration de Langfuse avec LLM via JWT : Résolution du problème d'autorisation dynamique

Langfuse est un outil puissant pour tracer les requêtes LLM, gérer les prompts et évaluer les modèles. Cependant, dans les environnements d'entreprise, un problème critique surgit : le système exige des jetons JWT dynamiques pour accéder à la passerelle d'inférence, tandis que Langfuse ne supporte que des clés API statiques. Cette incompatibilité rend l'intégration directe impossible. La solution consiste à créer un service proxy fin qui convertit l'autorisation statique en dynamique.

Examinons une solution architecturale compatible avec les API de type OpenAI. Elle maintient la transparence pour Langfuse tout en respectant les exigences de sécurité de l'infrastructure d'entreprise.

Architecture du proxy : Combler l'écart entre statique et dynamique

Le problème central est un choc de paradigmes :

Google AdInline article slot
  • Langfuse utilise des configurations de connexion fixes avec des clés API
  • Les passerelles d'inférence d'entreprise exigent des jetons JWT à courte durée de vie avec des TTL brefs

Le proxy agit comme un adaptateur, effectuant trois opérations critiques :

  • Réception des requêtes compatibles OpenAI provenant de Langfuse
  • Obtention dynamique de JWT via le flux client_credentials
  • Transmission transparente des requêtes vers l'API en amont avec remplacement de l'en-tête Authorization

Cela préserve le flux de travail standard de Langfuse sans modification de code, tout en respectant les politiques de sécurité de l'infrastructure interne.

Implémentation technique : Construction étape par étape

Étape 1 : Cœur asynchrone avec FastAPI

Le proxy est un service lié à l'E/S où la charge principale provient des appels réseau. Une architecture asynchrone utilisant FastAPI et httpx.AsyncClient offre :

Google AdInline article slot
  • Gestion non bloquante des requêtes parallèles
  • Gestion efficace des connexions
  • Débit maximal avec des ressources minimales

Structure du projet :

app/
├── __init__.py
├── app.py          # Entry point and lifecycle management
├── routers.py      # Request routing
├── proxy_service.py # Proxy logic
├── jwt_provider.py # Token retrieval and caching
└── settings.py     # Pydantic-based configuration

Exemple d'implémentation de la gestion du cycle de vie :

@asynccontextmanager
async def lifespan(app: FastAPI):
    configure_logging()
    logger = get_logger()
    logger.info("Microservice is starting...")

    proxy_service = LLMProxyService()
    app.state.proxy_service = proxy_service

    try:
        yield
    finally:
        await proxy_service.aclose()
        logger.info("Microservice is shutting down...")

Étape 2 : Routage des points de terminaison compatibles OpenAI

Routes minimalement requises :

Google AdInline article slot
  • /v1/chat/completions — point de terminaison principal pour la génération
  • /v1/models — vérification des modèles disponibles
  • /healthz — vérification de santé pour les orchestrateurs

Il est crucial de préserver les chemins originaux pour éviter de rompre la compatibilité avec les bibliothèques clientes. Implémentation du routeur :

@root_router.post("/v1/chat/completions")
async def proxy_chat_completions(request: Request):
    proxy_service = _get_proxy_service(request)
    return await proxy_service.proxy_chat_completions(request)

Étape 3 : Configuration dynamique via Pydantic Settings

L'utilisation de pydantic-settings permet une gestion flexible des paramètres dans différents environnements. Paramètres clés :

  • LLM_BASE_URL — adresse de la passerelle d'inférence
  • SYSTEM_ID_HEADER_NAME — en-tête pour l'identifiant client
  • JWT_ISSUER et JWT_SCOPE — paramètres du fournisseur OIDC
  • Timeouts pour tous les appels externes

Exemple de classe de paramètres :

class Settings(BaseSettings):
    llm_base_url: str = Field(default="", alias="LLM_BASE_URL")
    system_id_header_name: str = Field(
        default="systemId", alias="SYSTEM_ID_HEADER_NAME")
    jwt_issuer: str = Field(default="", alias="JWT_ISSUER")

Étape 4 : Fournisseur JWT intelligent avec mise en cache

Une optimisation critique est la mise en cache des jetons jusqu'à expiration. Algorithme du flux de travail :

  • Extraction de client_id et client_secret de la requête entrante
  • Découverte automatique de token_endpoint via la découverte OIDC
  • Demande de jeton via le flux client_credentials
  • Mise en cache de access_token en tenant compte de expires_in

Sans mise en cache, le service d'authentification devient un goulot d'étranglement sous forte charge. Implémentation avec cache LRU :

@lru_cache(maxsize=128)
async def get_token(
    self,
    client_id: str,
    client_secret: str
) -> str:
    # Token retrieval and caching logic

Étape 5 : Proxying transparent des requêtes

Aspects clés de l'implémentation :

  • Préservation des paramètres de requête originaux et du corps de la requête
  • Filtrage des en-têtes de transport (Host, Content-Length)
  • Remplacement de Authorization par un JWT frais
  • Transmission inchangée de tous les codes de statut HTTP

Ne jamais modifier la charge utile — cela garantit la compatibilité avec les bibliothèques clientes de Langfuse. Exemple de gestion :

async def _forward(self, request: Request, path: str) -> Response:
    client_id = request.headers.get(self._settings.system_id_header_name)
    authorization = request.headers.get("authorization")
    _, _, client_secret = authorization.partition(" ")

    token = await self._token_provider.get_token(
        client_id=client_id,
        client_secret=client_secret,
    )

    return await self._send(
        request=request,
        url=f"{self._settings.llm_base_url}{path}",
        token=token,
    )

Enseignements clés : Ce qui compte

  • Proxy comme adaptateur essentiel : Avec des jetons à courte durée de vie, l'intégration directe de Langfuse est impossible — une couche intermédiaire est requise
  • Mise en cache des jetons est critique : Sans elle, le service d'authentification crée un goulot d'étranglement sous charge
  • Maintenir la transparence : Ne pas modifier les charges utiles ou les codes de statut HTTP — cela garantit la compatibilité
  • Sécurité des données : Ne jamais journaliser les en-têtes Authorization ; restreindre l'accès au proxy
  • Configuration flexible : La paramétrisation via .env facilite l'adaptation dans différents environnements

Configuration de Langfuse : Étapes finales

Configuration dans l'interface Langfuse :

  • Définir Base URL sur l'adresse du proxy (ex. : http://proxy-service/v1)
  • Saisir client_secret dans API Key (transmis comme credential Bearer)
  • Ajouter un en-tête avec le nom de SYSTEM_ID_HEADER_NAME dans Custom Headers

Cela crée une boucle fermée : Langfuse envoie des requêtes avec des paramètres fixes → le proxy récupère dynamiquement le JWT → la requête atteint la passerelle d'inférence au format requis.

Important de tester les scénarios :

  • Expiration de jeton
  • Erreurs du service d'authentification
  • Forte charge (valider l'efficacité de la mise en cache)

Cette architecture conserve tous les avantages de Langfuse pour la surveillance des requêtes LLM tout en respectant les exigences de sécurité d'entreprise strictes. L'implémentation prend 1 à 2 jours avec des compétences de base en FastAPI et protocole OIDC.

— Editorial Team

Advertisement 728x90

Lire ensuite