Jungletech
/
Menu
Chiudi
Inizia un progetto
← Torna al blog

Engineering · 6 min

Observability per Agenti AI in Produzione: Tecniche di Tracing e Debugging oltre gli APM Tradizionali

Gli APM tradizionali non bastano per i sistemi autonomi. Scopri come implementare l'observability per agenti AI, dal LLM tracing al debugging in produzione.

di Redazione di Jungletech··
mlopsllm-agentsobservabilitytool-useAI AgentsMLOpsLLM ObservabilityDistributed Tracing
Observability per Agenti AI in Produzione: Tecniche di Tracing e Debugging oltre gli APM Tradizionali

Gli strumenti APM tradizionali misurano latenza, CPU e memoria, ma non ti dicono perché un agente AI ha fallito silenziosamente o entrato in un loop infinito. Un agente autonomo non è codice deterministico: può allucinare, raggiungere token limit, ricevere errori da API esterne e riprovare con la stessa sintassi sbagliata. Il debugging di agenti AI in produzione richiede visibilità sul chain of thought, non solo sulle metriche di sistema. Questo articolo descrive come implementare l'observability per sistemi agentico usando LLM tracing e OpenTelemetry, andando oltre i limiti degli APM convenzionali.

Perché gli APM tradizionali falliscono nel debugging di AI agents in produzione?

Come evidenziato da Stack Overflow nel rapporto "Code isn't the only thing causing your production failures", i problemi in produzione vanno oltre il codice e le metriche standard. Per gli agenti AI autonomi, questa realtà è ancora più evidente.

Un APM classico ti dice: "La richiesta ha impiegato 2.3 secondi e ha consumato 512 MB di memoria". Ma non ti dice:

  • Se l'agente ha allucinato nella risposta finale
  • Se ha raggiunto il token limit midstream, interrompendo il ragionamento
  • Se una chiamata a uno strumento ha fallito e l'agente ha riprovato con lo stesso payload
  • Quanti step ha eseguito prima di rinunciare
  • Se il contesto RAG recuperato era effettivamente rilevante

La natura non deterministica degli LLM introduce una nuova categoria di errori silenziosi: risposte sintatticamente corrette ma semanticamente sbagliate, cicli di retry dovuti a errori intermittenti nella toolchain, degradazione della qualità a causa di contesto mal formattato.

Gli APM tradizionali mancano di:

  • Visibilità sul reasoning step-by-step dell'agente
  • Tracciamento dei token consumati per call (critical per il costo e per scoprire infinite loop)
  • Monitoraggio dei fallimenti di function calling (quando uno strumento restituisce un formato inaspettato)
  • Correlation tra errori del backend e degradazione della qualità dell'agente

Serve un approccio diverso: LLM observability.

Come implementare il LLM Tracing per mappare il Chain of Thought dell'agente

Il LLM tracing non è semplice logging. È il tracciamento distribuito applicato al ragionamento di un agente: ogni step del chain of thought (sia che usi ReAct, Plan-and-Execute o altre architetture) diventa uno span in una gerarchia di tracce.

Rappresentazione astratta di uno span tree per LLM tracing

Rappresentazione astratta di uno span tree per LLM tracing

Anziché scrivere print() o log strutturati isolati, ogni operazione atomica diventa un nodo tracciato: la richiesta iniziale è lo span root, ogni invocazione di LLM è uno span child, ogni chiamata a uno strumento (retrieval, database query, API esterna) è uno span nested. Questo albero di span ti mostra esattamente dove è avvenuto il fallimento e in quale ordine.

Esempio di struttura pratica di un agente RAG con funzioni:

Span: agent_execution (id=abc123)
├─ Span: user_query_parsing
├─ Span: rag_retrieval
│  ├─ Span: embedding_generation
│  └─ Span: vector_db_search
├─ Span: first_llm_call
│  ├─ attribute: model = "gpt-4"
│  ├─ attribute: prompt_tokens = 1240
│  └─ attribute: completion_tokens = 315
├─ Span: tool_call_1
│  ├─ attribute: tool_name = "fetch_user_data"
│  ├─ attribute: args = {...}
│  └─ attribute: error = "API timeout after 3s"
├─ Span: fallback_llm_call (semantic routing)
└─ Span: response_validation

Ogni span porta con sé attributi custom: il modello usato, i token consumati, i parametri dello strumento, il payload della risposta. Quando un agente entra in loop, vedi immediatamente che lo stesso span tool_call_1 si ripete N volte con lo stesso errore.

