Pular para o conteúdo
iauaiiauai — portal de tecnologia, IA e Cloud
IAIntermediário

Observabilidade em aplicações com LLM

Mede tokens, custo, latência, recusas e alucinações. Monte um dashboard que responde 'por que subiu?'

Por Equipe iauai · 3 de julho de 2026 · 10 min de leitura

Nesta página

O problema real

Seu dashboard mostra que as chamadas de API estão normais. Mas a conta AWS subiu USD 2 mil em um mês. Você avalia o código, está igual. Verificas logs, estão iguais. Aí, três meses depois, descobre que um prompt começou a retornar 50 tokens a mais por requisição. Em 100 mil requisições, são milhões de tokens.

Aplicações com LLM precisam de observabilidade diferente de apps tradicionais. Você não quer saber só se a API respondeu; quer saber quantos tokens gastou, qual foi a latência do primeiro token (TTFT), qual é a taxa de erro semanticamente válido mas errado, etc. Este artigo mostra o que medir e como.

O que medir que é diferente

1. Tokens (input + output)

Cada token custa. Você precisa saber:

# A cada chamada de LLM:
tokens_input = response.usage.prompt_tokens
tokens_output = response.usage.completion_tokens
tokens_total = tokens_input + tokens_output

# Log estruturado
log_entry = {
    "timestamp": "2026-08-01T10:00:00Z",
    "request_id": "req_12345",
    "model": "gpt-4o-mini",
    "tokens_input": tokens_input,
    "tokens_output": tokens_output,
    "tokens_total": tokens_total,
    "cost": (tokens_input * 0.00015 + tokens_output * 0.0006) / 1000
}

Meta recomendada:

  • Rastreie tokens por modelo, por usuário, por feature
  • Compare input vs output (se output cresce, algo está errado)

2. Latência: TTFT (Time To First Token)

É a latência até o primeiro token chegar. Usuários percebem isso imediatamente.

import time

start = time.time()
response = client.chat.completions.create(...)
ttft = (time.time() - start) * 1000  # em ms

log_entry["ttft_ms"] = ttft
log_entry["latency_total_ms"] = (time.time() - start) * 1000

Meta recomendada:

  • Mediana < 300ms (aceitável)
  • p99 < 1.000ms (limite)
  • Se degradar de repente, algo está congestionado (fila, limite de rate)

3. Taxa de recusa (refusal rate)

O modelo recusa responder por questões de segurança/policy.

def check_if_refused(response_text: str) -> bool:
    """Detecta se modelo recusou."""
    refusal_phrases = [
        "cannot help",
        "inappropriate",
        "policy violation",
        "não posso"
    ]
    return any(phrase in response_text.lower() for phrase in refusal_phrases)

refused = check_if_refused(response.choices[0].message.content)
log_entry["refused"] = refused

Meta recomendada:

  • Monitore taxa de recusa por tipo de pergunta
  • Se sobe de repente, policy pode ter mudado
  • Se usuários reclamam, investigar

4. Taxa de alucinação (hallucination detection)

LLMs inventam coisas. Você pode detectar algumas offline, outras com LLM-as-judge.

Método 1: Detecção simples (quando há verdade ground)

def contains_hallucination(response: str, ground_truth: list[str]) -> bool:
    """Detecta se resposta contém fatos não contidos no contexto."""
    from datetime import datetime
    
    # Se resposta menciona uma data não no contexto
    # Se resposta cita uma fonte não fornecida
    # Se resposta menciona um número não no contexto
    
    # Exemplo simples: procura por "[source:" ou "[citação:"
    return "[source:" not in response and ("according to" in response.lower())

hallucinated = contains_hallucination(response_text, documents)
log_entry["hallucinated"] = hallucinated

Método 2: LLM-as-judge (mais preciso, mais caro)

def rate_hallucination_with_llm(response: str, context: str) -> float:
    """Usa outro LLM para avaliar se resposta alucina.
    
    Retorna: 0.0 (sem alucinação) a 1.0 (pura alucinação)
    """
    client = OpenAI()
    
    judge_prompt = f"""Leia o CONTEXTO e a RESPOSTA.
A resposta contém informações não mencionadas no contexto?

CONTEXTO:
{context}

RESPOSTA:
{response}

Responda com um número de 0 a 1:
0 = resposta está 100% baseada no contexto
1 = resposta alucina completamente

Apenas o número:"""
    
    judge_response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": judge_prompt}],
        temperature=0
    )
    
    try:
        score = float(judge_response.choices[0].message.content.strip())
        return min(max(score, 0.0), 1.0)
    except:
        return 0.5  # fallback

hallucination_score = rate_hallucination_with_llm(response_text, context)
log_entry["hallucination_score"] = hallucination_score

Cuidado: LLM-as-judge é caro (USD 0,05-0,10 por avaliação). Use apenas em amostra (5-10%).

5. Custo por requisição

cost_usd = (
    tokens_input * 0.00015 +  # OpenAI GPT-4o-mini input
    tokens_output * 0.0006    # GPT-4o-mini output
) / 1000

log_entry["cost_usd"] = cost_usd

# Agregue por dia
daily_cost = sum([entry["cost_usd"] for entry in logs if same_day])

Meta recomendada:

  • Custo por usuário / por feature
  • Monitore custo acumulado vs. orçamento
  • Se outlier (USD 10 por requisição), algo errado

6. Correlação: request_id

Tudo conectado:

import uuid

request_id = str(uuid.uuid4())[:12]

# Cada log tem esse ID
log_entry["request_id"] = request_id

# Você consegue fazer uma query: "mostre-me todos os eventos do request_id abc"
# E vê o trace completo

