[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-it-ai-pipeline-tracing-costs-mvp":3},{"id":4,"title":5,"author":6,"body":7,"cover":585,"description":586,"draft":587,"extension":588,"locale":589,"meta":590,"navigation":591,"path":592,"publishedAt":593,"seo":594,"seoTitle":595,"slug":596,"stem":597,"tags":598,"__hash__":600},"blog\u002Fit\u002Fblog\u002Fai-pipeline-tracing-costs-mvp.md","Tracing e costi minimi per pipeline AI in produzione","Manuel Tardivo",{"type":8,"value":9,"toc":574},"minimark",[10,27,52,57,60,92,96,103,228,235,244,260,264,275,296,303,307,310,331,337,341,344,366,377,381,458,465,472,552,556,570],[11,12,13,14,18,19,26],"p",{},"Prima di comprare una piattaforma “LLM observability”, puoi già capire se la pipeline regge il carico e quanto ti costa con ",[15,16,17],"strong",{},"pochi numeri"," raccolti nel tuo codice o in un log strutturato. Questo pezzo descrive un MVP di metriche allineato al metodo ",[20,21,25],"a",{"href":22,"rel":23},"https:\u002F\u002Fwww.weave.works\u002Fblog\u002Fthe-red-method-patterns-for-architecting-microservices",[24],"nofollow","RED"," (rate, errors, duration), adattato alle chiamate modello.",[11,28,29,32,33,38,39,43,44,47,48,51],{},[15,30,31],{},"Premessa verificabile",": i prezzi per token cambiano — prendi i valori dalla ",[20,34,37],{"href":35,"rel":36},"https:\u002F\u002Fopenai.com\u002Fapi\u002Fpricing\u002F",[24],"pagina pricing"," del provider che usi (OpenAI, Anthropic, Google, ecc.) e ricalcola ",[40,41,42],"code",{},"estimated_cost"," con la formula documentata (tipicamente ",[40,45,46],{},"input_tokens * price_in + output_tokens * price_out","). Qui parliamo di ",[15,49,50],{},"come misurare",", non di cifre fisse.",[53,54,56],"h2",{"id":55},"cosa-non-misurare-allinizio","Cosa non misurare all’inizio",[11,58,59],{},"Evita questi antipattern nelle prime settimane:",[61,62,63,74,80,86],"ul",{},[64,65,66,69,70,73],"li",{},[15,67,68],{},"Dashboard con 30 grafici"," prima di avere un elenco stabile di ",[40,71,72],{},"request_id"," correlati a un trace minimo.",[64,75,76,79],{},[15,77,78],{},"Token “approssimati”"," senza fonte (se il client non espone usage, leggi la risposta ufficiale del provider prima di inventare moltiplicatori).",[64,81,82,85],{},[15,83,84],{},"Solo latency media",": la coda p95 ti dice molto di più su UX e timeout.",[64,87,88,91],{},[15,89,90],{},"Log del prompt completo in prod"," senza policy di retention: costo storage + rischio privacy.",[53,93,95],{"id":94},"metriche-mvp-definizioni-e-unità","Metriche MVP: definizioni e unità",[11,97,98,99,102],{},"Per ogni ",[15,100,101],{},"invocazione"," di un modello (una chiamata HTTP al provider o al tuo gateway) registra:",[104,105,106,122],"table",{},[107,108,109],"thead",{},[110,111,112,116,119],"tr",{},[113,114,115],"th",{},"Campo",[113,117,118],{},"Unità",[113,120,121],{},"Nota",[123,124,125,139,160,176,203,216],"tbody",{},[110,126,127,133,136],{},[128,129,130],"td",{},[40,131,132],{},"duration_ms",[128,134,135],{},"ms",[128,137,138],{},"wall-clock dal send alla risposta completa (o fine stream).",[110,140,141,150,153],{},[128,142,143,146,147],{},[40,144,145],{},"input_tokens"," \u002F ",[40,148,149],{},"output_tokens",[128,151,152],{},"interi",[128,154,155,156,159],{},"dal payload ",[40,157,158],{},"usage"," del provider, se presente.",[110,161,162,167,170],{},[128,163,164],{},[40,165,166],{},"ok",[128,168,169],{},"boolean",[128,171,172,175],{},[40,173,174],{},"false"," se status non 2xx o eccezione prima della risposta.",[110,177,178,183,186],{},[128,179,180],{},[40,181,182],{},"error_class",[128,184,185],{},"stringa corta",[128,187,188,189,192,193,192,196,192,199,202],{},"es. ",[40,190,191],{},"rate_limit",", ",[40,194,195],{},"timeout",[40,197,198],{},"invalid_request",[40,200,201],{},"provider_5xx",".",[110,204,205,210,213],{},[128,206,207],{},[40,208,209],{},"model",[128,211,212],{},"stringa",[128,214,215],{},"id esatto inviato all’API.",[110,217,218,223,225],{},[128,219,220],{},[40,221,222],{},"step",[128,224,212],{},[128,226,227],{},"se la pipeline ha più step (retrieve, classify, generate).",[11,229,230,231,234],{},"Per batch o job asincroni, stessa riga ma con ",[40,232,233],{},"job_id"," invece di richiesta HTTP sincrona.",[11,236,237,240,241,243],{},[15,238,239],{},"Percentili",": calcola p50 e p95 di ",[40,242,132],{}," almeno giornalmente (query SQL o metric sink). Il p95 segnala code lente e provider instabili prima che la media si muova.",[11,245,246,249,250,253,254,256,257,259],{},[15,247,248],{},"Error rate",": ",[40,251,252],{},"errors \u002F attempts"," per ",[40,255,209],{}," e ",[40,258,222],{},". Un picco localizzato su uno step spesso indica prompt tool-calling o schema validation, non il modello “in generale”.",[53,261,263],{"id":262},"costo-stimato-vs-costo-misurato","Costo stimato vs costo misurato",[11,265,266,267,270,271,274],{},"Il costo ",[15,268,269],{},"misurato"," deriva da token effettivi * prezzi listino. Il costo ",[15,272,273],{},"stimato"," può essere un budget statico per richiesta (es. max token dichiarati * costo peggiore). Se stimato e misurato divergono sistematicamente, controlla:",[276,277,278,284,290],"ol",{},[64,279,280,283],{},[15,281,282],{},"Caching"," o prompt prefix lungo che cambia gli input token reali.",[64,285,286,289],{},[15,287,288],{},"Stream"," che taglia la risposta: output token più bassi del previsto.",[64,291,292,295],{},[15,293,294],{},"Retry"," nascosti nel client: moltiplicano le richieste senza correlazione in UI.",[11,297,298,299,302],{},"Traccia ",[40,300,301],{},"attempt"," (1 = primo try, 2 = dopo retry) per non confondere un utente con tre richieste con un job.",[53,304,306],{"id":305},"alerting-sensato","Alerting sensato",[11,308,309],{},"Tre soglie bastano per molte squadre:",[276,311,312,318,324],{},[64,313,314,317],{},[15,315,316],{},"Budget giornaliero"," in EUR\u002FUSD stimato dai token (somma della giornata > X).",[64,319,320,323],{},[15,321,322],{},"p95 latency"," > soglia contrattuale (es. 8s su un endpoint sync).",[64,325,326,328,329,202],{},[15,327,248],{}," > Y% per 15 minuti sullo stesso ",[40,330,222],{},[11,332,333,334,336],{},"Evita alert sul solo “un 500”: usa ",[40,335,182],{}," e volume.",[53,338,340],{"id":339},"quando-passare-a-un-tool-dedicato","Quando passare a un tool dedicato",[11,342,343],{},"Passa a Langfuse, Helicone, LangSmith o simili quando almeno una di queste è vera:",[61,345,346,353,359],{},[64,347,348,349,352],{},"Serve ",[15,350,351],{},"tracing distribuito"," su più servizi (retrieve DB + worker + LLM) con correlazione auto.",[64,354,355,358],{},[15,356,357],{},"Utenti non tecnici"," devono esplorare trace senza accesso ai log raw.",[64,360,361,362,365],{},"Vuoi ",[15,363,364],{},"dataset di eval"," versionati accanto ai trace.",[11,367,368,369,372,373,376],{},"Fino ad allora, una tabella ",[40,370,371],{},"llm_calls"," in Postgres\u002FSQLite con le colonne sopra + indice su ",[40,374,375],{},"created_at"," risolve l’80% delle domande “perché ieri è esploso il costo?”.",[53,378,380],{"id":379},"query-sql-di-esempio","Query SQL di esempio",[382,383,388],"pre",{"className":384,"code":385,"language":386,"meta":387,"style":387},"language-sql shiki shiki-themes github-light github-dark","SELECT\n  date_trunc('day', created_at) AS day,\n  model,\n  sum(input_tokens + output_tokens) AS tokens,\n  avg(duration_ms) AS avg_ms,\n  percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) AS p95_ms,\n  sum(CASE WHEN ok THEN 0 ELSE 1 END)::float \u002F count(*) AS error_rate\nFROM llm_calls\nWHERE created_at > now() - interval '7 days'\nGROUP BY 1, 2\nORDER BY 1 DESC, tokens DESC;\n","sql","",[40,389,390,398,404,410,416,422,428,434,440,446,452],{"__ignoreMap":387},[391,392,395],"span",{"class":393,"line":394},"line",1,[391,396,397],{},"SELECT\n",[391,399,401],{"class":393,"line":400},2,[391,402,403],{},"  date_trunc('day', created_at) AS day,\n",[391,405,407],{"class":393,"line":406},3,[391,408,409],{},"  model,\n",[391,411,413],{"class":393,"line":412},4,[391,414,415],{},"  sum(input_tokens + output_tokens) AS tokens,\n",[391,417,419],{"class":393,"line":418},5,[391,420,421],{},"  avg(duration_ms) AS avg_ms,\n",[391,423,425],{"class":393,"line":424},6,[391,426,427],{},"  percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) AS p95_ms,\n",[391,429,431],{"class":393,"line":430},7,[391,432,433],{},"  sum(CASE WHEN ok THEN 0 ELSE 1 END)::float \u002F count(*) AS error_rate\n",[391,435,437],{"class":393,"line":436},8,[391,438,439],{},"FROM llm_calls\n",[391,441,443],{"class":393,"line":442},9,[391,444,445],{},"WHERE created_at > now() - interval '7 days'\n",[391,447,449],{"class":393,"line":448},10,[391,450,451],{},"GROUP BY 1, 2\n",[391,453,455],{"class":393,"line":454},11,[391,456,457],{},"ORDER BY 1 DESC, tokens DESC;\n",[11,459,460,461,464],{},"(Adeguare sintassi ",[40,462,463],{},"percentile_cont"," se usi SQLite—usa window o export verso BI.)",[53,466,468,469,471],{"id":467},"allinea-le-metriche-a-usage-e-opzionale-a-opentelemetry","Allinea le metriche a ",[40,470,158],{}," e (opzionale) a OpenTelemetry",[61,473,474,512,546],{},[64,475,476,479,480,482,483,485,486,489,490,495,496,500,501,146,503,505,506,509,510,202],{},[15,477,478],{},"Payload dei provider",": quando disponibile, salva campi grezzi da ",[40,481,158],{}," (prompt\u002Fcompletion\u002Fcached) così riconcili fattura e log. Per OpenAI, oggetti Chat\u002FResponses espongono ",[40,484,158],{}," e, con prompt caching attivo, dettagli tipo ",[40,487,488],{},"prompt_tokens_details.cached_tokens","—vedi ",[20,491,494],{"href":492,"rel":493},"https:\u002F\u002Fplatform.openai.com\u002Fdocs\u002Fapi-reference\u002Fchat\u002Fobject",[24],"API Reference"," e note sul ",[497,498,499],"em",{},"prompt caching"," nei guide aggiornati. Stesso approccio per Anthropic\u002FGoogle: normalizza in ",[40,502,145],{},[40,504,149],{}," ",[15,507,508],{},"nel tuo"," schema ",[40,511,371],{},[64,513,514,517,518,521,522,192,525,192,528,531,532,537,538,541,542,545],{},[15,515,516],{},"OpenTelemetry GenAI",": le ",[15,519,520],{},"semantic conventions"," per span client verso LLM (",[40,523,524],{},"gen_ai.operation.name",[40,526,527],{},"gen_ai.request.model",[40,529,530],{},"gen_ai.usage.*",", …) sono in evoluzione—utile se già standardizzi su OTel. Riferimento: ",[20,533,536],{"href":534,"rel":535},"https:\u002F\u002Fopentelemetry.io\u002Fdocs\u002Fspecs\u002Fsemconv\u002Fgen-ai\u002Fgen-ai-spans",[24],"Gen AI spans"," (stato ",[497,539,540],{},"development\u002Fexperimental"," al momento della ricerca: verifica versione e opt-in ",[40,543,544],{},"OTEL_SEMCONV_STABILITY_OPT_IN"," se usi SDK datati).",[64,547,548,551],{},[15,549,550],{},"Coerenza con RED",": un singolo span “inference” per chiamata modello, attributi stabili, e eventi solo in dev—evita allegare prompt completi in span prod.",[53,553,555],{"id":554},"sintesi","Sintesi",[11,557,558,559,562,563,256,566,569],{},"Misura ",[15,560,561],{},"durata, token, esito, step"," per invocazione; calcola ",[15,564,565],{},"p95",[15,567,568],{},"error rate","; confronta costo a listino. Il resto è polish, non prerequisito per sapere se il prodotto regge.",[571,572,573],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":387,"searchDepth":400,"depth":400,"links":575},[576,577,578,579,580,581,582,584],{"id":55,"depth":400,"text":56},{"id":94,"depth":400,"text":95},{"id":262,"depth":400,"text":263},{"id":305,"depth":400,"text":306},{"id":339,"depth":400,"text":340},{"id":379,"depth":400,"text":380},{"id":467,"depth":400,"text":583},"Allinea le metriche a usage e (opzionale) a OpenTelemetry",{"id":554,"depth":400,"text":555},"\u002Fblog\u002F03_ai_pipeline_tracing_costs_mvp_1x.webp","Quali metriche servono per costi e affidabilità di una pipeline LLM in produzione, prima di comprare uno stack di osservabilità enterprise.",false,"md","it",{},true,"\u002Fit\u002Fblog\u002Fai-pipeline-tracing-costs-mvp","2026-05-14",{"title":5,"description":586},"Tracing e costi minimi per pipeline AI","ai-pipeline-tracing-costs-mvp","it\u002Fblog\u002Fai-pipeline-tracing-costs-mvp",[599],"mvp","7WEjOBThIaaAh2isIJ2tzS6-O1uuZHhroNuFx2OyHy0"]