La pratica ingegneristica corretta:

  1. Strumenta il punto di ingresso dell'agente (la richiesta utente)
  2. Cattura ogni decisione che l'agente prende (quale tool usare, quale modello, quale fallback)
  3. Traccia le latenze intermedie senza appesantire: usa sampling rate adeguato (es. 100% in staging, 10-20% in produzione per high-volume)
  4. Arricchisci i contesti di retrieval con metadata: quale chunk è stato usato, da quale source, con quale score di relevance

Strumenti come Langsmith (per LangChain/LangGraph) e Arize fanno questo nativamente, ma il golden standard è l'OpenTelemetry integration che vedremo nella sezione successiva.

OpenTelemetry per LLM: architettura pratica per l'MLOps degli agenti autonomi

OpenTelemetry (OTEL) è uno standard aperto per telemetria che non dipende dal vendor. Anziché essere legati a Datadog o New Relic, instrumenti il tuo codice una volta e esporti i dati dove vuoi.

Per un agente AI, l'architettura OTEL standard segue questo flow:

Application Code
    ↓
OpenTelemetry SDK (Python/Node/etc.)
    ↓
Instrumentation (auto + manual spans)
    ↓
OTLP Exporter (gRPC o HTTP)
    ↓
Collector (data processing, batching)
    ↓
Backend (Datadog, Grafana Loki, Jaeger, etc.)

Concretamente, per instrumentare function calling su un agente:

from opentelemetry import trace, attributes
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Setup
otlp_exporter = OTLPSpanExporter(endpoint="localhost:4317")
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(otlp_exporter)
)
tracer = trace.get_tracer(__name__)

# Nel tuo agente
def execute_agent_step(query: str, tools: List[Tool]):
    with tracer.start_as_current_span("agent_step") as span:
        span.set_attribute("query", query)
        
        # LLM call per decidere quale tool usare
        with tracer.start_as_current_span("llm_decision") as llm_span:
            response = llm_client.chat(...)
            llm_span.set_attribute("model", "gpt-4")
            llm_span.set_attribute("prompt_tokens", response.usage.prompt_tokens)
            llm_span.set_attribute("completion_tokens", response.usage.completion_tokens)
        
        # Function call
        tool_name = response.tool_calls[0].name
        with tracer.start_as_current_span("tool_execution") as tool_span:
            tool_span.set_attribute("tool_name", tool_name)
            tool_span.set_attribute("args", str(response.tool_calls[0].args))
            try:
                result = tools[tool_name].execute(...)
                tool_span.set_attribute("status", "success")
            except Exception as e:
                tool_span.set_attribute("status", "error")
                tool_span.set_attribute("error_message", str(e))
                raise

L'integrazione con il backend aziendale avviene al livello del Collector: puoi aggiungere processors che arricchiscono gli span con metadati aziendali (customer ID, environment, deployment version) prima di inviarli a destinazione.

Per la diagnostica degli errori quando l'agente interagisce con sistemi legacy: i dati di telemetria OTEL correlano automaticamente la traccia dell'agente con i log del sistema legacy (se anche quello espone OTEL o se usi correlation IDs negli header HTTP). Se un agente fallisce perché una API legacy ritorna 500, vedrai nella stessa traccia sia lo span del tool che il log di errore del backend.

Quali sono le cause degli infinite loop negli agenti AI e come prevenirli?

L'infinite loop è il nemico silenzioso della produzione agentica. Non è un crash — il codice gira, consuma token e crediti, e produce risposte progressivamente peggiori finché non hits il timeout.

Cause comuni:

  1. Tool failure loop: l'agente riceve un errore da uno strumento (es. "JSON parsing error") e ripete la stessa chiamata con la medesima sintassi sbagliata. L'LLM non sa che ha fallito perché il messaggio di errore non è abbastanza chiaro.
  2. Missing termination condition: nessun vincolo sul numero massimo di step o di token complessivi.
  3. Semantic confusion: l'agente pensa di aver raggiunto l'obiettivo ma in realtà ha solo fatto un passo intermedio; continua a richiamare strumenti senza logica.

Soluzioni concrete:

Hard limit di esecuzione: imposta sempre un massimo di iterazioni e un timeout globale.

MAX_ITERATIONS = 15
TIMEOUT_SECONDS = 30

async def run_agent_with_limits(query: str):
    start_time = time.time()
    iteration = 0
    
    while iteration < MAX_ITERATIONS:
        if time.time() - start_time > TIMEOUT_SECONDS:
            return {"status": "timeout", "last_response": current_response}
        
        response = await agent_step(query)
        iteration += 1
        
        # Check if agent concluded
        if response.get("action") == "finish":
            return response

