Pular para o conteúdo principal
André Costa
Blog
Claude Code Hermes Agent
Serviços Método Meu Sistema Contato
Falar comigo
15 de junho de 2026

Como Saber se Seu Agente de IA Está Melhorando: Métricas que Realmente Importam

Guia prático de métricas para avaliar agentes de IA: do básico (tempo, custo, acurácia) ao avançado (LLM-as-judge, A/B testing, regressão). Código real, thresholds e dashboards que funcionam.

Agentes IA Métricas Avaliação LLM-as-Judge Benchmarks Hermes Agent Python LLM
Tempo de leitura 22 min
Nível Intermediário / Avançado
Pré-requisito Agente rodando em produção
Código Python + JSON
Como Saber se Seu Agente de IA Está Melhorando: Métricas que Realmente Importam
Sumário
  • O Problema: "Parece que Está Funcionando"
  • As 4 Categorias de Métricas
  • Coleta Automática de Métricas no Hermes Agent
  • LLM-as-Judge: Avaliação Automática
  • A/B Testing de Agentes
  • Detecção de Regressão
  • Dashboard Simples
  • Anti-Patterns que Destroem Métricas
  • Conclusão
Sumário do Artigo
  • O Problema: "Parece que Está Funcionando"
  • As 4 Categorias de Métricas
  • Coleta Automática de Métricas no Hermes Agent
  • LLM-as-Judge: Avaliação Automática
  • A/B Testing de Agentes
  • Detecção de Regressão
  • Dashboard Simples
  • Anti-Patterns que Destroem Métricas
  • Conclusão

Como Saber se Seu Agente de IA Está Melhorando

Você construiu um agente. Ele está rodando. Mas ele está ficando melhor ou pior? A maioria das pessoas não sabe responder. Contam com "parece que sim" ou "os usuários não reclamaram". Isso não escala.

Este guia é sobre métricas de avaliação para agentes de IA — do básico que qualquer um pode medir em 10 minutos até sistemas avançados de LLM-as-judge, A/B testing e detecção de regressão. Tudo com código real, thresholds que funcionam na prática, e um dashboard que você pode rodar hoje.

Sumário executivo — Avaliar agentes de IA exige métricas de resultado (tempo de resolução, custo, satisfação), processo (passos, ferramentas usadas, taxa de conclusão) e qualidade (acurácia, consistência, alucinações). Comece com Task Resolution Time e Task Success Rate. Depois evolua para LLM-as-judge, A/B testing e detecção de regressão. Use Python + JSON para logs estruturados, defina thresholds por tarefa e visualize tudo em um dashboard simples. O guia inclui código real, thresholds práticos e um exemplo completo de dashboard.

TL;DR rápido — Se você só medir uma coisa, meça o tempo de resolução de tarefa (Task Resolution Time). É a métrica que mais correlaciona com valor percebido pelo usuário. Mas não pare por aí.

Este guia cobre:

  • As 4 categorias de métricas que todo agente precisa
  • Código para coleta automática de métricas no Hermes Agent
  • LLM-as-judge: como usar um modelo para avaliar outro
  • A/B testing de agentes: como comparar versões
  • Detecção de regressão: como saber quando uma mudança quebrou algo
  • Dashboard simples com Python + SQLite
  • Anti-patterns que destroem a confiabilidade das suas métricas

O Problema: "Parece que Está Funcionando"

Agentes de IA são sistemas estocásticos. Eles não são determinísticos como um API REST que sempre retorna o mesmo JSON para o mesmo input. O mesmo prompt pode gerar respostas diferentes. A mesma tarefa pode levar 3 chamadas de ferramenta em uma execução e 12 na outra.

Isso torna a avaliação inherentemente mais difícil que software tradicional. Você não pode simplesmente rodar assert response == expected.

Os 3 erros clássicos

Erro 1 — Medir só o que é fácil

"Meu agente responde em 2 segundos." Ótimo. Mas a resposta está certa? Ele chamou as ferramentas certas? O usuário precisou fazer 3 follow-ups para corrigir? Velocidade ≠ qualidade.

Erro 2 — Avaliar com o mesmo modelo que gerou

Você pede ao GPT-4 para avaliar se a resposta do GPT-4 está boa. O viés de confirmação é real — mas menos absoluto do que parece. Pesquisas recentes (2024-2025) mostram que modelos modernos (Claude 3.5 Sonnet, GPT-4o, Gemini 1.5 Pro) são surpreendentemente bons auto-avaliadores quando usam rubricas estruturadas (como as do nosso LLMJudge). O problema não é o modelo ser o mesmo; é a falta de critérios explícitos. Mesmo assim, múltiplos modelos diferentes + mediana continua sendo a prática mais segura.

Erro 3 — Ignorar regressão

Você melhorou o prompt de verão. O agente ficou melhor em 80% dos casos. Mas piorou nos outros 20% — e um deles era o caso mais importante do seu cliente enterprise. Você não sabia porque não estava medindo.

Regra prática: Se você não consegue apontar uma regressão específica nas últimas 2 semanas, você não está medindo o suficiente.


As 4 Categorias de Métricas

Toda métrica de agente cai em uma destas 4 categorias. Medir só uma é insuficiente. Medir todas 4 com pesos equilibrados é o mínimo para confiar no seu sistema.

Métricas de Agente 4 categorias essenciais Eficiência • Tempo de resolução • Chamadas de ferramenta • Tokens consumidos • Latência por passo Threshold: <30s, <5 tools Qualidade • Acurácia da resposta • Relevância (human/LLM) • Completude da tarefa • Clareza e estrutura Threshold: >85% acurácia Confiabilidade • Taxa de sucesso • Taxa de erro recuperável • Taxa de loop/hallucination • Consistência entre runs Threshold: >95% sucesso Custo • Custo por tarefa • Custo por token • Custo por usuário/mês • ROI (tempo economizado) Threshold: <R$ 0,50/tarefa Score Composto de Agente Ponderação: 30% eficiência + 35% qualidade + 25% confiabilidade + 10% custo Pesos ajustáveis por caso de uso. Agentes críticos: mais confiabilidade. Agentes exploratórios: mais qualidade.

1. Eficiência — Quão rápido e enxuto

Métricas de velocidade e consumo de recursos. Fáceis de medir, difíceis de interpretar isoladamente.

