Salta al contenuto principale

11 - AI Observability - Monitoring LLM Calls and Agent Behavior

LLM tracing (token usage, latency, errors), agent tracing (decisions, tool calls), cost tracking, hallucination detection signals, monitoring frameworks.

Cosa ne pensi di questo articolo?

11 - AI Observability - Monitoring LLM Calls and Agent Behavior
ObservabilityFoto:Lukas BlazekPexels

In Breve

LLM tracing (token usage, latency, errors), agent tracing (decisions, tool calls), cost tracking, hallucination detection signals, monitoring frameworks.

AI Observability: Monitorare LLM, Token e Agenti

Con l'adozione massiva dei Large Language Model (LLM) in produzione, emerge un nuovo dominio dell'observability: il monitoraggio delle applicazioni basate su AI. Le chiamate agli LLM hanno caratteristiche uniche rispetto alle API tradizionali: costi variabili basati sull'utilizzo dei token, latenze imprevedibili, output non deterministici e rischi di hallucination. L'AI observability applica i principi dell'observability classica a questo nuovo paradigma.

In questo articolo esploreremo come instrumentare le chiamate ai modelli linguistici, tracciare il comportamento degli agenti AI, monitorare i costi in tempo reale e rilevare anomalie nelle risposte dei modelli usando OpenTelemetry e le semantic conventions emergenti per l'AI.

Cosa Imparerai in Questo Articolo

  • perchè le applicazioni AI richiedono observability specializzata
  • Tracciare le chiamate LLM con span e attributi specifici
  • Monitorare l'utilizzo dei token e i costi in tempo reale
  • Osservare il comportamento degli agenti AI (tool calls, reasoning)
  • Rilevare hallucination e degradazioni della qualità
  • Framework e strumenti per l'AI observability

perchè le Applicazioni AI Richiedono Observability Specializzata

Le applicazioni basate su LLM hanno caratteristiche che le rendono fondamentalmente diverse dalle applicazioni tradizionali dal punto di vista dell'observability:

Sfide Uniche dell'AI Observability

Sfida Applicazione Tradizionale Applicazione AI/LLM
Costi Fissi (compute, storage) Variabili (per token, per richiesta)
Latenza Prevedibile (ms range) Alta e variabile (1-60s per streaming)
Output Deterministico Non deterministico (temperature, sampling)
Errori Codici di stato chiari Hallucination, risposte incoerenti
Testing Unit test deterministici Valutazione qualitativa (eval)

Instrumentare le Chiamate LLM

L'instrumentazione delle chiamate ai modelli linguistici segue lo stesso pattern di OTel per le API esterne, ma con attributi specifici per catturare i metadata dell'AI: modello utilizzato, conteggio token, temperatura, costo stimato.


from opentelemetry import trace, metrics
import time
import tiktoken

tracer = trace.get_tracer("ai-service", "1.0.0")
meter = metrics.get_meter("ai-service", "1.0.0")

# Metriche specifiche per LLM
llm_token_usage = meter.create_counter(
    name="llm.token.usage",
    description="Token utilizzati nelle chiamate LLM",
    unit="token"
)

llm_request_duration = meter.create_histogram(
    name="llm.request.duration",
    description="Durata delle chiamate LLM",
    unit="ms"
)

llm_cost = meter.create_counter(
    name="llm.cost.total",
    description="Costo stimato delle chiamate LLM",
    unit="USD"
)

