Technique
Partie II. Architecture et configurations de référence
Pour : ingénieurs et SRE · architectesPrérequis : Avoir lu la partie I ; connaître l'OpenTelemetry Collector et le langage PromQL ou MetricsQL.
5. Le plan de télémétrie unifié
Section intitulée « 5. Le plan de télémétrie unifié »Ne pas construire une pile GenAI en silo. Émettre la télémétrie LLM, agent, RAG et MCP dans le même plan que l’infrastructure et les applications existantes. La méthode est agnostique de l’outillage. VictoriaMetrics, Grafana et Langfuse sont une instanciation possible parmi d’autres, retenue ici parce qu’elle s’auto-héberge et repose sur des protocoles ouverts (OTLP, remote write), pas une dépendance de la méthode. Les mêmes rôles se tiennent avec Prometheus, Mimir ou Thanos pour les métriques, Tempo ou Jaeger pour les traces, Loki pour les logs, Phoenix (sous Elastic License 2.0, une licence source disponible) ou un service commercial comme LangSmith pour l’évaluation. Langfuse appartient à ClickHouse depuis janvier 2026 ; son cœur reste sous licence MIT.
flowchart TB APP["Application : LLM / Agent / RAG / Clients MCP"] INST["Instrumentation OTel<br/>OpenLLMetry, OpenInference, SDK natif"] COL["OpenTelemetry Collector<br/>masquage, attributs, spanmetrics"] MET["Métriques<br/>VictoriaMetrics<br/>(DCGM, vLLM)"] TRA["Traces<br/>backend OTLP"] LOG["Logs<br/>VictoriaLogs / Loki"] CON["Contenu opt-in<br/>stockage objet chiffré"] EVAL["Couche évaluation<br/>Langfuse / Phoenix"] GRAF["Grafana<br/>tableaux de bord, alerting, SLO"] APP --> INST --> COL COL --> MET COL --> TRA COL --> LOG COL --> CON MET --> GRAF TRA --> GRAF LOG --> GRAF TRA --> EVAL CON --> EVAL EVAL --> GRAF
6. Configuration de référence du collecteur
Section intitulée « 6. Configuration de référence du collecteur »Exemple commenté, à adapter. Points clés : un pipeline trace, un pipeline métriques, masquage des données sensibles au collecteur, dérivation de métriques à partir des spans (spanmetrics) avec des dimensions à faible cardinalité.
# otel-collector.yaml (extrait de référence)receivers: otlp: protocols: grpc: { endpoint: 0.0.0.0:4317 } http: { endpoint: 0.0.0.0:4318 }
processors: batch: timeout: 5s send_batch_size: 1000
# Masquage : on retire ou hache le contenu sensible avant tout export. redaction: allow_all_keys: true blocked_values: - "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" # emails - "\\b(?:\\d[ -]*?){13,16}\\b" # numéros de carte summary: info
# Filet de sécurité sur les traces : si une instrumentation capture le # contenu par erreur, on le retire des spans avant export. Le contenu opt-in # passe par le stockage externe chiffré, jamais par le backend de traces. # Les métriques n'ont pas besoin de ce filtre : spanmetrics ne garde que les # dimensions listées ci-dessous. attributes/clean: actions: - key: gen_ai.input.messages action: delete - key: gen_ai.output.messages action: delete
connectors: # Dérivation de métriques depuis les spans, dimensions low-cardinality. spanmetrics: dimensions: - name: gen_ai.request.model - name: gen_ai.operation.name - name: gen_ai.agent.name - name: error.type
exporters: # Métriques vers VictoriaMetrics (endpoint remote write). prometheusremotewrite: endpoint: http://victoriametrics:8428/api/v1/write # Traces vers le backend OTLP. otlp/traces: endpoint: tempo:4317 tls: { insecure: true } # Logs vers VictoriaLogs ou Loki. otlphttp/logs: logs_endpoint: http://victorialogs:9428/insert/opentelemetry/v1/logs
service: pipelines: traces: receivers: [otlp] processors: [attributes/clean, redaction, batch] exporters: [otlp/traces, spanmetrics] metrics: receivers: [otlp, spanmetrics] processors: [batch] exporters: [prometheusremotewrite] logs: receivers: [otlp] processors: [redaction, batch] exporters: [otlphttp/logs]7. Recording rules MetricsQL
Section intitulée « 7. Recording rules MetricsQL »Exemples à poser dans VictoriaMetrics ou un Prometheus compatible. Les métriques sources sont celles des conventions OTel GenAI 1.40 et 1.41 (gen_ai.client.operation.duration, gen_ai.client.token.usage et gen_ai.client.operation.time_to_first_chunk à partir de la 1.41). Les noms tiennent compte de la traduction faite par l’exportateur prometheusremotewrite : par défaut (translation_strategy: UnderscoreEscapingWithSuffixes), il remplace les points par des tirets bas et ajoute le suffixe d’unité, donc gen_ai.client.operation.duration (unité s) devient gen_ai_client_operation_duration_seconds_bucket, _sum et _count. L’unité {token} est une annotation et ne produit pas de suffixe : gen_ai_client_token_usage_sum reste tel quel. Si vous choisissez UnderscoreEscapingWithoutSuffixes, retirez _seconds des règles. Les prix sont à prendre dans votre propre table datée (annexe D).
# rules.yaml (extrait de référence, syntaxe MetricsQL)groups: - name: genai interval: 30s rules: # Latence p95 par modèle : durée d'opération. - record: genai:operation_duration:p95 expr: | histogram_quantile(0.95, sum(rate(gen_ai_client_operation_duration_seconds_bucket[5m])) by (le, gen_ai_request_model))
# TTFT p95 par modèle : délai avant le premier fragment, appels en streaming seulement. - record: genai:time_to_first_chunk:p95 expr: | histogram_quantile(0.95, sum(rate(gen_ai_client_operation_time_to_first_chunk_seconds_bucket[5m])) by (le, gen_ai_request_model))
# Taux d'erreur par type d'erreur. - record: genai:error_ratio expr: | sum(rate(gen_ai_client_operation_duration_seconds_count{error_type!=""}[5m])) by (error_type) / ignoring(error_type) group_left sum(rate(gen_ai_client_operation_duration_seconds_count[5m]))
# Débit de coût estimé (euros par seconde), via tokens et prix injectés en labels. # Diviser par le débit de requêtes pour obtenir un coût par requête. # price_in et price_out sont fournis par un recording rule de prix ou un relabel. - record: genai:cost_rate:eur_per_second expr: | (sum(rate(gen_ai_client_token_usage_sum{gen_ai_token_type="input"}[5m])) by (gen_ai_request_model) * on(gen_ai_request_model) group_left price_in_eur_per_token) + (sum(rate(gen_ai_client_token_usage_sum{gen_ai_token_type="output"}[5m])) by (gen_ai_request_model) * on(gen_ai_request_model) group_left price_out_eur_per_token)
# Tokens entrée moyens par requête (détection du gonflement de prompt). - record: genai:input_tokens_per_request:avg expr: | sum(rate(gen_ai_client_token_usage_sum{gen_ai_token_type="input"}[1h])) by (gen_ai_request_model) / sum(rate(gen_ai_client_operation_duration_seconds_count[1h])) by (gen_ai_request_model)La série de prix utilisée par la règle de coût n’est pas magique. On la matérialise par un petit recording rule par modèle, tenu à jour à partir de votre table de prix datée (annexe D). Les prix changent vite et varient selon les contrats : ce guide n’en donne pas ; utilisez la grille datée de votre fournisseur. Remplacez <p_in> et <p_out> par vos prix en euros par token :
# <p_in> et <p_out> : à renseigner depuis la grille datée de votre fournisseur.- record: price_in_eur_per_token labels: { gen_ai_request_model: "demo-llm" } expr: vector(<p_in>)- record: price_out_eur_per_token labels: { gen_ai_request_model: "demo-llm" } expr: vector(<p_out>)La jointure group_left de la règle de coût lit alors ces séries. Tant qu’aucune série de prix n’existe pour un modèle, la jointure ne renvoie rien et la règle n’émet pas de coût pour ce modèle : les règles en jetons (genai:input_tokens_per_request:avg) restent disponibles. En production ces deux séries sont produites par un job qui relit une table de prix versionnée. Le lab public genaiotel suit une variante : il calcule le coût de chaque span dans le Collector (OTTL) à partir d’une table de prix, puis une règle vmalert en tire la série de coût.
Tableaux de bord Grafana : un tableau par couche, corrélé par trace_id, avec en panneaux principaux la latence p95, le taux d’erreur par error.type, la distribution des finish_reasons, le coût par requête et par fonctionnalité et les tokens entrée par requête dans le temps. Règle de conception : chaque panneau répond à une décision, sinon il est supprimé ; un panneau qui représente un SLO a son alerte (voir le guide, 9.5).
Révisé le 2 octobre 2026 : prix retirés, noms de métriques corrigés (suffixe _seconds ajouté par l’exportateur), règle TTFT ajoutée, filtre de contenu déplacé dans le pipeline de traces, renvoi au lab public genaiotel, mention du rachat de Langfuse par ClickHouse. Les métriques de jetons évoluent dans le dépôt semantic-conventions-genai (voir la partie I).
Révisé le 4 octobre 2026 : la règle de conception des tableaux de bord renvoie au guide, 9.5, le piège correspondant ayant quitté la partie VIII.