Métrica O que mede Como calcular Threshold saudável
Task Resolution Time Tempo desde o input do usuário até a resposta final timestamp_fim - timestamp_inicio < 30s para tarefas simples; < 5min para complexas
Tool Calls per Task Quantidade de chamadas de ferramenta Contar function_call na sessão < 5 para tarefas simples; < 15 para complexas
Tokens Consumidos Total de tokens (input + output) Soma do uso de API Depende do modelo; normalize por tarefa
Latency Modelo Tempo de resposta do LLM (sem I/O) Média de t_resposta - t_prompt < 3s para modelos cloud; < 15s para locais
Latency Ferramenta Tempo de execução de tool calls (I/O externo) Média de t_fim_tool - t_inicio_tool < 5s para APIs; < 30s para buscas complexas

Por que importa: Um agente que resolve em 10s com 2 tool calls é mais confiável que um que resolve em 30s com 8 calls. Mais steps = mais oportunidades de erro.

Distinga latências: "Latency per Step < 2s" é irrealista se o step inclui uma chamada API externa. Separe latência do modelo (puro LLM) da latência de ferramenta (I/O de rede). O modelo você controla trocando de provider; a ferramenta você controla otimizando queries ou usando cache.

2. Qualidade — Quão boa é a resposta

Métricas subjetivas que requerem avaliação humana ou LLM-as-judge. São as mais importantes e as mais difíceis.

Métrica O que mede Como calcular Threshold saudável
Task Completion Rate A tarefa foi completamente resolvida? Binário: sim/não (human label) > 90%
Response Accuracy A informação está correta? Comparar com ground truth ou human review > 85%
Relevance Score A resposta atende ao que foi perguntado? 1-5 scale (human ou LLM) > 4.0
Clarity Score A resposta é clara e bem estruturada? 1-5 scale (human ou LLM) > 4.0

3. Confiabilidade — Quão previsível

Métricas de estabilidade e robustez. Um agente que funciona 95% das vezes é muito mais útil que um que funciona 99% mas explode nos 1% críticos.

Métrica O que mede Como calcular Threshold saudável
Success Rate % de tarefas concluídas sem erro sucessos / total > 95%
Human Intervention Rate % de tarefas que precisaram de humano intervenções / total < 10%
Recoverable Error Rate % de erros que o agente resolve sozinho auto-recuperados / total_erros > 70%
Hallucination Rate % de respostas com informação fabricada Detectado por human ou fact-checker < 5%
Loop Rate % de tarefas que entram em loop infinito Timeout ou max_steps atingido < 1%
Consistency Score Mesma tarefa, mesma resposta? N=5 runs, coeficiente de variação < 15% CV < 15%

Nota sobre métricas de confiabilidade:

  • Human Intervention Rate é a métrica mais honesta para produção. Se seu agente precisa de um humano a cada 3 tarefas, ele não é autônomo — é assistido. O threshold de < 10% é ambicioso; muitos agentes em produção operam em 20-30% no primeiro mês.
  • Recoverable Error Rate exige classificação automática. Use o LLM-as-judge para categorizar erros em "recuperável" (timeout, API rate limit, input malformado) vs "irrecuperável" (hallucination crítica, comando destrutivo, violação de segurança). O código do RegressionDetector abaixo já faz isso.
  • Hallucination Rate < 5% é mais realista que < 2%. Detecção automática via LLM-as-judge captura ~60-70% das alucinações; o restante requer fact-checking humano periódico.
  • Consistency Score: rode a mesma tarefa 5 vezes (N=5) e calcule o coeficiente de variação (desvio padrão / média). CV < 15% = consistente; CV > 30% = instável, requer investigação.

4. Custo — Quanto custa

Métricas financeiras. Fáceis de medir, mas requerem contexto de ROI para serem úteis.

Métrica O que mede Como calcular Threshold saudável
Cost per Task Custo médio de uma tarefa custo_total / num_tarefas Depende do caso; normalize
Cost per Token Eficiência de uso de tokens custo_total / tokens_total Compare com baseline
Monthly Cost per User Custo operacional custo_mês / usuários_ativos < orçamento alocado
ROI Tempo economizado vs custo (tempo_economizado * valor_hora) - custo > 3x (cada R$ 1 gera R$ 3+ de valor)

Coleta Automática de Métricas no Hermes Agent

O Hermes Agent tem acesso a todas as ferramentas necessárias para coletar métricas automaticamente. Vamos construir um sistema de logging que captura tudo — sem modificar o core do Hermes.

Estrutura de dados

Cada execução de tarefa gera um registro estruturado em JSON:

{
  "task_id": "uuid-v4",
  "timestamp_start": "2026-06-15T10:23:45Z",
  "timestamp_end": "2026-06-15T10:24:12Z",
  "task_description": "Auditar repo em busca de código morto",
  "model": "claude-sonnet-4",
  "tools_used": ["terminal", "file", "search_files"],
  "tool_calls_count": 4,
  "tokens_input": 1240,
  "tokens_output": 890,
  "cost_usd": 0.023,
  "status": "completed",
  "error": null,
  "user_rating": null,
  "llm_judge_score": null,
  "metadata": {
    "session_id": "sess-abc123",
    "profile": "default",
    "skills_triggered": ["repo-audit"]
  }
}

Código: Logger de Métricas

# metrics_logger.py
# Logger leve que se integra ao Hermes Agent via wrapper de sessão

import json
import uuid
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict

@dataclass
class TaskMetrics:
    task_id: str
    timestamp_start: str
    timestamp_end: Optional[str] = None
    task_description: str = ""
    model: str = ""
    tools_used: List[str] = None
    tool_calls_count: int = 0
    tokens_input: int = 0
    tokens_output: int = 0
    cost_usd: float = 0.0
    status: str = "running"  # running, completed, failed, timeout
    error: Optional[str] = None
    user_rating: Optional[int] = None  # 1-5
    llm_judge_score: Optional[float] = None  # 0-1
    metadata: Dict[str, Any] = None
    
    def __post_init__(self):
        if self.tools_used is None:
            self.tools_used = []
        if self.metadata is None:
            self.metadata = {}
    
    @property
    def duration_seconds(self) -> float:
        if self.timestamp_end:
            start = datetime.fromisoformat(self.timestamp_start.replace('Z', '+00:00'))
            end = datetime.fromisoformat(self.timestamp_end.replace('Z', '+00:00'))
            return (end - start).total_seconds()
        return 0.0
    
    def to_dict(self) -> Dict:
        d = asdict(self)
        d['duration_seconds'] = self.duration_seconds
        return d

