
Sessioni, trace e span nell'osservabilità LLM: uno di questi non è un livello strutturale
La pagina dei termini di Datadog, il primo risultato su Google per sessioni trace span nell'osservabilità LLM, definisce due di queste tre parole. Non tre. Quella mancante corrisponde a gen_ai.conversation.id, e il motivo per cui manca è che la specifica OpenTelemetry non l'ha mai resa un livello strutturale. Se ti servono le ragioni dell'osservabilità in sé, parti da qui. Questo post riprende da dove si ferma quello: il modello dei dati.
Punti chiave
- Gli span si annidano dentro i trace; i trace si raggruppano in sessioni. L'annidamento va dall'interno verso l'esterno: prima lo span, poi il trace, poi la sessione.
- Uno span è una singola operazione misurata nel tempo. Un trace è una richiesta end-to-end. Una sessione è una conversazione multi-turno.
- Le convenzioni GenAI di OpenTelemetry definiscono gli span e l'attributo
gen_ai.conversation.id. Non definiscono un livello di sessione. - Gli ID di trace e span si propagano automaticamente tramite il contesto. L'ID di sessione no. Lo imposti tu, a ogni turno.
Sessioni, trace e span a colpo d'occhio
Nell'osservabilità LLM, uno span è una singola operazione misurata nel tempo (una chiamata al modello, una fase di retrieval), un trace è l'albero di span prodotto da una richiesta e una sessione raggruppa molti trace della stessa conversazione. L'annidamento va verso l'interno: span dentro trace, trace dentro sessioni. Il terzo raggruppamento è quello che non è ciò che sembra.
| Livello | Cosa racchiude | Quanto vive | Chi imposta l'ID | A cosa risponde | Conteggio tipico per conversazione |
|---|---|---|---|---|---|
| Sessione | Molti trace di una conversazione utente | Da minuti a giorni; termina per timeout di inattività o chiusura esplicita (definiti dal vendor) | Tu, manualmente, a ogni turno | L'intera conversazione ha avuto successo? | 1 |
| Trace | Una richiesta o un turno end-to-end | Da millisecondi a secondi | Automatico (SDK / OTel) | Cos'è successo in questo turno? | Di solito 5–20 |
| Span | Una operazione: un retrieval, una chiamata al modello, una chiamata a un tool | Da meno di un millisecondo a secondi | Automatico (SDK / OTel) | Quale fase era lenta, sbagliata o costosa? | Circa 3–30 per trace |
Queste cifre su conteggio e durata sono intervalli tipici che ci si aspetta in un chatbot RAG o in un loop di agente, non misurazioni di un test controllato. I tuoi numeri saranno diversi. Ciò che non cambia: la riga Sessione è quella che non è un livello strutturale nella specifica, e la sezione «Sessioni: il livello che il tuo tool probabilmente ha inventato» lo dimostra.
Cos'è uno span e cos'è un tipo di span?
Uno span è una singola operazione misurata nel tempo, dotata di nome, timestamp di inizio, timestamp di fine, codice di stato e un insieme di attributi chiave-valore. Nel tracing LLM, gli attributi sono il punto in cui vivono i dati utili: gen_ai.usage.input_tokens, gen_ai.usage.output_tokens e gen_ai.request.model ti dicono quanto è costata l'operazione e quale modello l'ha eseguita.
Uno span è una operazione, non una chiamata di funzione
Ogni span porta con sé un puntatore all'ID dello span padre (vuoto nello span radice) che costruisce l'albero. L'insieme degli attributi è aperto: ci alleghi il contesto che ti serve. Le convenzioni GenAI di OpenTelemetry per gli span (stato: Development) richiedono gen_ai.operation.name e gen_ai.provider.name su ogni span GenAI e raccomandano gli attributi di consumo dei token qui sopra.
Una regola pratica dalla pagina dei termini di Datadog: gli span LLM, Workflow e Agent possono fungere da span radice; gli span Tool, Task, Embedding e Retrieval no. È una regola di Datadog, non universale, ma è l'unico vendor a dichiararla, e ti evita di costruire un trace che inizia su una chiamata a un tool senza padre.
Tipi di span: la stessa idea, cinque vocabolari
Ogni tool ha bisogno di un modo per dire «questo span è una chiamata al modello» rispetto a «questo span è un retrieval». Semplicemente non concordano sulla parola:
| Tool | La sua parola per «tipo di operazione» | Valori |
|---|---|---|
| OpenTelemetry GenAI | Attributo gen_ai.operation.name | 15 valori noti (chat, embeddings, execute_tool, invoke_agent, retrieval e altri 10); se uno si applica, DEVE essere usato; valori personalizzati consentiti quando nessuno si applica |
| Datadog | Span kind | LLM, Workflow, Agent, Tool, Task, Embedding, Retrieval |
| OpenInference / Phoenix | Span kind | CHAIN, LLM, TOOL, RETRIEVER, RERANKER, EMBEDDING, AGENT, GUARDRAIL, EVALUATOR, PROMPT |
| Langfuse | Tipo di observation | generation, span, event |
| LangSmith | Tipo di run | LLM, chain, tool, retriever |
La specifica OpenInference elenca dieci tipi. Datadog ne elenca sette. OTel prende una terza strada: il suo registro degli attributi GenAI pubblica 15 valori noti per gen_ai.operation.name (chat, create_agent, create_memory, create_memory_store, delete_memory, delete_memory_store, embeddings, execute_tool, generate_content, invoke_agent, invoke_workflow, plan, retrieval, search_memory, text_completion) e stabilisce che, se uno di essi si applica, quel valore DEVE essere usato; un valore personalizzato PUÒ essere usato solo quando nessuno è adatto. Quindi è un enum semi-aperto, non l'assenza di un enum. Tre liste, tre lunghezze e nessun allineamento tra loro. Se stai scegliendo un tool, questo divario di vocabolario conta più dell'elenco di funzionalità, perché è ciò su cui saranno impostate le tue dashboard e i filtri di alert.
Cos'è un trace e perché la forma ad albero conta?
Un trace è l'albero di span prodotto da una richiesta. Uno span radice sta in cima; ogni altro span pende sotto di esso tramite archi di ID-span-padre. La forma ad albero è il punto centrale: un log piatto ti dice che qualcosa era lento, ma l'albero ti dice quale fase era lenta e quale fase ha prodotto l'output sbagliato.
chat_request (root) 2,340ms
├── retrieval 410ms
│ └── rerank 85ms
├── chat gpt-4o 1,720ms
└── tool_call: search_calendar 190msLeggi quell'albero e la diagnosi è immediata: il 74% della latenza stava nella chiamata al modello, non nel retrieval. Un log piatto di cinque timestamp ti dà lo stesso totale ma nessuna attribuzione.
Un loop di agente rende questo albero più profondo e più ampio di una semplice richiesta RAG. Ogni chiamata a un tool genera il proprio sotto-albero; un turno di agente in cinque fasi può facilmente produrre più di 30 span sotto un'unica radice. È normale, ed è il motivo per cui esiste la domanda sulla granularità degli span più avanti.
Conta anche la distinzione tra tracing e logging: il logging registra eventi, il tracing registra la causalità. Se stai ancora decidendo cosa loggare rispetto a cosa tracciare, il nostro post sulle migliori pratiche di logging LLM traccia quella linea.
Sessioni: il livello che il tuo tool probabilmente ha inventato
No. Una sessione non è un livello strutturale nelle convenzioni GenAI di OpenTelemetry. La specifica definisce gli span e l'attributo gen_ai.conversation.id (richiesto condizionalmente, «quando disponibile», stato: Development), descritto come l'identificatore univoco di una conversazione o thread usato per correlare i messaggi. I vendor poi costruiscono il proprio oggetto sessione sopra quell'attributo. Nessun altro in questa SERP dichiara apertamente lo stato della specifica, quindi eccolo.
La conseguenza è la frase per cui esiste questo intero post:
Una sessione è una chiave di raggruppamento, non uno span padre. Non si propaga come fa un trace ID; la imposti tu stesso a ogni turno.
Se perdi un turno, quel turno esce dalla sessione. Non esiste propagazione automatica del contesto per essa.
Quando inizia e finisce una sessione?
Definito dal vendor. Alcuni tool aprono una sessione al primo trace che porta un nuovo ID conversazione e la chiudono per timeout di inattività (Langfuse ha come default una finestra configurabile). Altri richiedono una chiamata di chiusura esplicita. La specifica non dice nulla sul ciclo di vita perché non modella la sessione come oggetto.
Cosa si porta da un turno all'altro e cosa no?
La finestra di contesto del modello non è la sessione. La sessione è una chiave di raggruppamento sopra trace indipendenti. Ogni turno ha il proprio trace, il proprio span radice, i propri conteggi di token. Ciò che si porta è l'attributo ID conversazione che hai impresso su ogni span radice. Ciò che non si porta: latenza, consumo di token, struttura degli span. Quelli sono per trace.
Cosa misura una metrica a livello di sessione?
Cose che un singolo trace non può misurare: tasso di risoluzione (la conversazione ha risolto il problema dell'utente?), turni per risposta (quanti trace prima che l'utente ottenesse ciò che gli serviva?) e conversazioni abbandonate (sessioni senza segnale di chiusura). Eseguire eval sui trace live a livello di sessione è il modo in cui individui i fallimenti multi-turno che sembrano corretti turno per turno.
Il codice, neutrale rispetto ai vendor
Questo snippet usa solo primitive OTel stabili. Nessun SDK di vendor. Crea uno span radice per un turno, uno span figlio per il retrieval, un figlio per la chiamata al modello e imposta gen_ai.conversation.id così che tre turni finiscano in una sessione:
from opentelemetry import trace
tracer = trace.get_tracer("my-llm-app")
SESSION_ID = "conv-8f3a2c" # same value on every turn
def handle_turn(user_message: str):
with tracer.start_as_current_span("chat_request") as root:
# You set this. It does not propagate automatically.
root.set_attribute("gen_ai.conversation.id", SESSION_ID)
with tracer.start_as_current_span("retrieval") as ret:
ret.set_attribute("gen_ai.operation.name", "retrieval")
docs = retrieve(user_message)
with tracer.start_as_current_span("chat gpt-4o") as llm:
llm.set_attribute("gen_ai.operation.name", "chat")
llm.set_attribute("gen_ai.provider.name", "openai")
llm.set_attribute("gen_ai.request.model", "gpt-4o")
response = call_model(user_message, docs)
llm.set_attribute("gen_ai.usage.input_tokens", 1_204)
llm.set_attribute("gen_ai.usage.output_tokens", 312)
return responseChiama handle_turn tre volte con lo stesso SESSION_ID e tutti e tre i trace si raggruppano sotto una sessione in qualsiasi backend che legge l'attributo. Cambia l'ID e hai iniziato una nuova sessione. Questo è l'intero meccanismo.
Abbiamo letto la documentazione di cinque vendor, affiancata. Non concordano.
Il 30/07/2026 abbiamo letto, affiancata, la documentazione attuale del modello dei dati di Langfuse, LangSmith, OpenInference / Phoenix e Datadog, più la specifica GenAI di OpenTelemetry per gli span. Quattro dei cinque chiamano lo stesso oggetto in modo diverso. Solo uno tratta la sessione come oggetto di prima classe anziché come attributo. La pagina dei termini di Datadog, il primo risultato Google per questa query, non definisce affatto la sessione.
| Concetto | Semconv GenAI OTel | Langfuse | LangSmith | OpenInference / Phoenix | Datadog |
|---|---|---|---|---|---|
| Conversazione intera | Attributo gen_ai.conversation.id | Session (raggruppamento opzionale di trace) | Thread (via metadati session_id / thread_id) | Attributo di span session.id | Non definito nella pagina dei termini |
| Una richiesta | Trace | Trace | Trace («una collezione di run») | Trace | Trace |
| Una operazione | Span | Observation (span / generation / event) | Run («uno span che rappresenta una singola unità di lavoro») | Span con un tipo di span | Span con un tipo di span |
Una nota sulla prima riga: session.id di OpenInference non è nella specifica dei trace linkata qui sopra, che copre i dieci tipi di span. È definito nel file gemello OpenInference semantic conventions come l'identificatore univoco di una sessione. Due file, una specifica.
Non abbiamo inventato noi il confronto tra vendor; anche FutureAGI pubblica una tabella OTel contro vendor. Le nostre due aggiunte sono la riga della sessione (FutureAGI la salta) e la trappola della stessa parola con significato diverso: «observation» di Langfuse e «run» di LangSmith sono lo stesso oggetto dello span, mentre i tipi di span di Datadog e OpenInference sono vocabolari diversi per la stessa idea.
Langfuse lo chiama observation, LangSmith lo chiama run, Datadog lo chiama span. Stesso oggetto, tre dashboard che si rompono quando migri.
Questa è la nostra lettura del costo di migrazione, non un'affermazione di un vendor. Ma è il motivo per cui filtri salvati, configurazioni di eval e regole di alert impostati su «observation» o «run» smettono di funzionare il giorno in cui cambi tool. Non stai rinominando un campo. Stai rinominando un livello. Se stai valutando quei due tool specifici, il nostro confronto Langfuse vs LangSmith entra nel dettaglio della divergenza.
I lettori potrebbero anche avere già Opik, PostHog, Sentry o Weights & Biases nel proprio stack; Google associa tutti e quattro a llm tracing e ognuno mappa questi concetti in modo leggermente diverso. Stai scegliendo quello giusto? La nostra rassegna delle piattaforme di osservabilità copre il settore.
Una nota di freschezza: le convenzioni GenAI sono state spostate nel proprio repository, fuori dal repository principale semantic-conventions. Il vecchio percorso opentelemetry.io/docs/specs/semconv/gen-ai/ ora contiene solo un puntatore.
Quale ID va dove?
Un trace ID identifica una richiesta e si propaga automaticamente tramite il contesto. Uno span ID identifica una operazione all'interno di quel trace, anche questo automatico. Un ID di correlazione (o request ID) proviene dal tuo livello web prima che inizi il tracing, ed è quello che più spesso si confonde con il trace ID. L'ID di sessione è l'eccezione: tocca a te impostarlo, manualmente, a ogni turno.
| ID | Impostato da | Ambito | Confuso con |
|---|---|---|---|
| Trace ID | Automatico | Una richiesta; si propaga tramite il contesto | L'ID di correlazione del tuo livello web |
| Span ID | Automatico | Una operazione | , |
| ID span padre | Automatico | Costruisce l'albero; vuoto nello span radice | , |
| ID sessione / conversazione | Tu, manualmente, a ogni turno | Molti trace | Si presume che si propaghi. Non lo fa. |
| User ID | Tu, manualmente | Molte sessioni | L'ID di sessione |
| Request ID / ID di correlazione | Il tuo livello web, prima che inizi il tracing | Una richiesta HTTP | Il trace ID (questo è l'errore grosso) |
La regola pratica: allega gen_ai.conversation.id come attributo di span sullo span radice di ogni turno e imprimi lo user ID accanto ad esso. Salta un turno e le tue metriche a livello di sessione perdono silenziosamente quel turno.
Un avvertimento sulla cardinalità: gli user ID e gli ID di sessione sono valori ad alta cardinalità. Questo conta per la fattura di indicizzazione del tuo backend, che è il problema della prossima sezione.
Quanto deve essere granulare uno span?
Due modalità di fallimento, entrambe comuni:
Over-spanning. Uno span per ogni chiamata di funzione ti dà un trace da 400 span che nessuno riesce a leggere e una fattura per span che nessuno ha approvato. I backend gestiti in cloud (Datadog, Langfuse Cloud) hanno prezzi basati sul volume di span. Un loop di agente prolisso che strumenta ogni concatenazione di stringhe brucerà un free tier in un pomeriggio.
Under-spanning. Uno span per «l'intera chain» ti dice che era lenta ma non dove. Finisci per rimettere dentro le print statement, che è esattamente ciò che il tracing doveva sostituire.
La regola pratica (ed è una regola pratica, non una misurazione): traccia con uno span i confini dove avviene una decisione o una chiamata esterna.
- Fase di retrieval: span.
- Chiamata di rerank: span.
- Ogni chiamata al modello: span.
- Ogni chiamata a un tool: span.
- Ogni controllo guardrail: span.
- Trasformazioni pure in-process (formattazione di stringhe, parsing JSON, assemblaggio del prompt): attributi sullo span padre, non span propri.
Su cardinalità, campionamento e conservazione:
- Gli attributi ad alta cardinalità (user ID, prompt completi) gonfiano i costi di storage. Campionali o troncali.
- La maggior parte dei backend ti permette di campionare a livello di trace. Tieni il 100% dei trace con errori; campiona il percorso felice.
- Le finestre di conservazione variano: 7 giorni sui free tier, 30–90 giorni sui piani a pagamento. Decidi prima di aver bisogno dei dati.
Per il modello di costo reale dietro il volume di span e i prezzi per span, vedi la nostra guida al monitoraggio dei costi LLM. Non lo ricostruiamo qui.
Come Techsy affronta tutto questo
Per il lavoro sugli agenti dei clienti, standardizziamo su tre regole:
- Un trace per turno. Non unire mai due turni utente in un trace, anche se l'agente va in loop internamente.
- Un ID di sessione impresso su ogni span radice, impostato nel codice applicativo, mai dato per scontato che si propaghi.
- Tipi di span mantenuti su un piccolo insieme fisso (retrieval, inference, tool, guardrail) così che le dashboard sopravvivano a un cambio di vendor.
La terza regola è quella che i team saltano, ed è quella che salva una migrazione. Se il tuo vocabolario di span è legato all'enum di un vendor, ogni alert e ogni vista salvata si rompe il giorno in cui cambi.
Se stai costruendo un sistema di agente e vuoi un secondo parere sull'architettura di tracing, richiedi una consulenza gratuita.
Informazioni sull'autore
Mert Batur è Co-Founder di Techsy.io, dove il team rilascia agenti AI, sistemi di automazione e pipeline vocali/SDR per clienti B2B. Scrive dello stack di tooling LLM che il team Techsy usa davvero in produzione. Collegati su LinkedIn.
Domande frequenti
Cos'è uno span nel distributed tracing?
Uno span è una singola unità di lavoro misurata nel tempo: ha un nome, un'ora di inizio, un'ora di fine, uno stato e un insieme di attributi. Gli span si collegano tra loro tramite riferimenti all'ID dello span padre, formando un albero. Nelle applicazioni LLM, uno span tipicamente avvolge una chiamata al modello, un retrieval o una invocazione di tool.
Cos'è uno span in Datadog?
In LLM Observability di Datadog, uno span è la stessa operazione misurata nel tempo, ma Datadog aggiunge una tassonomia di span kind: LLM, Workflow, Agent, Tool, Task, Embedding e Retrieval. Solo i tipi LLM, Workflow e Agent possono fungere da span radice. La tassonomia è specifica di Datadog; non fa parte dello standard OpenTelemetry.
Quali sono i quattro pilastri dell'osservabilità?
I quattro pilastri sono log, metriche, trace e (a seconda dell'inquadramento) profili o eventi. I trace sono il pilastro in cui vive questo post. Il caso LLM aggiunge una piega: il consumo di token e l'identità del modello sono attributi sugli span del trace, non flussi di metriche separati, il che riduce quelli che sarebbero due pilastri a una singola query.
Quali sono i quattro segnali d'oro dell'osservabilità?
Latenza, traffico, errori e saturazione. Per i sistemi LLM, latenza significa time-to-first-token e tempo totale di generazione; traffico significa richieste al secondo per modello; errori significa span falliti (codice di stato ERROR); saturazione significa esaurimento del budget di token o profondità della coda. I segnali sono gli stessi; le unità differiscono.
La sessione fa parte della specifica OpenTelemetry?
Non come livello strutturale. Le convenzioni GenAI di OTel per gli span definiscono gen_ai.conversation.id come attributo richiesto condizionalmente («quando disponibile») per correlare i messaggi in una conversazione o thread. Sta sugli span. Vendor come Langfuse e LangSmith costruiscono i propri oggetti sessione o thread sopra di esso.
Che differenza c'è tra trace ID, span ID e ID di correlazione?
Un trace ID identifica una richiesta e si propaga automaticamente attraverso tutti i servizi a valle. Uno span ID identifica una operazione all'interno di quel trace. Un ID di correlazione (o request ID) è generato dal tuo livello web prima che inizi il tracing ed è il valore che più spesso viene scambiato per il trace ID. Si sovrappongono nell'ambito ma hanno origini diverse.
Quanti span dovrebbe avere un trace?
Non c'è una risposta fissa, ma gli intervalli tipici sono 3–30 per una richiesta RAG e 10–50+ per un loop di agente con più chiamate a tool. La regola pratica: traccia con uno span le chiamate esterne e i punti di decisione, non le trasformazioni in-process. Se il tuo trace supera i 100 span, probabilmente stai strumentando troppo.
Le «observation» di Langfuse sono la stessa cosa degli span?
Sì. Una observation di Langfuse è lo stesso oggetto di uno span OTel: una operazione misurata nel tempo con attributi. Langfuse divide le observation in tre tipi (generation, span, event) laddove OTel usa gen_ai.operation.name. Se stai valutando tool che leggono i tuoi trace, la nostra rassegna dei tool di valutazione LLM copre quali accettano entrambi i vocabolari.
Come si raggruppa una conversazione multi-turno di un chatbot in una sessione?
Imposta lo stesso identificatore di conversazione sullo span radice di ogni turno. In termini OTel, è gen_ai.conversation.id. In Langfuse, passi un session_id quando crei i trace. In LangSmith, imposti i metadati session_id o thread_id. Perdi un turno e quel turno esce dal raggruppamento.
Mi servono le sessioni se gestisco solo richieste a turno singolo?
Probabilmente no. Le sessioni esistono per correlare più trace in una conversazione. Se ogni richiesta è indipendente (una API di classificazione, un riassuntore one-shot), le metriche a livello di trace sono sufficienti. Aggiungi le sessioni quando ti servono metriche cross-turno: tasso di risoluzione, turni per risposta o costo a livello di conversazione. La nostra guida alla valutazione degli LLM copre quando le eval a livello di sessione valgono il loro costo.
La versione breve
Gli span si annidano dentro i trace; i trace si raggruppano in sessioni. L'annidamento è reale, ma la specifica struttura solo due dei tre livelli. gen_ai.conversation.id è un attributo che imposti tu stesso, non uno span padre che si propaga. E il vendor che scegli oggi chiama questi oggetti in modo diverso dal vendor a cui passerai tra 18 mesi, quindi mantieni il tuo vocabolario di span piccolo e portatile.
Se stai scegliendo una piattaforma, inizia con il nostro confronto tra piattaforme di osservabilità. Se stai costruendo eval sopra i tuoi trace, la guida alla valutazione degli LLM riprende da qui.