def call_llm(messages, model="gpt-4", temperature=0.7, max_tokens=1000):
    with tracer.start_as_current_span(
        "llm.chat.completion",
        attributes={
            # Semantic conventions per AI (draft)
            "gen_ai.system": "openai",
            "gen_ai.request.model": model,
            "gen_ai.request.temperature": temperature,
            "gen_ai.request.max_tokens": max_tokens,
            "gen_ai.request.top_p": 1.0,
            # Contesto applicativo
            "llm.prompt.messages_count": len(messages),
            "llm.prompt.system_prompt_length": len(messages[0]["content"])
                if messages[0]["role"] == "system" else 0,
        }
    ) as span:
        start_time = time.monotonic()

        try:
            # Conteggio token input (pre-chiamata)
            encoding = tiktoken.encoding_for_model(model)
            input_tokens = sum(
                len(encoding.encode(m["content"])) for m in messages
            )
            span.set_attribute("gen_ai.usage.prompt_tokens", input_tokens)

            # Chiamata al modello
            response = openai_client.chat.completions.create(
                model=model,
                messages=messages,
                temperature=temperature,
                max_tokens=max_tokens
            )

            # Attributi dalla risposta
            output_tokens = response.usage.completion_tokens
            total_tokens = response.usage.total_tokens

            span.set_attribute("gen_ai.usage.completion_tokens", output_tokens)
            span.set_attribute("gen_ai.usage.total_tokens", total_tokens)
            span.set_attribute("gen_ai.response.model", response.model)
            span.set_attribute("gen_ai.response.finish_reason",
                response.choices[0].finish_reason)

            # Calcolo costo stimato
            cost = estimate_cost(model, input_tokens, output_tokens)
            span.set_attribute("llm.cost.estimated_usd", cost)

            # Registrare metriche
            duration_ms = (time.monotonic() - start_time) * 1000
            common_attrs = {
                "gen_ai.system": "openai",
                "gen_ai.request.model": model
            }

            llm_token_usage.add(input_tokens,
                {**common_attrs, "gen_ai.token.type": "input"})
            llm_token_usage.add(output_tokens,
                {**common_attrs, "gen_ai.token.type": "output"})
            llm_request_duration.record(duration_ms, common_attrs)
            llm_cost.add(cost, common_attrs)

            span.set_status(StatusCode.OK)
            return response

        except RateLimitError as e:
            span.record_exception(e)
            span.set_status(StatusCode.ERROR, "Rate limit exceeded")
            span.set_attribute("llm.error.type", "rate_limit")
            raise

        except Exception as e:
            span.record_exception(e)
            span.set_status(StatusCode.ERROR, str(e))
            raise
      

Tracciare Agenti AI

Gli agenti AI sono sistemi che usano LLM per ragionare, pianificare e invocare strumenti (tool calls) per completare task complessi. Il loro comportamento e particolarmente difficile da osservare perchè coinvolge cicli multipli di reasoning, decisioni di tool selection e composizione di risultati.


def trace_agent_execution(task, agent):
    with tracer.start_as_current_span(
        "agent.execute",
        attributes={
            "agent.name": agent.name,
            "agent.model": agent.model,
            "agent.task": task.description,
            "agent.max_iterations": agent.max_iterations,
            "agent.tools_available": ",".join(agent.tool_names)
        }
    ) as agent_span:
        iteration = 0
        total_tokens = 0
        total_cost = 0.0
        tools_called = []

        while not agent.is_done() and iteration < agent.max_iterations:
            iteration += 1

            # Span per ogni iterazione del reasoning loop
            with tracer.start_as_current_span(
                f"agent.iteration.{iteration}",
                attributes={
                    "agent.iteration": iteration,
                    "agent.state": agent.current_state
                }
            ) as iter_span:

                # Span per la chiamata LLM di reasoning
                with tracer.start_as_current_span("agent.reasoning") as reason_span:
                    decision = agent.reason(task)
                    reason_span.set_attribute("agent.decision.type",
                        decision.type)  # "tool_call" | "final_answer"
                    reason_span.set_attribute("agent.decision.confidence",
                        decision.confidence)
                    total_tokens += decision.tokens_used

                # Se l'agente decide di chiamare un tool
                if decision.type == "tool_call":
                    with tracer.start_as_current_span(
                        "agent.tool_call",
                        attributes={
                            "agent.tool.name": decision.tool_name,
                            "agent.tool.input_summary": decision.tool_input[:200]
                        }
                    ) as tool_span:
                        result = agent.execute_tool(decision)
                        tool_span.set_attribute("agent.tool.success",
                            result.success)
                        tool_span.set_attribute("agent.tool.output_length",
                            len(str(result.output)))
                        tools_called.append(decision.tool_name)

        # Attributi finali dell'agente
        agent_span.set_attribute("agent.iterations_total", iteration)
        agent_span.set_attribute("agent.tokens_total", total_tokens)
        agent_span.set_attribute("agent.cost_total_usd", total_cost)
        agent_span.set_attribute("agent.tools_called",
            ",".join(tools_called))
        agent_span.set_attribute("agent.success", agent.is_done())
      

Monitoraggio dei Costi in Tempo Reale

Il costo delle chiamate LLM può scalare rapidamente, specialmente con modelli come GPT-4 o Claude. Il monitoraggio in tempo reale dei costi permette di rilevare anomalie (un loop infinito che consuma token), impostare budget alert e ottimizzare l'uso dei modelli.


