Technique
Taxonomie des métriques LLM et instrumentation Python
Pour : ingénieurs et SREPrérequis : Avoir lu la leçon 2 du parcours et savoir lire du Python.
Module 5 : taxonomie des métriques LLM
Section intitulée « Module 5 : taxonomie des métriques LLM »La taxonomie couvre les quatre dimensions de l’observabilité LLM : qualité des réponses, performance et latence, coûts et tokens, infrastructure GPU et CPU.
5.1 Conventions de nommage recommandées
Section intitulée « 5.1 Conventions de nommage recommandées »- Préfixe
llm_pour toutes les métriques propres aux LLM. - Préfixe
rag_pour les métriques propres à la recherche documentaire (retrieval). - Noms DCGM standard pour les métriques GPU (
DCGM_FI_*). - Suffixes standard :
_total(compteur),_secondsou_ms(histogramme de latence),_ratio(valeur sans unité entre 0 et 1, quel que soit le type : jauge ou histogramme, commerag_context_pressure_ratio),_bytes(jauge).
| Métrique | Type Prometheus | Cardinalité | Labels essentiels |
|---|---|---|---|
llm_requests_total | Compteur | Moyenne | model, provider, pipeline_id, status |
llm_request_duration_ms | Histogramme | Moyenne | model, provider, pipeline_id |
llm_tokens_total{type} | Compteur | Faible | model, provider, type=input ou output |
llm_cost_usd_total | Compteur | Faible | model, provider, tenant_id, use_case |
llm_errors_total | Compteur | Moyenne | model, provider, error_type |
llm_ttft_ms | Histogramme | Moyenne | model, provider |
rag_retrieval_score_avg | Jauge | Faible | index_id, pipeline_id, strategy |
rag_context_pressure_ratio | Histogramme | Faible | pipeline_id, model |
rag_chunks_dropped_total | Compteur | Faible | pipeline_id, reason |
DCGM_FI_DEV_GPU_UTIL | Jauge | Faible | gpu, UUID, pod, namespace, node |
vllm:num_requests_running | Jauge | Faible | model_name, instance |
Module 6 : instrumentation Python, émettre les métriques
Section intitulée « Module 6 : instrumentation Python, émettre les métriques »6.1 Deux approches complémentaires
Section intitulée « 6.1 Deux approches complémentaires »- Bibliothèque cliente Prometheus : l’application expose un point
/metrics, collecté par vmagent. Simple et très répandu. - OTLP via le SDK OpenTelemetry : métriques et traces dans le même pipeline. Recommandé pour les nouvelles applications.
# Option 1 : prometheus_clientpip install prometheus-client
# Option 2 : SDK OTel (métriques et traces unifiées)pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc6.2 Client Prometheus : les métriques LLM de base
Section intitulée « 6.2 Client Prometheus : les métriques LLM de base »from prometheus_client import Counter, Histogram, Gauge, start_http_serverimport time
LLM_REQUESTS = Counter( 'llm_requests_total', 'Total LLM requests', ['model', 'provider', 'pipeline_id', 'status'])LLM_ERRORS = Counter( 'llm_errors_total', 'Total failed LLM requests', ['model', 'provider', 'error_type'])LLM_DURATION = Histogram( 'llm_request_duration_ms', 'LLM request duration in ms', ['model', 'provider', 'pipeline_id'], buckets=[50, 100, 200, 500, 1000, 2000, 5000, 10000, 30000])LLM_TOKENS = Counter( 'llm_tokens_total', 'Total tokens processed', ['model', 'provider', 'type'] # type : input ou output)LLM_COST = Counter( 'llm_cost_usd_total', 'Total cost in USD', ['model', 'provider', 'tenant_id', 'use_case'])LLM_TTFT = Histogram( 'llm_ttft_ms', 'Time to first token in ms', ['model', 'provider'], buckets=[50, 100, 200, 500, 1000, 2000, 5000])RAG_RETRIEVAL_SCORE = Gauge( 'rag_retrieval_score_avg', 'Average retrieval similarity score', ['index_id', 'pipeline_id', 'strategy'])RAG_CONTEXT_PRESSURE = Histogram( 'rag_context_pressure_ratio', 'Context window pressure ratio (0-1)', ['pipeline_id', 'model'], buckets=[0.1, 0.3, 0.5, 0.7, 0.8, 0.85, 0.9, 0.95, 1.0])RAG_CHUNKS_DROPPED = Counter( 'rag_chunks_dropped_total', 'Chunks dropped from the context', ['pipeline_id', 'reason'])
# Expose /metrics sur le port 8000 (9090 est le port habituel de Prometheus)start_http_server(8000)6.3 Décorateur d’instrumentation : suivi automatique des coûts
Section intitulée « 6.3 Décorateur d’instrumentation : suivi automatique des coûts »import functoolsimport jsonimport os
# Grille de prix en USD par token, lue depuis un fichier JSON versionné que vous# fournissez ; aucune valeur ici. À renseigner depuis la grille datée de votre fournisseur :# {"effective_date": "AAAA-MM-JJ", "currency": "USD",# "models": {"grand-modele": {"input": null, "output": null}}}# Sans fichier, TOKEN_PRICES reste vide : le décorateur compte les tokens et n'émet# pas llm_cost_usd_total.PRICE_GRID_FILE = os.getenv('LLM_PRICE_GRID_FILE')PRICE_GRID_EFFECTIVE_DATE = NoneTOKEN_PRICES = {}if PRICE_GRID_FILE: with open(PRICE_GRID_FILE, encoding='utf-8') as fh: _grid = json.load(fh) PRICE_GRID_EFFECTIVE_DATE = _grid['effective_date'] TOKEN_PRICES = {m: p for m, p in _grid['models'].items() if p.get('input') is not None and p.get('output') is not None}
def track_llm_call(model, provider, pipeline_id, tenant_id='default', use_case='chat'): def decorator(func): @functools.wraps(func) async def wrapper(*args, **kwargs): labels = {'model': model, 'provider': provider, 'pipeline_id': pipeline_id} t0 = time.monotonic() status = 'success' try: result = await func(*args, **kwargs) if hasattr(result, 'usage'): in_tok = result.usage.prompt_tokens out_tok = result.usage.completion_tokens LLM_TOKENS.labels(model=model, provider=provider, type='input').inc(in_tok) LLM_TOKENS.labels(model=model, provider=provider, type='output').inc(out_tok) if model in TOKEN_PRICES: p = TOKEN_PRICES[model] cost = in_tok * p['input'] + out_tok * p['output'] LLM_COST.labels(model=model, provider=provider, tenant_id=tenant_id, use_case=use_case).inc(cost) return result except Exception as e: status = type(e).__name__ LLM_ERRORS.labels(model=model, provider=provider, error_type=status).inc() raise finally: LLM_DURATION.labels(**labels).observe((time.monotonic() - t0) * 1000) LLM_REQUESTS.labels(**labels, status=status).inc() return wrapper return decorator
@track_llm_call(model='grand-modele', provider='fournisseur-a', pipeline_id='support-rag', tenant_id='acme')async def call_llm(messages: list) -> dict: # client : le client SDK de votre fournisseur ; utilisez l'identifiant exact du modèle return await client.chat.completions.create(model='identifiant-du-modele', messages=messages)Le label status prend le nom de la classe d’exception : limitez le nombre de types d’exception distincts, ou ramenez-les à une courte liste de catégories, pour borner la cardinalité.
Révisé le 2 octobre 2026 : prix retirés ; la grille de prix du décorateur est lue depuis un fichier que vous fournissez, sans valeur dans le guide et le coût n’est émis que pour les modèles dont le prix est renseigné.