Semantic routing per fallback sicuro: non far riprovare l'agente con lo stesso tool. Usa il chain-of-thought prompting per far ragionare l'agente sul fallimento.

if tool_execution_failed:
    # Anziché: "Riprova con lo strumento"
    # Usa: "Lo strumento ha restituito questo errore. Quale approccio alternativo usi?"
    
    fallback_response = llm_client.chat(
        messages=[
            {"role": "user", "content": f"Tool failed with: {error_message}. What's your next step?"}
        ]
    )

Monitoraggio real-time dei token e dei tempi di esecuzione per alert preventivi: ogni span di tool execution ha una latenza. Se vedi che uno strumento impiega 5+ secondi ripetutamente, è probabile un fallimento ricorrente. Configura alert su:

  • Numero di iterazioni dell'agente > soglia (es. > 10 step)
  • Tempo cumulativo di esecuzione > soglia (es. > 25s)
  • Token consumati > soglia (es. > 15k token)

Con LLM tracing e OTEL, questi alert sono automatici: il collector sa contare gli step perché vede ogni span di iterazione.

Affrontare la complessità in produzione: il ruolo strategico della LLM observability

Il passaggio dal notebook al codice in produzione è dove la maggior parte dei progetti AI fallisce. In un notebook, sai cosa succede a ogni riga. In produzione, con concorrenza, errori di rete, modelli drift e carico variabile, perdi quella visibilità.

La LLM observability non è opzionale: è il fondamento della stabilità. Serve per tre motivi:

  1. Debugging veloce: quando un agente inizia a produrre risposte sbagliate, le tracce ti mostrano esattamente dove è cambiato il comportamento (change in RAG relevance? Degradazione della qualità del modello? Nuova categoria di query?).
  2. Cost tracking preciso: ogni token costanza. Con OTEL, aggreghi i token per customer, per query type, per modello. Scopri che il 5% delle query consuma il 40% dei token — puoi ottimizzare.
  3. Compliance e auditability: in ambienti regulated, serve dimostrare come un agente ha preso una decisione. La traccia completa è la tua prova.

Implementare tutto questo richiede competenze di engineering avanzato: non solo machine learning, ma anche distributed systems, telemetry infrastructure, database design. È il motivo per cui molti progetti AI falliscono — assembli componenti sofisticati (LLM, RAG, tools) senza il fondamento osservabile che li tiene insieme.

Se state affrontando questa complessità con agenti autonomi, possiamo confrontarci sulla vostra architettura e aiutarvi a costruire l'observability giusta dai primi step. L'integrazione con sistemi legacy richiede attenzione particolare — è qui che la telemetria distribuita fa la differenza.


FAQ

Quali sono le differenze tra prompt monitoring e LLM observability?

Il prompt monitoring misura input e output testuali per ogni singola chiamata LLM: costi, latenza, token count. È una visione point-in-time. La LLM observability è olistica: traccia l'intero ciclo di vita della richiesta attraverso span correlati, including tool calls, step intermedi del ragionamento, metriche di sistema, e connessioni logiche tra operazioni. Per un agente, è la differenza tra sapere che una richiesta ha impiegato 2.5s e sapere che ha impiegato 0.5s in LLM call + 1.8s in retrieval + 0.2s in validation.

Come posso tracciare il costo esatto per ogni esecuzione di un agente AI?

Cattura i metadati di utilizzo (prompt_tokens, completion_tokens) come attributi dello span per ogni LLM call. Applica il prezzo per token in base al modello usato. Aggrega a livello di execution ID (il root span id) per ottenere il costo totale. Con OpenTelemetry, crei una query nel backend (es. Datadog, Grafana) che somma i costi per customer ID o per execution type. Esempio: SUM(prompt_tokens * 0.005 + completion_tokens * 0.015) WHERE agent_id = 'X'.

È possibile usare Datadog o Grafana per il monitoraggio degli LLM?

Sì. Esporti le tracce via OTLP (OpenTelemetry Line Protocol): il tuo collector invia gli span a Datadog o Grafana Loki tramite grpc o http. La best practice è instrumentare il codice in modo vendor-agnostic (usando OpenTelemetry SDK), poi scegliere il backend in base alle tue esigenze. Aggiungi attributi custom agli span: temperatura del modello, schema di retrieval, tool payload — questi diventano filtri e dimensioni nelle tue dashboard.