Instrumentando seu código

Wrapper para OpenAI

import json
from datetime import datetime
from openai import OpenAI

class ObservedOpenAI(OpenAI):
    """Wrapper que observa cada chamada."""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = self._get_logger()
    
    def _get_logger(self):
        """Retorna logger (implementar com seu serviço)."""
        # Você pode usar: CloudWatch, DataDog, Splunk, etc.
        class SimpleLogger:
            def log(self, entry):
                print(json.dumps(entry, default=str))
        return SimpleLogger()
    
    def chat_completions_create(self, *args, **kwargs):
        """Override que registra."""
        request_id = kwargs.pop("request_id", str(uuid.uuid4())[:12])
        
        import time
        start = time.time()
        
        response = super().chat.completions.create(*args, **kwargs)
        
        duration = time.time() - start
        
        log_entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "request_id": request_id,
            "model": response.model,
            "tokens_input": response.usage.prompt_tokens,
            "tokens_output": response.usage.completion_tokens,
            "tokens_total": response.usage.prompt_tokens + response.usage.completion_tokens,
            "duration_ms": duration * 1000,
            "cost_usd": (
                response.usage.prompt_tokens * 0.00015 +
                response.usage.completion_tokens * 0.0006
            ) / 1000
        }
        
        self.logger.log(log_entry)
        return response

# Uso
client = ObservedOpenAI()
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "olá"}],
    request_id="my_req_123"
)

Avaliação online vs offline

Online

Avalia enquanto o usuário usa:

  • Real-time
  • Baseada em comportamento real
  • Problema: pode ser lenta, custa tokens

Exemplo:

# Avalia em produção, 10% das requisições
if random.random() < 0.1:
    hallucination_score = rate_hallucination_with_llm(response, context)

Offline

Avalia em lote, depois:

  • Mais barato
  • Pode ser mais sofisticado
  • Problema: não é real-time

Exemplo:

# Uma vez por dia
python evaluate_responses.py --date 2026-08-01

LLM-as-judge: seus limites

LLM-as-judge é útil mas não perfeito.

O que funciona bem:

  • Avaliar relevância ("essa resposta responde a pergunta?")
  • Avaliar tom ("está professional?")
  • Avaliar factualidade contra contexto pequeno

O que NÃO funciona bem:

  • Verificar se fato é 100% preciso (requer fact-checking externo)
  • Detecção robusta de viés (requer dataset anotado)
  • Avaliações numérica calibradas (juiz muda opinião)

Solução: Combine com métrica manual.

def evaluate_batch_with_human_sample(responses: list, sample_size: int = 50):
    """Avalia com LLM, depois humano valida amostra."""
    
    # LLM avalia tudo
    scores = [rate_with_llm(r) for r in responses]
    
    # Humano valida 50 aleatorios
    human_scores = manual_evaluation(sample(responses, sample_size))
    
    # Calibra scores de LLM
    correlation = calculate_correlation(scores[:sample_size], human_scores)
    print(f"Correlação LLM vs Humano: {correlation:.2f}")
    
    return scores, correlation

Dashboard de observabilidade

Seu painel deve responder:

Pergunta Métrica Meta
Quanto estou gastando? Custo total diário (USD) < Orçamento
Que modelos custam mais? Custo por modelo Identificar outliers
Como é a latência? TTFT mediana / p99 Med < 300ms, p99 < 1s
Taxa de erro semanticamente válido? Hallucination score, recusal rate < 2%
Qual feature custa mais? Custo por endpoint Investigar se anormal
Está degradando? Tokens por requisição ao longo do tempo Alertar se subir 20%+
import pandas as pd

# Agregação simples
df = pd.DataFrame(logs)

# Custo por dia
daily = df.groupby(df['timestamp'].dt.date).agg({
    'cost_usd': 'sum',
    'tokens_total': 'sum',
    'request_id': 'count'
}).rename(columns={'request_id': 'num_requests'})

print(daily)

Armadilhas comuns

"Rastreio tudo, 100% das requisições"

Vai poluir seus logs. Rastreie 100% de métricas agrupadas (custo total), mas trace detalhado apenas em amostra.

"LLM-as-judge resolve tudo"

Não resolve. Combine com métricas simples e validação manual.

"Se TTFT é bom, tudo está bom"

Não. TTFT pode ser rápido mas saída ser alucinação. Mede múltiplas dimensões.

"Depois eu configuro observabilidade"

Você esquece. Configure desde o dia 1. Leva 2 horas.

Quando não monitorar

  • Protótipo de 1 dia (ok ignorar, mas não em produção)
  • Menos de 100 requisições/mês (overhead > benefício)

Próximos passos

  1. Integre com FinOps para IA para alertas de orçamento.
  2. Investigate alucinações com RAG na prática como mitigation.
  3. Melhore estrutura de prompts com Engenharia de prompt em produção.
  4. Proteja com Segurança em aplicações com LLM.

Checklist: observabilidade pronta

  • Registra tokens por requisição
  • Calcula custo por requisição
  • Mede TTFT
  • Detecta taxa de recusa
  • Avalia alucinação em amostra (5-10%)
  • Correlação com request_id
  • Dashboard com 6+ métricas
  • Alertas para outliers (custo +50%, TTFT +100%)
  • Teste com dados reais

Observabilidade é seu radar. Sem ela, você está voando cego.

Teste seu conhecimento

Teste o que você aprendeu sobre observabilidade de LLM

Pergunta 1 de 5

Seu retriever devolve um trecho que corta no meio de uma frase importante, perdendo contexto. Qual é a causa mais provável?

Continue lendo