class MetricsLogger:
    """Logger que persiste métricas em SQLite + JSONL para análise."""
    
    def __init__(self, db_path: str = "~/.hermes/metrics.db"):
        self.db_path = Path(db_path).expanduser()
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self.jsonl_path = self.db_path.with_suffix('.jsonl')
        self._init_db()
    
    def _init_db(self):
        import sqlite3
        conn = sqlite3.connect(str(self.db_path))
        conn.execute('''
            CREATE TABLE IF NOT EXISTS task_metrics (
                task_id TEXT PRIMARY KEY,
                timestamp_start TEXT,
                timestamp_end TEXT,
                task_description TEXT,
                model TEXT,
                tools_used TEXT,
                tool_calls_count INTEGER,
                tokens_input INTEGER,
                tokens_output INTEGER,
                cost_usd REAL,
                status TEXT,
                error TEXT,
                user_rating INTEGER,
                llm_judge_score REAL,
                duration_seconds REAL,
                metadata TEXT
            )
        ''')
        conn.execute('''
            CREATE INDEX IF NOT EXISTS idx_status ON task_metrics(status)
        ''')
        conn.execute('''
            CREATE INDEX IF NOT EXISTS idx_model ON task_metrics(model)
        ''')
        conn.execute('''
            CREATE INDEX IF NOT EXISTS idx_timestamp ON task_metrics(timestamp_start)
        ''')
        conn.commit()
        conn.close()
    
    def log_task(self, metrics: TaskMetrics):
        # Persiste em SQLite
        import sqlite3
        conn = sqlite3.connect(str(self.db_path))
        conn.execute('''
            INSERT OR REPLACE INTO task_metrics VALUES (
                ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
            )
        ''', (
            metrics.task_id,
            metrics.timestamp_start,
            metrics.timestamp_end,
            metrics.task_description,
            metrics.model,
            json.dumps(metrics.tools_used),
            metrics.tool_calls_count,
            metrics.tokens_input,
            metrics.tokens_output,
            metrics.cost_usd,
            metrics.status,
            metrics.error,
            metrics.user_rating,
            metrics.llm_judge_score,
            metrics.duration_seconds,
            json.dumps(metrics.metadata)
        ))
        conn.commit()
        conn.close()
        
        # Também append em JSONL para análise externa fácil
        with open(self.jsonl_path, 'a') as f:
            f.write(json.dumps(metrics.to_dict(), default=str) + '\n')
    
    def get_metrics(self, days: int = 7) -> List[Dict]:
        import sqlite3
        conn = sqlite3.connect(str(self.db_path))
        cursor = conn.execute('''
            SELECT * FROM task_metrics 
            WHERE timestamp_start >= datetime('now', '-{} days')
            ORDER BY timestamp_start DESC
        '''.format(days))
        rows = cursor.fetchall()
        conn.close()
        
        columns = [d[0] for d in cursor.description]
        return [dict(zip(columns, row)) for row in rows]
    
    def get_metrics_safe(self, days: int = 7) -> List[Dict]:
        """Versão com queries parametrizadas (produção)."""
        import sqlite3
        conn = sqlite3.connect(str(self.db_path))
        cursor = conn.execute('''
            SELECT * FROM task_metrics 
            WHERE timestamp_start >= datetime('now', '-' || ? || ' days')
            ORDER BY timestamp_start DESC
        ''', (days,))
        rows = cursor.fetchall()
        conn.close()
        
        columns = [d[0] for d in cursor.description]
        return [dict(zip(columns, row)) for row in rows]

Wrapper para sessões do Hermes

# hermes_metrics_wrapper.py
# Wrapper que intercepta execuções do Hermes e loga métricas

import asyncio
from metrics_logger import MetricsLogger, TaskMetrics
from datetime import datetime, timezone

logger = MetricsLogger()

class HermesMetricsWrapper:
    """Envolve uma sessão do Hermes e coleta métricas automaticamente."""
    
    def __init__(self, hermes_session):
        self.session = hermes_session
        self.current_task: TaskMetrics = None
    
    async def run_task(self, task_description: str) -> str:
        # Inicia métricas
        self.current_task = TaskMetrics(
            task_id=str(uuid.uuid4()),
            timestamp_start=datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'),
            task_description=task_description,
            model=self.session.config.get('model', 'unknown')
        )
        
        try:
            # Executa a tarefa real
            result = await self.session.run(task_description)
            
            # Coleta métricas pós-execução
            self.current_task.timestamp_end = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
            self.current_task.status = "completed"
            self.current_task.tool_calls_count = len(self.session.tool_calls)
            self.current_task.tools_used = list(set(self.session.tool_calls))
            self.current_task.tokens_input = self.session.token_usage.get('input', 0)
            self.current_task.tokens_output = self.session.token_usage.get('output', 0)
            self.current_task.cost_usd = self.session.cost_usd
            
            logger.log_task(self.current_task)
            return result
            
        except Exception as e:
            self.current_task.timestamp_end = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
            self.current_task.status = "failed"
            self.current_task.error = str(e)
            logger.log_task(self.current_task)
            raise

LLM-as-Judge: Avaliação Automática

A ideia é simples: use um modelo de linguagem para avaliar a qualidade das respostas do seu agente. A implementação é onde mora o diabo.

O problema do viés

Se você usar o mesmo modelo que gerou a resposta para avaliá-la, o viés de confirmação existe mas é mitigável. Pesquisas recentes mostram que modelos modernos (Claude 3.5 Sonnet, GPT-4o, Gemini 1.5 Pro) são bons auto-avaliadores quando usam rubricas estruturadas — como as do nosso LLMJudge abaixo. O problema histórico não era o modelo ser o mesmo; era a falta de critérios explícitos. Mesmo assim, múltiplos modelos diferentes + mediana continua sendo a prática mais segura.

Solção: Use um modelo diferente e mais barato para avaliar. Ou melhor: use múltiplos modelos e tire a mediana.

Código: LLM-as-Judge com múltiplos avaliadores

# llm_judge.py
# Sistema de avaliação com múltiplos modelos para reduzir viés

import asyncio
import json
from typing import List, Dict, Optional
from dataclasses import dataclass

@dataclass
class JudgeResult:
    model: str
    score: float  # 0-1
    reasoning: str
    dimensions: Dict[str, float]  # accuracy, relevance, clarity, completeness

class LLMJudge:
    """Avalia respostas de agentes usando múltiplos modelos como juízes."""
    
    # Modelos avaliadores — mistura de baratos e fortes
    JUDGE_MODELS = [
        "claude-haiku-3",      # Rápido e barato
        "gpt-4o-mini",          # Rápido e barato
        "claude-sonnet-4",      # Forte, mais caro
    ]
    
    # Rubrica de avaliação — critérios explícitos
    RUBRIC = """
    Você é um avaliador imparcial de respostas de agentes de IA.
    Avalie a resposta abaixo em 4 dimensões, cada uma de 0 a 1:
    
    1. **Accuracy (0-1)**: A informação está factualmente correta? 
       1.0 = 100% correto, 0.0 = completamente errado
    
    2. **Relevance (0-1)**: A resposta atende diretamente à pergunta?
       1.0 = responde exatamente o que foi perguntado, 0.0 = irrelevante
    
    3. **Clarity (0-1)**: A resposta é clara, bem estruturada e fácil de entender?
       1.0 = excelente, 0.0 = confusa ou ilegível
    
    4. **Completeness (0-1)**: A resposta cobre todos os aspectos importantes da pergunta?
       1.0 = completa, 0.0 = omitiu aspectos críticos
    
    Regras:
    - Seja CRÍTICO. Notas acima de 0.9 devem ser raras.
    - Compare mentalmente com a melhor resposta possível.
    - Se houver erro factual, accuracy = 0 independente das outras dimensões.
    
    Responda APENAS em JSON válido:
    {
      "accuracy": 0.0-1.0,
      "relevance": 0.0-1.0,
      "clarity": 0.0-1.0,
      "completeness": 0.0-1.0,
      "overall": 0.0-1.0,
      "reasoning": "explicação breve da nota"
    }
    """
    
    def __init__(self, api_client):
        self.api = api_client
    
    async def evaluate(
        self, 
        task_description: str, 
        agent_response: str,
        ground_truth: Optional[str] = None
    ) -> List[JudgeResult]:
        """Avalia uma resposta usando múltiplos modelos."""
        
        # Monta o prompt de avaliação
        eval_prompt = self._build_prompt(task_description, agent_response, ground_truth)
        
        # Avalia com cada modelo em paralelo
        tasks = [
            self._evaluate_with_model(model, eval_prompt)
            for model in self.JUDGE_MODELS
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Filtra erros
        valid_results = [r for r in results if isinstance(r, JudgeResult)]
        
        return valid_results
    
    def _build_prompt(
        self, 
        task: str, 
        response: str, 
        ground_truth: Optional[str]
    ) -> str:
        parts = [
            self.RUBRIC,
            f"\nTAREFA: {task}",
            f"\nRESPOSTA DO AGENTE:\n{response}",
        ]
        if ground_truth:
            parts.append(f"\nRESPOSTA ESPERADA (ground truth):\n{ground_truth}")
        return "\n".join(parts)
    
    async def _evaluate_with_model(self, model: str, prompt: str) -> JudgeResult:
        """Chama um modelo avaliador e parseia o resultado."""
        try:
            response = await self.api.complete(prompt, model=model, temperature=0.0)
            
            # Extrai JSON da resposta
            json_str = self._extract_json(response)
            data = json.loads(json_str)
            
            return JudgeResult(
                model=model,
                score=data.get('overall', 0.0),
                reasoning=data.get('reasoning', ''),
                dimensions={
                    'accuracy': data.get('accuracy', 0.0),
                    'relevance': data.get('relevance', 0.0),
                    'clarity': data.get('clarity', 0.0),
                    'completeness': data.get('completeness', 0.0),
                }
            )
        except Exception as e:
            # Fallback: retorna score baixo com erro documentado
            return JudgeResult(
                model=model,
                score=0.0,
                reasoning=f"Erro na avaliação: {str(e)}",
                dimensions={}
            )
    
    def _extract_json(self, text: str) -> str:
        """Extrai JSON de texto que pode ter markdown ou explicações."""
        # Tenta encontrar JSON entre ```json ... ```
        if '```json' in text:
            start = text.find('```json') + 7
            end = text.find('```', start)
            return text[start:end].strip()
        # Tenta encontrar JSON entre ``` ... ```
        if '```' in text:
            start = text.find('```') + 3
            end = text.find('```', start)
            return text[start:end].strip()
        # Assume que o texto inteiro é JSON
        return text.strip()
    
    def aggregate_scores(self, results: List[JudgeResult]) -> Dict:
        """Agrega scores de múltiplos juízes — usa mediana para resistir a outliers."""
        if not results:
            return {"overall": 0.0, "dimensions": {}, "confidence": 0.0}
        
        import statistics
        
        overall_scores = [r.score for r in results]
        dimensions = {}
        
        # Agrega cada dimensão
        for dim in ['accuracy', 'relevance', 'clarity', 'completeness']:
            scores = [r.dimensions.get(dim, 0.0) for r in results if dim in r.dimensions]
            if scores:
                dimensions[dim] = {
                    "median": statistics.median(scores),
                    "mean": statistics.mean(scores),
                    "stdev": statistics.stdev(scores) if len(scores) > 1 else 0.0
                }
        
        # Confiança = inverso da variância entre juízes
        if len(overall_scores) > 1:
            variance = statistics.variance(overall_scores)
            confidence = max(0.0, 1.0 - variance)  # 1 = total concordância
        else:
            confidence = 0.5
        
        return {
            "overall": {
                "median": statistics.median(overall_scores),
                "mean": statistics.mean(overall_scores),
                "min": min(overall_scores),
                "max": max(overall_scores)
            },
            "dimensions": dimensions,
            "confidence": confidence,
            "judge_count": len(results),
            "raw_results": [
                {"model": r.model, "score": r.score, "reasoning": r.reasoning}
                for r in results
            ]
        }

Por que múltiplos juízes?

Juízes Vantagem Quando usar
1 (mesmo modelo) Barato, rápido Nunca — viés enorme
1 (modelo diferente) Razoável Prototipagem rápida
2 (modelos diferentes) Detecta divergência MVP de avaliação
3+ (mix barato + forte) Mediana robusta, confiança calculada Produção

Regra prática: Se 3 juízes discordam amplamente (variância > 0.3), a tarefa é ambígua ou a resposta está na fronteira da qualidade. Flag para revisão humana.


A/B Testing de Agentes

Como comparar duas versões do seu agente? Não é só "rodar 10 tarefas e ver qual foi melhor". Você precisa de significância estatística.

Código: Framework de A/B Testing

# ab_testing.py
# Framework para comparar duas versões de agente com significância estatística

import random
from typing import List, Dict, Callable, Any
from dataclasses import dataclass
from statistics import mean, stdev
import math

@dataclass
class ABTestResult:
    variant_a: str
    variant_b: str
    metric: str
    n_a: int
    n_b: int
    mean_a: float
    mean_b: float
    diff_pct: float
    p_value: float
    significant: bool
    winner: str

class ABTestFramework:
    """Compara duas variantes de agente com teste t de Student."""
    
    def __init__(self, confidence: float = 0.95):
        self.confidence = confidence
        self.alpha = 1 - confidence
    
    def run_test(
        self,
        variant_a: Callable,
        variant_b: Callable,
        test_cases: List[Dict],
        metric_fn: Callable[[Any], float],
        randomize: bool = True
    ) -> ABTestResult:
        """
        Executa A/B test entre duas variantes.
        
        Args:
            variant_a: Função que executa a versão A do agente
            variant_b: Função que executa a versão B do agente
            test_cases: Lista de inputs de teste
            metric_fn: Função que extrai a métrica do resultado (ex: lambda r: r['duration'])
            randomize: Se True, distribui casos aleatoriamente entre A e B
        """
        
        results_a = []
        results_b = []
        
        for case in test_cases:
            if randomize and random.random() < 0.5:
                # A primeiro
                r_a = variant_a(case)
                r_b = variant_b(case)
            else:
                # B primeiro
                r_b = variant_b(case)
                r_a = variant_a(case)
            
            results_a.append(metric_fn(r_a))
            results_b.append(metric_fn(r_b))
        
        return self._analyze(results_a, results_b)
    
    def _analyze(self, results_a: List[float], results_b: List[float]) -> ABTestResult:
        """Análise estatística dos resultados."""
        
        n_a, n_b = len(results_a), len(results_b)
        mean_a, mean_b = mean(results_a), mean(results_b)
        
        # Teste t de Student (variâncias independentes)
        var_a = stdev(results_a) ** 2 if n_a > 1 else 0
        var_b = stdev(results_b) ** 2 if n_b > 1 else 0
        
        # Standard error
        se = math.sqrt(var_a / n_a + var_b / n_b)
        
        # t-statistic
        if se == 0:
            t_stat = 0
        else:
            t_stat = (mean_a - mean_b) / se
        
        # Graus de liberdade (Welch-Satterthwaite)
        if var_a == 0 and var_b == 0:
            df = n_a + n_b - 2
        else:
            numerator = (var_a / n_a + var_b / n_b) ** 2
            denominator = (var_a / n_a) ** 2 / (n_a - 1) + (var_b / n_b) ** 2 / (n_b - 1)
            df = numerator / denominator if denominator > 0 else n_a + n_b - 2
        
        # p-value (aproximação para two-tailed)
        from math import erf, sqrt
        p_value = 2 * (1 - 0.5 * (1 + erf(abs(t_stat) / sqrt(2))))
        
        diff_pct = ((mean_b - mean_a) / mean_a * 100) if mean_a != 0 else 0
        
        winner = "tie"
        if p_value < self.alpha:
            winner = "B" if mean_b > mean_a else "A"
        
        return ABTestResult(
            variant_a="A",
            variant_b="B",
            metric="custom",
            n_a=n_a,
            n_b=n_b,
            mean_a=mean_a,
            mean_b=mean_b,
            diff_pct=diff_pct,
            p_value=p_value,
            significant=p_value < self.alpha,
            winner=winner
        )
    
    def recommend_sample_size(
        self,
        baseline_mean: float,
        expected_improvement_pct: float,
        baseline_std: float
    ) -> int:
        """Calcula tamanho de amostra necessário para detectar uma melhoria."""
        
        # Efeito mínimo detectável
        mde = baseline_mean * (expected_improvement_pct / 100)
        
        # Cohen's d
        cohens_d = mde / baseline_std if baseline_std > 0 else 0.5
        
        # Para 80% power, 95% confidence, two-tailed
        # Fórmula simplificada: n = 16 / d^2 por grupo
        if cohens_d > 0:
            n_per_group = int(math.ceil(16 / (cohens_d ** 2)))
        else:
            n_per_group = 100  # fallback
        
        return n_per_group

Exemplo de uso

# Exemplo: comparar dois prompts de agente SEO

from ab_testing import ABTestFramework

# Versão A: prompt atual
agent_a = lambda case: run_seo_agent(case['url'], prompt_version="v1")

# Versão B: novo prompt com instruções mais específicas
agent_b = lambda case: run_seo_agent(case['url'], prompt_version="v2")

# Métrica: tempo de resolução
metric = lambda result: result['duration_seconds']

# Casos de teste
 test_cases = [
    {"url": "https://example1.com", "expected_pages": 50},
    {"url": "https://example2.com", "expected_pages": 200},
    {"url": "https://example3.com", "expected_pages": 10},
    # ... 20+ casos
]

# Recomendação de tamanho de amostra
framework = ABTestFramework(confidence=0.95)
n_needed = framework.recommend_sample_size(
    baseline_mean=45.0,      # 45s médio atual
    expected_improvement_pct=20,  # Espero 20% de melhoria
    baseline_std=12.0        # Desvio padrão de 12s
)
print(f"Precisa de {n_needed} casos por grupo ({n_needed * 2} total)")

# Executa o teste
result = framework.run_test(agent_a, agent_b, test_cases, metric)

print(f"Variante A: {result.mean_a:.1f}s (n={result.n_a})")
print(f"Variante B: {result.mean_b:.1f}s (n={result.n_b})")
print(f"Diferença: {result.diff_pct:+.1f}%")
print(f"p-value: {result.p_value:.4f}")
print(f"Significativo: {result.significant}")
print(f"Vencedor: {result.winner}")

Regras para A/B testing de agentes

  1. Nunca use menos de 30 casos por grupo — a variância de agentes é alta
  2. Randomize a ordem — se A sempre roda primeiro, efeitos de cache/warmup viciam
  3. Use métricas primárias e secundárias — defina uma métrica principal antes de rodar o teste
  4. Não peek — não pare o teste no meio porque "parece bom". Isso invalida a significância.
  5. Documente tudo — versão do modelo, temperatura, seed, versão do prompt. Tudo afeta.

Detecção de Regressão

A mudança que melhora 80% dos casos mas quebra 20% é pior que não mudar nada. Você precisa detectar regressões automaticamente.

Código: Sistema de Regressão Detection

# regression_detector.py
# Detecta quando uma nova versão do agente piora em casos específicos

import json
from typing import List, Dict, Tuple
from dataclasses import dataclass
from datetime import datetime, timedelta

@dataclass
class RegressionAlert:
    severity: str  # critical, warning, info
    task_type: str
    metric: str
    baseline_value: float
    current_value: float
    change_pct: float
    affected_cases: List[str]
    recommendation: str

class RegressionDetector:
    """Compara métricas atuais com baseline histórico e detecta regressões."""
    
    # Thresholds de regressão por métrica
    THRESHOLDS = {
        "success_rate": {"critical": -5.0, "warning": -2.0},  # queda de %
        "duration_seconds": {"critical": 50.0, "warning": 20.0},  # aumento de %
        "tool_calls_count": {"critical": 100.0, "warning": 50.0},  # aumento de %
        "cost_usd": {"critical": 100.0, "warning": 50.0},  # aumento de %
        "llm_judge_score": {"critical": -10.0, "warning": -5.0},  # queda de %
    }
    
    def __init__(self, metrics_logger, baseline_days: int = 14):
        self.logger = metrics_logger
        self.baseline_days = baseline_days
    
    def detect(self, current_days: int = 1) -> List[RegressionAlert]:
        """Detecta regressões comparando período atual com baseline."""
        
        # Busca métricas
        baseline = self.logger.get_metrics(days=self.baseline_days)
        current = self.logger.get_metrics(days=current_days)
        
        alerts = []
        
        # Agrupa por tipo de tarefa
        baseline_by_type = self._group_by_task_type(baseline)
        current_by_type = self._group_by_task_type(current)
        
        for task_type in set(baseline_by_type.keys()) | set(current_by_type.keys()):
            b_metrics = baseline_by_type.get(task_type, [])
            c_metrics = current_by_type.get(task_type, [])
            
            if len(b_metrics) < 10 or len(c_metrics) < 5:
                continue  # Dados insuficientes
            
            # Compara cada métrica
            for metric_name in ["success_rate", "duration_seconds", "tool_calls_count", "cost_usd", "llm_judge_score"]:
                alert = self._compare_metric(task_type, metric_name, b_metrics, c_metrics)
                if alert:
                    alerts.append(alert)
        
        return sorted(alerts, key=lambda a: a.severity)
    
    def _group_by_task_type(self, metrics: List[Dict]) -> Dict[str, List[Dict]]:
        """Agrupa métricas por tipo de tarefa (extraído da descrição)."""
        groups = {}
        for m in metrics:
            # Extrai tipo da descrição (primeiras 3 palavras)
            desc = m.get('task_description', '')
            task_type = ' '.join(desc.split()[:3]).lower() if desc else 'unknown'
            
            if task_type not in groups:
                groups[task_type] = []
            groups[task_type].append(m)
        
        return groups
    
    def _compare_metric(
        self, 
        task_type: str, 
        metric_name: str, 
        baseline: List[Dict], 
        current: List[Dict]
    ) -> Optional[RegressionAlert]:
        """Compara uma métrica específica e retorna alerta se houver regressão."""
        
        # Calcula valores agregados
        b_value = self._aggregate(baseline, metric_name)
        c_value = self._aggregate(current, metric_name)
        
        if b_value == 0:
            return None
        
        change_pct = ((c_value - b_value) / b_value) * 100
        
        # Determina direção do problema
        thresholds = self.THRESHOLDS.get(metric_name, {})
        
        # Para métricas onde MAIOR é pior (duration, cost, tool_calls)
        is_bigger_worse = metric_name in ["duration_seconds", "tool_calls_count", "cost_usd"]
        
        if is_bigger_worse and change_pct > 0:
            # Aumento = regressão
            if change_pct >= thresholds.get("critical", 50.0):
                severity = "critical"
            elif change_pct >= thresholds.get("warning", 20.0):
                severity = "warning"
            else:
                return None
        elif not is_bigger_worse and change_pct < 0:
            # Queda = regressão (success_rate, judge_score)
            if abs(change_pct) >= abs(thresholds.get("critical", -5.0)):
                severity = "critical"
            elif abs(change_pct) >= abs(thresholds.get("warning", -2.0)):
                severity = "warning"
            else:
                return None
        else:
            return None  # Melhoria, não regressão
        
        # Identifica casos afetados
        affected = [m['task_id'] for m in current if self._is_outlier(m, metric_name, b_value)]
        
        return RegressionAlert(
            severity=severity,
            task_type=task_type,
            metric=metric_name,
            baseline_value=b_value,
            current_value=c_value,
            change_pct=change_pct,
            affected_cases=affected[:10],  # Top 10
            recommendation=self._recommendation(metric_name, change_pct)
        )
    
    def _aggregate(self, metrics: List[Dict], metric_name: str) -> float:
        """Agrega métrica — média para a maioria, taxa para success_rate."""
        if metric_name == "success_rate":
            successes = sum(1 for m in metrics if m.get('status') == 'completed')
            return (successes / len(metrics)) * 100 if metrics else 0
        
        values = [m.get(metric_name, 0) for m in metrics if m.get(metric_name) is not None]
        return sum(values) / len(values) if values else 0
    
    def _is_outlier(self, metric: Dict, metric_name: str, baseline: float) -> bool:
        """Verifica se um caso específico é outlier negativo."""
        value = metric.get(metric_name, 0)
        if value is None:
            return False
        
        is_bigger_worse = metric_name in ["duration_seconds", "tool_calls_count", "cost_usd"]
        
        if is_bigger_worse:
            return value > baseline * 1.5  # 50% pior que baseline
        else:
            return value < baseline * 0.8  # 20% pior que baseline
    
    def _recommendation(self, metric_name: str, change_pct: float) -> str:
        """Gera recomendação baseada na métrica e magnitude."""
        
        recommendations = {
            "duration_seconds": "Verifique se há loops ou chamadas de ferramenta desnecessárias. Reveja o prompt.",
            "tool_calls_count": "O agente está chamando mais ferramentas que o necessário. Adicione restrições no prompt.",
            "cost_usd": "Custo aumentou significativamente. Considere usar modelo mais barato ou reduzir tokens.",
            "success_rate": "Taxa de sucesso caiu. Verifique casos de erro e adicione handling.",
            "llm_judge_score": "Qualidade percebida caiu. Revise o prompt e ground truth."
        }
        
        return recommendations.get(metric_name, "Investigue a causa raiz da regressão.")

Dashboard Simples

Métricas sem visualização são números mortos. Vamos construir um dashboard leve com Python puro.

Código: Dashboard em Python

# dashboard.py
# Dashboard CLI para métricas de agente — roda no terminal

import sqlite3
from pathlib import Path
from datetime import datetime, timedelta
from typing import Dict, List

class AgentDashboard:
    """Dashboard CLI para acompanhar métricas do agente."""
    
    def __init__(self, db_path: str = "~/.hermes/metrics.db"):
        self.db_path = Path(db_path).expanduser()
    
    def render(self, days: int = 7):
        """Renderiza dashboard no terminal."""
        
        conn = sqlite3.connect(str(self.db_path))
        
        # Header
        print("\n" + "=" * 70)
        print(f"  AGENT METRICS DASHBOARD — Últimos {days} dias")
        print("=" * 70)
        
        # KPIs principais
        kpis = self._get_kpis(conn, days)
        print("\n  📊 KPIs PRINCIPAIS")
        print("  " + "-" * 66)
        print(f"  Tarefas executadas:    {kpis['total_tasks']:>6}")
        print(f"  Taxa de sucesso:       {kpis['success_rate']:>5.1f}%")
        print(f"  Tempo médio:           {kpis['avg_duration']:>5.1f}s")
        print(f"  Custo médio/tarefa:    ${kpis['avg_cost']:>5.3f}")
        print(f"  Chamadas de tool/task: {kpis['avg_tools']:>5.1f}")
        print(f"  Score LLM Judge:       {kpis['avg_judge_score']:>5.2f}")
        
        # Tendência (comparar com período anterior)
        trend = self._get_trend(conn, days)
        print("\n  📈 TENDÊNCIA (vs período anterior)")
        print("  " + "-" * 66)
        for metric, change in trend.items():
            arrow = "↑" if change > 0 else "↓" if change < 0 else "→"
            color = "🟢" if (metric in ['success_rate', 'llm_judge_score'] and change > 0) or \
                           (metric not in ['success_rate', 'llm_judge_score'] and change < 0) else \
                    "🔴" if (metric in ['success_rate', 'llm_judge_score'] and change < 0) or \
                           (metric not in ['success_rate', 'llm_judge_score'] and change > 0) else "⚪"
            print(f"  {color} {metric:<20} {arrow} {abs(change):>5.1f}%")
        
        # Top tarefas mais lentas
        print("\n  🐌 TOP 5 TAREFAS MAIS LENTAS")
        print("  " + "-" * 66)
        slow = self._get_slowest_tasks(conn, days)
        for i, task in enumerate(slow, 1):
            desc = task['task_description'][:40] + "..." if len(task['task_description']) > 40 else task['task_description']
            print(f"  {i}. {desc:<45} {task['duration_seconds']:>6.1f}s")
        
        # Top erros
        print("\n  ❌ TOP 5 ERROS MAIS FREQUENTES")
        print("  " + "-" * 66)
        errors = self._get_top_errors(conn, days)
        for i, err in enumerate(errors, 1):
            print(f"  {i}. {err['error'][:50]:<50} ({err['count']}x)")
        
        # Modelos mais usados
        print("\n  🤖 MODELOS MAIS USADOS")
        print("  " + "-" * 66)
        models = self._get_model_usage(conn, days)
        for model in models:
            bar = "█" * int(model['pct'] / 5)
            print(f"  {model['model']:<30} {bar:<20} {model['pct']:>5.1f}%")
        
        print("\n" + "=" * 70 + "\n")
        conn.close()
    
    def _get_kpis(self, conn, days: int) -> Dict:
        """Calcula KPIs agregados."""
        cursor = conn.execute('''
            SELECT 
                COUNT(*) as total,
                SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate,
                AVG(duration_seconds) as avg_duration,
                AVG(cost_usd) as avg_cost,
                AVG(tool_calls_count) as avg_tools,
                AVG(llm_judge_score) as avg_judge
            FROM task_metrics
            WHERE timestamp_start >= datetime('now', '-' || ? || ' days')
        ''', (days,))
        
        row = cursor.fetchone()
        return {
            'total_tasks': row[0] or 0,
            'success_rate': row[1] or 0,
            'avg_duration': row[2] or 0,
            'avg_cost': row[3] or 0,
            'avg_tools': row[4] or 0,
            'avg_judge_score': (row[5] or 0) * 100  # converter para 0-100
        }
    
    def _get_trend(self, conn, days: int) -> Dict[str, float]:
        """Calcula tendência comparando período atual vs período anterior de mesma duração."""
        from datetime import datetime, timedelta
        
        now = datetime.now()
        current_start = now - timedelta(days=days)
        previous_start = now - timedelta(days=days * 2)
        
        # Período atual: [now-days, now]
        cursor = conn.execute('''
            SELECT 
                COUNT(*) as total,
                SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate,
                AVG(duration_seconds) as avg_duration,
                AVG(cost_usd) as avg_cost,
                AVG(tool_calls) as avg_tools,
                AVG(llm_judge_score) as avg_judge
            FROM task_metrics
            WHERE timestamp_start >= ? AND timestamp_start <= ?
        ''', (current_start.isoformat(), now.isoformat()))
        row_curr = cursor.fetchone()
        
        # Período anterior: [now-2*days, now-days]
        cursor = conn.execute('''
            SELECT 
                COUNT(*) as total,
                SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate,
                AVG(duration_seconds) as avg_duration,
                AVG(cost_usd) as avg_cost,
                AVG(tool_calls) as avg_tools,
                AVG(llm_judge_score) as avg_judge
            FROM task_metrics
            WHERE timestamp_start >= ? AND timestamp_start < ?
        ''', (previous_start.isoformat(), current_start.isoformat()))
        row_prev = cursor.fetchone()
        
        current = {
            'success_rate': row_curr[1] or 0,
            'avg_duration': row_curr[2] or 0,
            'avg_cost': (row_curr[3] or 0) * 100,
            'avg_tools': row_curr[4] or 0,
            'avg_judge_score': (row_curr[5] or 0) * 100,
        }
        previous = {
            'success_rate': row_prev[1] or 0,
            'avg_duration': row_prev[2] or 0,
            'avg_cost': (row_prev[3] or 0) * 100,
            'avg_tools': row_prev[4] or 0,
            'avg_judge_score': (row_prev[5] or 0) * 100,
        }
        
        trend = {}
        for metric in ['success_rate', 'avg_duration', 'avg_cost', 'avg_tools', 'avg_judge_score']:
            curr = current.get(metric, 0)
            prev = previous.get(metric, 0)
            if prev and prev != 0:
                trend[metric] = ((curr - prev) / prev) * 100
            else:
                trend[metric] = 0
        
        return trend
    
    def _get_slowest_tasks(self, conn, days: int) -> List[Dict]:
        cursor = conn.execute('''
            SELECT task_description, duration_seconds
            FROM task_metrics
            WHERE timestamp_start >= datetime('now', '-' || ? || ' days')
            AND status = 'completed'
            ORDER BY duration_seconds DESC
            LIMIT 5
        ''', (days,))
        
        return [
            {'task_description': row[0], 'duration_seconds': row[1]}
            for row in cursor.fetchall()
        ]
    
    def _get_top_errors(self, conn, days: int) -> List[Dict]:
        cursor = conn.execute('''
            SELECT error, COUNT(*) as count
            FROM task_metrics
            WHERE timestamp_start >= datetime('now', '-' || ? || ' days')
            AND error IS NOT NULL
            GROUP BY error
            ORDER BY count DESC
            LIMIT 5
        ''', (days,))
        
        return [
            {'error': row[0], 'count': row[1]}
            for row in cursor.fetchall()
        ]
    
    def _get_model_usage(self, conn, days: int) -> List[Dict]:
        cursor = conn.execute('''
            SELECT model, COUNT(*) as count
            FROM task_metrics
            WHERE timestamp_start >= datetime('now', '-' || ? || ' days')
            GROUP BY model
            ORDER BY count DESC
        ''', (days,))
        
        rows = cursor.fetchall()
        total = sum(r[1] for r in rows)
        
        return [
            {'model': row[0], 'count': row[1], 'pct': (row[1] / total * 100) if total else 0}
            for row in rows
        ]

# Uso
if __name__ == "__main__":
    dashboard = AgentDashboard()
    dashboard.render(days=7)

Exemplo de output

======================================================================
  AGENT METRICS DASHBOARD — Últimos 7 dias
======================================================================

  📊 KPIs PRINCIPAIS
  ------------------------------------------------------------------
  Tarefas executadas:      847
  Taxa de sucesso:        94.2%
  Tempo médio:            18.3s
  Custo médio/tarefa:     $0.042
  Chamadas de tool/task:   3.2
  Score LLM Judge:        0.78

  📈 TENDÊNCIA (vs período anterior)
  ------------------------------------------------------------------
  🟢 success_rate         ↑   2.1%
  🔴 avg_duration         ↑  15.3%
  🔴 avg_cost             ↑   8.7%
  🟢 avg_tools            ↓  12.0%
  🟢 avg_judge_score      ↑   5.2%

  🐌 TOP 5 TAREFAS MAIS LENTAS
  ------------------------------------------------------------------
  1. Auditar repo completo em busca de vuln...      245.6s
  2. Gerar relatório SEO para site de 500 pá...    189.2s
  3. Migrar componentes React para Vue...           156.3s
  4. Analisar concorrentes para campanha...         134.1s
  5. Criar pipeline de CI/CD completo...             98.7s

  ❌ TOP 5 ERROS MAIS FREQUENTES
  ------------------------------------------------------------------
  1. Timeout: tool call exceeded 30s limit (23x)
  2. API rate limit exceeded (12x)
  3. Invalid JSON response from tool (8x)
  4. Model context window exceeded (5x)
  5. Permission denied on file access (3x)

  🤖 MODELOS MAIS USADOS
  ------------------------------------------------------------------
  claude-sonnet-4          ████████████████     45.2%
  gpt-4o-mini              ██████████           28.7%
  claude-haiku-3           ██████               18.3%
  gpt-4o                   ██                    7.8%

======================================================================

Anti-Patterns que Destroem Métricas

1. Métricas de vaidade

"Meu agente processou 10.000 tarefas este mês." Quantas deram certo? Quantas o usuário teve que refazer? Quantas geraram valor real?

Correção: Sempre pareie volume com qualidade. Tarefas/processadas é inútil sem tarefas/sucesso.

2. Avaliação pontual

Você avalia o agente uma vez, diz "tá bom", e nunca mais avalia. O modelo muda. O prompt muda. Os dados mudam. O agente pode estar piorando silenciosamente.

Correção: Avaliação contínua. Rode o LLM-as-judge em uma amostra de 5% das tarefas automaticamente.

3. Ground truth desatualizado

Seu benchmark de avaliação tem 50 casos de teste. Você não atualiza há 3 meses. O agente agora lida com cenários que não existiam no benchmark.

Correção: Adicione 2-3 casos novos por semana. Remova casos que não são mais relevantes.

4. Métricas desconectadas de negócio

Você mede tokens/segundo. Seu CEO pergunta "quanto dinheiro isso economizou?" Você não sabe responder.

Correção: Sempre conecte métricas técnicas a métricas de negócio. Tokens → tempo → dinheiro.

5. Alert fatigue

Seu sistema de regressão dispara 50 alertas por dia. Você começa a ignorar todos.

Correção: Use thresholds dinâmicos. Só alerte quando a mudança é estatisticamente significante E afeta casos críticos.


Conclusão

Avaliar agentes de IA é mais difícil que avaliar software tradicional — mas não é impossível. O segredo é medir as 4 categorias (eficiência, qualidade, confiabilidade, custo) com múltiplas técnicas (automáticas + humanas + LLM-as-judge) e comparar ao longo do tempo (baseline, A/B, regressão).

O que você precisa lembrar:

  • Task Resolution Time é a métrica mais correlacionada com valor percebido
  • LLM-as-judge com múltiplos modelos reduz viés e dá confiança calculada
  • A/B testing precisa de significância estatística — não adivinhação
  • Regressão detection é mais importante que otimização contínua
  • Dashboard semanal é o mínimo para times que levam agentes a sério

Próximos passos:

  1. Implemente o MetricsLogger no seu agente hoje — são 50 linhas de código
  2. Escolha 5 casos de teste representativos e meça baseline
  3. Configure o dashboard para rodar toda segunda-feira de manhã
  4. Na próxima mudança de prompt, rode A/B test em vez de "testar manualmente"
  5. Leia como construir agentes efetivos — métricas sem boa arquitetura são números vazios
  6. Revise segurança de agentes em produção — métricas de confiabilidade e detecção de regressão são sua primeira linha de defesa contra falhas de segurança. Um agente que está degradando em success_rate ou aumentando em Human Intervention Rate pode estar exposto a vetores de ataque que você não detectou ainda.

Publicado em andrecosta.ia.br.

Leia também

  • Como Construir Agentes de IA Efetivos Patterns de arquitetura, os 3 pilares de um agente e implementação prática com código.
  • Segurança de Agentes de IA em Produção 5 vetores de ataque, defesa em camadas e código real para guardrails.
  • Hermes Agent: Guia para Iniciantes Instalação, workflows e o learning loop que faz o agente melhorar sozinho.
  • Hermes Agent: Múltiplos Modelos como Máquina de Conteúdo Como usar routing inteligente de modelos para otimizar custo e qualidade.
  • O que são Agentes de IA Fundamentos de agentes autônomos de IA: arquitetura, ferramentas e exemplos práticos.
  • Todos os artigos Explore todos os guias e tutoriais do blog.

Quer aplicar isso no seu trabalho? Falar comigo no WhatsApp (confiança humana) ou comece seu próximo passo na trilha para construir autonomia real via fundamentos + método.

← Blog Início
André Costa

Fundamentos e método para você projetar e construir automações e agentes com autonomia real. Sem hype.

Páginas

Blog O que faço Método Conteúdos Contato

Legal

Privacidade Termos de Uso
© 2026 André Costa Autonomia com IA / fundamentos práticos / sistemas que você controla

Utilizamos cookies para entender como você interage com nosso site (GA4/GTM) e melhorar sua experiência técnica. Ao continuar, você concorda com nossa Política de Privacidade.