Osservabilità degli agenti LLM: trace, sessioni e 10 problemi
Indice
- Che cosa sono trace, span e sessioni in un agente LLM?
- Quale vista risponde a quale domanda?
- Perché un agente LLM è lento?
- Perché un agente LLM costa troppo?
- Come si fa il debug di un agente LLM partendo dalla segnalazione di un utente?
- Si può tracciare un agente di sviluppo senza modificarne il codice?
- Un modello può leggere le trace al posto nostro?
- Che cosa dovrebbe registrare ogni agente?
- Che cosa non copre il tracing dal gateway?
- Domande frequenti
- Che cos’è l’osservabilità degli agenti LLM?
- Letture correlate
L’osservabilità di un agente LLM parte da due modi di raggruppare le chiamate ai modelli: la trace, che rappresenta una singola esecuzione dell’agente su una linea temporale, e la sessione, che raccoglie un’intera conversazione e il suo costo totale. Basta aggiungere tre header a ogni richiesta perché un gateway LLM ricostruisca entrambe dal proprio log, senza un SDK di tracing. Il gateway è un proxy tra il codice e i provider dei modelli e registra ogni richiesta. Abbiamo usato questa configurazione con un piccolo agente di assistenza e con Claude Code, riproducendo dieci problemi comuni: tre che rallentano l’agente, quattro che ne aumentano i costi e tre che lo fanno fallire.
TL;DR
- Tre header (
X-Trace-Id,X-Span-Id,X-Session-Id) trasformano il log delle richieste del gateway Synthorai in trace e sessioni, senza un SDK di tracing. - Eseguire insieme tre controlli indipendenti di sottoagenti ha ridotto il tempo mediano da 3.7 s a 1.8 s, a parità di costo.
- Spostare il timestamp dalla prima riga del prompt su GPT-6 Luna ha portato il tasso di cache hit dallo 0% al 73%, partendo da una cache vuota.
- Un’esecuzione di Claude Code è costata $0.1728. Le prime chiamate dei suoi tre agenti hanno rappresentato il 72% del totale.
Questo articolo presenta una serie di sette guide. Ogni sezione riassume una guida, ne riporta i risultati e rimanda al codice e agli screenshot. Tutte le esecuzioni risalgono al 10 e 11 ottobre 2026. I valori di latenza sono mediane di cinque esecuzioni; tutti gli altri dati provengono da una singola esecuzione. Il codice si trova su github.com/synthorai-io/use-cases.
Che cosa sono trace, span e sessioni in un agente LLM?
Una trace comprende tutto ciò che un agente fa per gestire un messaggio dell’utente o un’attività. Uno span è un singolo passaggio della trace. Una sessione è l’intera conversazione, composta da più trace. Nel nostro agente di assistenza, una conversazione di due turni con un cliente corrisponde a una sessione con due trace, ciascuna contenente cinque o sei chiamate ai modelli. Alcune chiamate provengono da un sottoagente: un secondo agente che quello principale avvia per svolgere parte del lavoro, con un prompt proprio e spesso un modello meno costoso.
| Termine | Che cosa comprende | Header | Usare lo stesso valore per |
|---|---|---|---|
| Trace | una singola esecuzione dell’agente | X-Trace-Id | tutte le chiamate dell’esecuzione |
| Span | una chiamata al modello nell’esecuzione | X-Span-Id | solo quella chiamata |
| Sessione | una conversazione | X-Session-Id | tutte le chiamate della conversazione |
| Agente | il sottoagente che ha effettuato la chiamata | X-Agent-Id | tutte le chiamate di quel sottoagente |
| Etichette | utente, funzionalità, release | X-Synthorai-Metadata | qualsiasi chiamata da raggruppare o filtrare |
Gli ID sono stringhe a scelta, ciascuna di massimo 128 caratteri ASCII stampabili. Le etichette formano un oggetto JSON piatto con valori stringa: al massimo 10 per richiesta, ciascun valore di massimo 128 byte. Il gateway restituisce gli ID nella risposta e non inoltra nessuno di questi header al provider del modello. Con l’SDK Python di OpenAI, gli header della singola richiesta vanno in extra_headers:
resp = client.chat.completions.create(
model="gpt-6-luna",
messages=messages,
extra_headers={
"X-Session-Id": session_id, # one per conversation
"X-Trace-Id": trace_id, # one per user message
"X-Span-Id": "plan", # one per call
"X-Synthorai-Metadata": '{"feature":"support-agent","_user":"u_1002"}',
},
)
Se l’agente invia già gli ID di trace tramite un altro strumento, potrebbe non servire aggiungere nulla. Per ciascuno dei tre ID assenti dalla richiesta, il gateway legge l’header equivalente di OpenTelemetry, LiteLLM, Langfuse, Open WebUI, Claude Code o OpenAI Codex CLI. OpenTelemetry è lo standard aperto per il tracing; il suo header traceparent contiene un ID di trace. La guida Tracciare un agente LLM con 3 header HTTP costruisce l’agente dimostrativo passo per passo: una chiamata di pianificazione su GPT-6 Luna, due strumenti locali, tre controlli paralleli su DeepSeek V4.1 Flash e una chiamata per la risposta. L’esecuzione dimostrativa ha effettuato 21 chiamate ai modelli per $0.0017.
Quale vista risponde a quale domanda?
Ogni domanda della serie trova risposta in un punto della console o in un modo di leggere i log dall’esterno. La console mostra ogni trace come un diagramma a cascata: una barra per chiamata al modello, su un asse temporale comune. Le chiamate eseguite in parallelo si sovrappongono; quelle che si aspettano a vicenda formano una scala.
| Domanda | Dove guardare | Guida |
|---|---|---|
| Perché questa esecuzione ha impiegato 8 secondi? | riepilogo della trace: tempo nel gateway rispetto al tempo nell’app | Latenza degli agenti |
| Quale turno della conversazione è costato di più? | pagina della sessione, con una riga per trace | Costo degli agenti |
| Perché il tasso di cache hit è allo 0%? | colonna dei cache hit nella pagina della sessione | Costo degli agenti |
| Che cosa è successo ieri alla richiesta di questo utente? | ricerca per etichetta nell’elenco delle trace | Debug degli agenti |
| Quale sottoagente di Claude Code ha generato la spesa? | tabella degli agenti nella pagina della sessione | Costi di Claude Code per sottoagente |
| Posso fare queste domande in chat? | un connettore MCP in Claude | Interrogare Claude sui log LLM |
| Il mio programma può analizzare i log? | strumenti MCP o API REST | Dare all’agente strumenti per i log |
Ogni pagina di trace ha anche un pulsante Compare. Mostra due trace nella stessa pagina, abbinando le chiamate per nome e usando un unico asse temporale per entrambi i diagrammi. Abbiamo verificato così ogni correzione della serie, confrontando l’esecuzione precedente con quella successiva.
Perché un agente LLM è lento?
Il primo dato da guardare è la ripartizione in cima alla trace. Divide il tempo totale percepito dall’utente, dalla prima richiesta all’ultima risposta, in tempo nel gateway e tempo nell’app. Il primo comprende gli intervalli con almeno una richiesta al modello aperta; il secondo quelli in cui era in esecuzione solo il nostro codice. Nella trace dimostrativa, 8.5 s totali si dividevano in 7.0 s nel gateway e 1.6 s nell’app: bisognava quindi indagare sulle chiamate ai modelli. La guida sulla latenza ha riprodotto tre cause, eseguendo ciascun caso cinque volte:
| Causa | Che cosa mostra la trace | Correzione | Mediana prima | Mediana dopo |
|---|---|---|---|---|
| Chiamate indipendenti inviate una alla volta | barre disposte a scala | inviarle insieme con asyncio.gather, che in Python permette di attendere più chiamate contemporaneamente | 3.7 s | 1.8 s |
| Codice lento tra le chiamate | una lunga riga your app | eseguire insieme le quattro ricerche | 11.0 s | 9.2 s |
| Ragionamento per risposte di una riga | un conteggio dei token di output molto superiore alla risposta | reasoning_effort="none" | 25.6 s per cinque risposte | 10.2 s |
Nel caso del codice lento, le chiamate ai modelli hanno richiesto circa lo stesso tempo nelle due esecuzioni. A cambiare è stato il tempo nell’app: nella coppia di esecuzioni mostrata nella guida, la riga your app è passata da 4.6 s a 1.3 s. Cambiare modello non può ridurre quella parte; l’indagine deve partire dal profiler dell’app o dagli span OpenTelemetry. Il terzo caso è stato eseguito su GPT-6 Luna. reasoning_effort è il parametro della richiesta che regola quanto ragiona il modello prima di rispondere. Impostandolo a none, i token di output delle cinque risposte sono scesi da 1,150 a 190. Perché il mio agente LLM è lento? contiene il codice e le trace affiancate di tutti e tre i casi.
Perché un agente LLM costa troppo?
Si parte dall’elenco delle trace ordinato per costo, poi si apre la sessione più costosa e si leggono le colonne dei cache hit e dell’input, turno per turno. Il tasso di cache hit è la quota di token di input letti dalla prompt cache del provider. Un prefisso del prompt già presente in cache viene fatturato a una frazione del prezzo di input. La guida sui costi ha riprodotto quattro cause. Gli importi sono frazioni di centesimo perché la demo è piccola: sono i rapporti a essere utili anche su altre scale.
| Causa | Dove si vede | Prima | Dopo |
|---|---|---|---|
| Un timestamp nella prima riga del prompt | i cache hit restano allo 0% | 0% dalla cache | 73% dalla cache, costo di input ridotto del 66% |
| Cronologia che reinvia ogni risultato degli strumenti | input in crescita a ogni turno | 24,546 token di input | 8,053 token di input |
| Un ciclo di tentativi ripetuti | una trace con troppe chiamate, tutte con stato 200 | 10 chiamate | 2 chiamate |
| Un modello costoso per un passaggio semplice | tabella degli agenti nella pagina della sessione | $0.0012 | $0.0003 |
Il primo caso è stato eseguito su GPT-6 Luna, partendo da una cache vuota. La guida mostra anche un secondo modo di perdere i cache hit: aggiungere un’istruzione a ogni messaggio dell’utente al momento dell’invio, ma non salvarla nella cronologia. Così ogni turno ricostruisce un prompt diverso da quello inviato al turno precedente. Salvare il messaggio così come era stato inviato ha portato il tasso di cache hit della conversazione dallo 0% al 76%. Nel caso della cronologia, triplicare i token di input ha aumentato il costo di input solo del 21%, perché il 71% proveniva dalla cache. Nell’ultimo caso, tre controlli di sottoagenti su GPT-6.1 Sol rappresentavano l’81% del costo della sessione. Gli stessi controlli su DeepSeek V4.1 Flash costavano un decimo. Perché il mio agente LLM costa troppo? analizza ogni caso.
Come si fa il debug di un agente LLM partendo dalla segnalazione di un utente?
Si trovano le esecuzioni dell’utente tramite un’etichetta, poi si legge la trace per individuare la chiamata andata male, che spesso ha restituito uno stato 200. Con un’etichetta _user su ogni richiesta, la ricerca metadata._user = u_1002 nell’elenco delle trace restituisce le sessioni e le trace di quell’utente. La guida al debug ha poi riprodotto tre errori:
- Un 404 seguito da un nuovo tentativo. Una chiamata a un ID di modello inesistente è fallita in 95 ms, al costo di $0. Il nuovo tentativo ha mantenuto l’ID di trace ma usato un nuovo nome di span (
policy-check.retry): nel diagramma, l’errore e il tentativo successivo compaiono uno accanto all’altro. - Risposte troncate con stato 200. Con
max_tokens=40e il ragionamento attivo, tre chiamate consecutive a DeepSeek V4.1 Flash hanno restituito stato 200, esattamente 40 token di output e una risposta vuota: il ragionamento aveva consumato tutto il limite. La trace mostra lo stesso span tre volte, con il conteggio di output uguale al limite. Alzando il limite a 2,000 e disattivando il ragionamento, è bastata una chiamata con una risposta di 49 token. Anche aumentare solo il limite ha funzionato, ma quella chiamata ha usato 1,876 token per ragionare e ha richiesto 22 s. - Un 400 al secondo turno. Una cronologia salvata da cui mancava una chiamata a uno strumento ha fatto rifiutare il secondo turno a GPT-6 Luna in 0.5 s, senza addebiti. Il primo turno aveva funzionato.
Il secondo errore spiega perché ogni chiamata dovrebbe registrare finish_reason, il campo della risposta che indica perché il modello si è fermato. Il valore length significa che la risposta ha raggiunto il limite max_tokens e va considerato un errore anche con stato 200. Fare il debug di un agente LLM elenca i cinque campi che ogni agente dovrebbe registrare, ripresi verso la fine di questo articolo.
Si può tracciare un agente di sviluppo senza modificarne il codice?
Claude Code si può tracciare senza modificarlo, perché invia già un ID di sessione e un ID di agente in ogni richiesta. Bastano due variabili d’ambiente per indirizzarlo al gateway; una terza aggiunge il tipo di ciascun sottoagente:
export ANTHROPIC_BASE_URL="https://synthorai.io" # no /v1: Claude Code adds /v1/messages itself
export ANTHROPIC_API_KEY="<your gateway key>"
export CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 # optional: names sub-agent types such as Explore
Un’esecuzione di Claude Code su Claude Sonnet 5.5, con due sottoagenti impegnati a correggere un test fallito, ha effettuato 11 richieste per $0.1728. La pagina della sessione ha ripartito il costo per agente: i due sottoagenti rappresentavano il 39% del totale, le scritture in cache il 77% e le prime chiamate dei tre agenti il 72%. La stessa esecuzione, ripetuta mezzo minuto dopo con la cache già popolata, è costata $0.0812. Costi di Claude Code per sottoagente mostra anche come ripartire la spesa di un team per sviluppatore e repository.
Un modello può leggere le trace al posto nostro?
Un modello può leggerle tramite MCP (Model Context Protocol, lo standard per fornire strumenti ai modelli), perché il gateway espone il log delle richieste attraverso un server MCP. Il modo di accedere ai log dipende da chi li interroga:
| Chi interroga i log | Cosa usare | Token del modello | Risultato misurato |
|---|---|---|---|
| Una persona con una domanda | Claude con il gateway aggiunto come connettore | inclusi nel piano della chat | 3 domande in 14 chiamate agli strumenti, tutti i dati coincidevano con le registrazioni di fatturazione |
| Un programma con una query predefinita | API REST | nessuno | le 3 sessioni più costose per $0 |
| Un proprio agente che decide cosa esaminare | strumenti MCP nel programma | a pagamento | un’indagine sui costi per $0.0015 |
Nel caso della chat, Claude Fable 5.1 si è collegato tramite OAuth, un accesso dal browser che evita di incollare una chiave API, e ha ricevuto solo l’accesso in lettura al log delle richieste. Aveva a disposizione quattro strumenti di sola lettura e una chiave con scadenza di 7 giorni. Gli strumenti restituiscono costi, token e stato, ma non il testo di prompt e risposte. Nel caso del programma, GPT-6 Luna ha individuato tra 26 sessioni quelle con problemi di cache, modello sbagliato e tentativi ripetuti, con una sola chiamata agli strumenti per $0.0015. Claude Sonnet 5.5 ha trovato le stesse sessioni con tre chiamate agli strumenti per $0.0631, dopo aver consultato i prezzi. Interrogare Claude sui log LLM e Dare all’agente strumenti per i log descrivono le due configurazioni.
Che cosa dovrebbe registrare ogni agente?
Cinque campi coprono tutti i problemi della serie:
- Un ID di trace per esecuzione e un ID di sessione per conversazione, inviati negli header e scritti anche nei log dell’applicazione.
- Un nome di span per chiamata che identifichi il passaggio e il tentativo, come
plan-2opolicy-check.retry. - Etichette per utente, funzionalità e release, per risalire dalle segnalazioni o dai costi alle esecuzioni che li hanno generati.
- L’header di risposta
x-request-iddi ogni chiamata, per recuperarne in seguito il costo fatturato. finish_reasone il conteggio dei token di output di ogni chiamata, trattandolengthcome un errore.
Impostate anche un timeout del client e un valore max_tokens per ogni chiamata, entrambi sufficientemente ampi. Un limite dimensionato sulla sola risposta visibile ha causato le risposte 200 vuote descritte sopra, e un modello di ragionamento può impiegare minuti per una singola chiamata lunga.
Che cosa non copre il tracing dal gateway?
Il tracing basato sul log delle richieste copre le chiamate ai modelli, non ciò che avviene nel codice dell’applicazione. Il gateway deduce il tempo di esecuzione del codice dagli intervalli tra le richieste, come nel caso del codice lento. Per gli span di strumenti, recupero dei dati e code serve un SDK OpenTelemetry. Le due viste possono condividere l’ID di trace, perché il gateway legge traceparent quando manca X-Trace-Id. Le trace non contengono neppure i corpi di prompt e risposte e riportano i costi a posteriori: per monitorare un budget in tempo reale, il ciclo dell’agente deve calcolare il costo di usage a ogni risposta ricevuta.
Domande frequenti
Che cos’è l’osservabilità degli agenti LLM?
L’osservabilità degli agenti LLM permette di vedere che cosa ha fatto un agente in ogni esecuzione: quali chiamate ai modelli ha effettuato, in quale ordine, quanto è durata e costata ciascuna e quali sono fallite. In pratica, significa raggruppare le chiamate in trace (una singola esecuzione) e sessioni (una conversazione), con etichette per utente e funzionalità.
Letture correlate
- Tracciare un agente LLM con 3 header HTTP: l’agente dimostrativo e gli header, passo per passo.
- Riferimento degli header di richiesta: tutti gli header di questo articolo, con gli alias letti da altri strumenti.
- Trace e sessioni nella console: l’elenco, il diagramma a cascata e la pagina della sessione.
- Anatomia del consumo di token LLM: il significato dei conteggi dei token in ciascun record, per provider.
- Guida completa alla cache dei prompt: perché un prefisso del prompt viene trovato o meno in cache.