Pular para conteúdo

Telemetria / Dashboard

Todo turno do agente é logado em 4 tabelas para análise operacional.

Tabelas

  • dashboard_sessions — uma linha por sessão nova
  • dashboard_token_usage — uma linha por chamada LLM
  • dashboard_errors — uma linha por erro capturado
  • dashboard_tags — uma linha por tag aplicada via WTS

Schema completo em Banco de dados.

Funções em tracking.py

log_session

def log_session(session_id: str, phone: str, contact_name: str = "",
                content_type: str = "TEXT") -> None:
    with _conn.cursor() as cur:
        cur.execute(
            """INSERT INTO dashboard_sessions
               (session_id, phone_number, contact_name, content_type)
               VALUES (%s, %s, %s, %s)
               ON CONFLICT (session_id) DO NOTHING""",
            (session_id, phone, contact_name, content_type),
        )

Chamado no início do workflow — idempotente via ON CONFLICT DO NOTHING.

log_token_usage

def log_token_usage(session_id, provider, model, prompt_tokens, completion_tokens):
    total = prompt_tokens + completion_tokens
    cost_usd = _calc_cost_usd(model, prompt_tokens, completion_tokens)
    cost_brl = cost_usd * 5.70   # taxa estática; ajuste quando necessário
    with _conn.cursor() as cur:
        cur.execute(
            """INSERT INTO dashboard_token_usage
               (session_id, provider, model, prompt_tokens, completion_tokens,
                total_tokens, cost_usd, cost_brl)
               VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
            (session_id, provider, model, prompt_tokens, completion_tokens,
             total, cost_usd, cost_brl),
        )

Tabela de preços OpenAI (simplificada)

_OPENAI_PRICES = {
    # $ por 1M tokens (atualizar conforme tabela oficial)
    "gpt-4.1":      {"input": 2.50,  "output": 10.00},
    "gpt-4.1-mini": {"input": 0.15,  "output": 0.60},
    "whisper-1":    {"per_min": 0.006},
}

def _calc_cost_usd(model: str, p_in: int, p_out: int) -> float:
    price = _OPENAI_PRICES.get(model, {})
    return (p_in / 1e6) * price.get("input", 0) + (p_out / 1e6) * price.get("output", 0)

log_error

def log_error(error_type: str, error_message: str, session_id: str = "",
              phone_number: str = "", stack_trace: str = "") -> None:
    with _conn.cursor() as cur:
        cur.execute(
            """INSERT INTO dashboard_errors
               (session_id, phone_number, error_type, error_message, stack_trace)
               VALUES (%s, %s, %s, %s, %s)""",
            (session_id, phone_number, error_type, error_message, stack_trace),
        )

error_type usado tipicamente:

  • "agent_error" — exceção no agente
  • "workflow_error" — exceção no workflow
  • "otimus_error" — falha em chamada OTIMUS
  • "media_error" — falha em Gemini/Whisper
  • "falha_consulta" — FALHA_CONSULTA controlado (opcional logar)

log_transfer

def log_transfer(session_id: str) -> None:
    with _conn.cursor() as cur:
        cur.execute(
            "UPDATE dashboard_sessions SET transferred = TRUE WHERE session_id = %s",
            (session_id,),
        )

log_tag

def log_tag(session_id: str, phone: str, tag_name: str) -> None:
    with _conn.cursor() as cur:
        cur.execute(
            """INSERT INTO dashboard_tags (session_id, phone_number, tag_name)
               VALUES (%s, %s, %s)""",
            (session_id, phone, tag_name),
        )

Queries úteis

Sessões hoje

SELECT COUNT(*) FROM dashboard_sessions
WHERE started_at::date = CURRENT_DATE;

Taxa de transferência

SELECT
    COUNT(*) FILTER (WHERE transferred) * 100.0 / COUNT(*) AS transfer_rate
FROM dashboard_sessions
WHERE started_at > NOW() - INTERVAL '7 days';

Custo acumulado

SELECT
    DATE(created_at) AS dia,
    SUM(cost_usd) AS usd,
    SUM(cost_brl) AS brl,
    SUM(total_tokens) AS tokens
FROM dashboard_token_usage
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY dia
ORDER BY dia DESC;

Top erros

SELECT error_type, COUNT(*) AS n
FROM dashboard_errors
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY error_type
ORDER BY n DESC;

Dashboard visual

Os projetos atuais não expõem um dashboard visual pronto — as queries são rodadas direto no Postgres. Se quiser:

  • Grafana com datasource Postgres: painel de sessões, tokens, erros.
  • Metabase em container separado: mais visual para time não-técnico.

Limpeza / retenção

-- Apagar telemetria > 180 dias
DELETE FROM dashboard_token_usage WHERE created_at < NOW() - INTERVAL '180 days';
DELETE FROM dashboard_errors      WHERE created_at < NOW() - INTERVAL '180 days';
DELETE FROM dashboard_tags        WHERE created_at < NOW() - INTERVAL '180 days';
DELETE FROM dashboard_sessions    WHERE started_at < NOW() - INTERVAL '180 days';

Rodar mensalmente via cron no servidor.

Integração com Slack / alertas

Nenhum projeto tem hoje, mas recomendado:

  • Alerta Slack quando `COUNT(dashboard_errors WHERE created_at > NOW()
  • interval '5 min') > 10`.
  • Alerta Slack quando cost_brl diário > R$ X (detecta loop / prompt ruim).