# 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 :
- 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 :
- 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 :
/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érenceSYSTEM_ID_HEADER_NAME— en-tête pour l'identifiant clientJWT_ISSUERetJWT_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
Aucun commentaire pour le moment.