# Alert rules Prometheus per AI cost monitoring
groups:
  - name: ai-cost-alerts
    rules:
      # Alert: spesa oraria sopra il budget
      - alert: HighAICostPerHour
        expr: |
          sum(rate(llm_cost_total[1h])) * 3600 > 50
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "AI cost exceeding $50/hour"

      # Alert: tasso di errori LLM alto
      - alert: HighLLMErrorRate
        expr: |
          rate(llm_request_duration_count{status="error"}[5m])
          / rate(llm_request_duration_count[5m]) > 0.1
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "LLM error rate above 10%"

      # Alert: latenza LLM degradata
      - alert: HighLLMLatency
        expr: |
          histogram_quantile(0.95,
            rate(llm_request_duration_bucket[5m])
          ) > 30000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "LLM P95 latency above 30 seconds"

      # Alert: anomalia token usage
      - alert: AnomalousTokenUsage
        expr: |
          rate(llm_token_usage[5m]) > 3 * avg_over_time(rate(llm_token_usage[1h])[24h:1h])
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Token usage 3x above 24h average"
      

Metriche Chiave per l'AI Observability

  • Token usage: input/output tokens per modello, per endpoint, per utente
  • Costo per richiesta: stima del costo in base al modello e ai token usati
  • Latenza TTFT: Time To First Token, critica per applicazioni streaming
  • Tasso di errori: rate limit, timeout, errori del modello
  • Finish reason: distribuzione di stop, max_tokens, tool_call, content_filter
  • Agent iterations: numero di cicli di reasoning per task completato
  • Tool call success rate: percentuale di tool call riuscite vs fallite

Rilevamento di Hallucination e qualità

Il monitoraggio della qualità delle risposte e un aspetto unico dell'AI observability. Mentre gli errori tecnici (timeout, rate limit) sono facili da rilevare, le hallucination e le risposte di bassa qualità richiedono metriche proxy e valutazione automatica.

Segnali Proxy per la qualità delle Risposte

Lunghezza della risposta: risposte troppo brevi o troppo lunghe rispetto alla media possono indicare problemi di qualità.

Finish reason anomalo: un alto tasso di max_tokens indica risposte troncate; un alto tasso di content_filter indica contenuti bloccati.

User feedback: tracciare le azioni dell'utente dopo la risposta (retry, thumbs down, abbandono) come segnali indiretti di qualità.

Similarity score: confrontare la risposta con risposte di riferimento usando embeddings per rilevare drift nella qualità.

Framework per l'AI Observability

Diversi framework stanno emergendo per semplificare l'AI observability, offrendo instrumentazione automatica per le principali librerie AI e dashboard preconfigurate. Tra i più rilevanti: OpenLLMetry (basato su OTel per LLM), LangSmith (per LangChain), Helicone (proxy per OpenAI) e le OTel Semantic Conventions per GenAI (standard emergente).

Conclusioni e Prossimi Passi

L'AI observability e un campo in rapida evoluzione che estende i principi dell'observability classica alle applicazioni basate su LLM. Le sfide uniche (costi variabili, output non deterministico, hallucination) richiedono metriche e pattern di monitoring specializzati.

I tre pilastri dell'AI observability sono: cost monitoring (token usage, costo per richiesta), performance monitoring (latenza, TTFT, tasso di errori) e quality monitoring (finish reason, user feedback, similarity scores).

Nel prossimo e ultimo articolo della serie, presenteremo un case study completo: l'implementazione end-to-end di un sistema di observability per un'architettura a microservizi, con metriche prima e dopo l'adozione di OpenTelemetry.

Domande frequenti

Perchè le applicazioni AI richiedono observability specializzata?

Le applicazioni basate su LLM hanno caratteristiche uniche che le rendono fondamentalmente diverse dalle applicazioni tradizionali dal punto di vista dell'observability.

Come si instrumentano le chiamate ai modelli linguistici?

L'instrumentazione delle chiamate ai modelli linguistici segue lo stesso pattern di OTel per le API esterne, ma con attributi specifici per catturare i metadata dell'AI.

Perchè è importante monitorare i costi in tempo reale?

Il costo delle chiamate LLM può scalare rapidamente e il monitoraggio in tempo reale dei costi permette di rilevare anomalie, impostare budget alert e ottimizzare l'uso dei modelli.

Quali sono i principali indicatori di qualità per le risposte dei modelli?

I principali indicatori di qualità per le risposte dei modelli includono lunghezza della risposta, finish reason anomalo, user feedback e similarity score.

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti