⚡ AutomationsAI|Portal de Cursos →

Verificando acesso...

MÓDULO 5.3

📡 Observabilidade em produção

OpenTelemetry, Grafana, logs estruturados, SLOs, alertas PagerDuty/ntfy. Sem isso, prod é caixa-preta — você só descobre que quebrou quando o usuário reclama no Discord.

6
Tópicos
~55
Minutos
Avançado
Nível
Ops
Tipo
1

📊 Os 3 pilares — Logs, métricas, traces

Observabilidade moderna se apoia em três sinais: logs (eventos discretos com contexto), métricas (séries temporais agregadas) e traces (caminho de uma request por N serviços). Hermes Agent precisa dos três porque cada turno do agente envolve LLM call + tool execution + sandbox spawn — qualquer um pode falhar de jeito diferente.

💡 Quando usar cada um

  • Logs: "o que aconteceu numa request específica" — debug pós-incidente, auditoria.
  • Métricas: "como o sistema está agora vs ontem" — dashboards, alertas, SLOs.
  • Traces: "onde está o gargalo desta request lenta" — Hermes chama LLM, depois sandbox, depois LLM de novo.
# Stack canônica para Hermes em produção
                    ┌──────────────────┐
                    │  Hermes Agent    │
                    │  (Python + OTel) │
                    └────────┬─────────┘
                             │ OTLP gRPC :4317
                             ▼
                    ┌──────────────────┐
                    │  OTel Collector  │  (sidecar/daemon)
                    │  receivers/proc  │
                    └───┬────┬─────┬───┘
                        │    │     │
              logs ─────┘    │     └───── traces
                  ┌───────┐  │  ┌────────┐
                  │ Loki  │  │  │ Tempo  │
                  └───┬───┘  │  └───┬────┘
                      │      │      │
                      │   ┌──▼──┐   │
                      └──►│Graf │◄──┘
                          │ana  │
                  ┌──────►│     │
                  │       └─────┘
              ┌───┴────┐
              │ Prom   │ ◄── métricas
              └────────┘

📊 Dados — RED vs USE

  • RED (serviços): Rate, Errors, Duration — perfeito para o endpoint do Hermes.
  • USE (recursos): Utilization, Saturation, Errors — para CPU/MEM/GPU do sandbox.
  • Times maduros instrumentam ambos: RED por feature, USE por nó.
2

🔭 OpenTelemetry — Instrumentação no Hermes

OpenTelemetry (OTel) é o padrão CNCF para emitir telemetria. Você instrumenta o Hermes uma vez e troca o backend (Tempo, Jaeger, Datadog, Honeycomb) sem mudar código. Use o SDK Python + as instrumentações automáticas para requests, httpx, openai.

# requirements adicionais
opentelemetry-api==1.27.0
opentelemetry-sdk==1.27.0
opentelemetry-exporter-otlp==1.27.0
opentelemetry-instrumentation-httpx==0.48b0
opentelemetry-instrumentation-openai==0.30.0   # community

# hermes/telemetry.py
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource

resource = Resource.create({
    "service.name": "hermes-agent",
    "service.version": "0.7.2",
    "deployment.environment": os.getenv("ENV", "prod"),
})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(
    OTLPSpanExporter(endpoint="http://otel-collector:4317", insecure=True)
))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("hermes.agent")

# uso dentro do loop do agente
with tracer.start_as_current_span("agent.turn") as span:
    span.set_attribute("user.id", user_id)
    span.set_attribute("model.name", model)
    response = llm.complete(...)
    span.set_attribute("llm.tokens.in", response.usage.prompt_tokens)
    span.set_attribute("llm.tokens.out", response.usage.completion_tokens)
# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  resource:
    attributes:
      - key: cluster
        value: prod-us-east-1
        action: upsert

exporters:
  otlp/tempo:
    endpoint: tempo:4317
    tls: { insecure: true }
  prometheusremotewrite:
    endpoint: http://mimir:9009/api/v1/push
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

service:
  pipelines:
    traces:  { receivers: [otlp], processors: [memory_limiter, batch, resource], exporters: [otlp/tempo] }
    metrics: { receivers: [otlp], processors: [memory_limiter, batch, resource], exporters: [prometheusremotewrite] }
    logs:    { receivers: [otlp], processors: [memory_limiter, batch, resource], exporters: [loki] }

💡 Dica — Sentry para erros

OTel é excelente para traces/metrics, mas Sentry (ou GlitchTip self-hosted) ainda ganha em UX de exceções: stack trace agrupado, release tracking, source maps. Rode os dois lado a lado: OTel para latência, Sentry para erros. Custo: free tier do Sentry cobre 5k events/mês.

3

📈 Grafana dashboards — Templates prontos para Hermes

Você não precisa começar do zero. Use o Grafana com 3 datasources (Prometheus, Loki, Tempo) e organize em 3 dashboards: Overview (saúde geral), Per-User (debug individual), Cost (tokens × $).

# Queries PromQL essenciais para Hermes

# 1. Request rate (RED-R) por canal
sum(rate(hermes_agent_turns_total[5m])) by (channel)

# 2. Error rate (RED-E) como % do total
sum(rate(hermes_agent_turns_total{status="error"}[5m]))
  / sum(rate(hermes_agent_turns_total[5m]))

# 3. Latência P95 (RED-D) do turno completo
histogram_quantile(0.95,
  sum(rate(hermes_agent_turn_duration_seconds_bucket[5m])) by (le))

# 4. Tokens/segundo por modelo
sum(rate(hermes_llm_tokens_total[5m])) by (model, direction)

# 5. Custo aproximado por minuto ($/1M tokens hardcoded)
sum(rate(hermes_llm_tokens_total{model="hermes-4-405b",direction="output"}[1m]))
  * 60 * 3.0 / 1e6

# 6. Sandbox failures (alvo: <0.5%)
sum(rate(hermes_sandbox_spawn_total{status="failed"}[5m]))
  / sum(rate(hermes_sandbox_spawn_total[5m]))

🧩 Layout sugerido — Dashboard Overview

  • Row 1: 4 stat panels — turns/min, error %, P95, $/h.
  • Row 2: timeseries de RED (rate, errors, duration) com janela de 6h.
  • Row 3: heatmap de latência por modelo + breakdown de tools chamadas.
  • Row 4: tabela top-10 usuários por custo + link drill-down para Tempo.

📥 Atalho

Importe o dashboard "LLM Observability" ID 20100 do grafana.com como base e adapte os labels para os do Hermes. Economiza ~6h de design.

4

🎯 SLOs práticos — Latência P95, error rate, custo

SLO (Service Level Objective) é uma promessa numérica que você se compromete a cumprir. Sem SLO, alertas viram ruído. Comece com 3 SLOs por canal e ajuste depois de 2 semanas observando o real.

SLI SLO sugerido Janela Error budget/mês Por quê
Latência P95 turn< 3.0 s28d rolling5% (36 h)Chat humano tolera 3s; acima vira "lento".
Error rate (5xx + tool fail)< 1.0 %28d rolling1% (7.2 h)99% de respostas úteis = baseline mínimo.
Custo médio/request< $0.027d rolling10% das requests acimaAcima disso, prompt está inflado ou modelo é caro demais.
Sandbox spawn success> 99.5 %7d rolling0.5% (36 min)Sandbox down = agente inútil para qualquer task de código.
Disponibilidade endpoint99.9 %30d43 minPadrão de SaaS; "três noves" é alcançável em single-region.
# Recording rule para burn rate do SLO de latência
# (Sloth ou Pyrra geram isto automaticamente a partir de YAML simples)

groups:
- name: slo-hermes-latency
  interval: 30s
  rules:
  - record: slo:hermes_latency_p95:burnrate_5m
    expr: |
      (
        sum(rate(hermes_agent_turn_duration_seconds_count{le="3.0"}[5m]))
        /
        sum(rate(hermes_agent_turn_duration_seconds_count[5m]))
      )
  - alert: HermesLatencySLOBurnFast
    expr: slo:hermes_latency_p95:burnrate_5m < 0.95
    for: 2m
    labels: { severity: page }
    annotations:
      summary: "Queimando 14.4x do budget — <2h até exaurir"
      runbook: "https://wiki/runbooks/hermes-latency"
5

🚨 Alerting — PagerDuty, ntfy, Discord webhook

Alerta sem ação é ruído. Cada alerta precisa de 3 coisas: severidade clara (page vs ticket), runbook linkado, owner identificável. Hermes em produção raramente justifica PagerDuty pago — comece com ntfy.sh (FOSS, $0) e escale depois.

✓ O que FAZER

  • Alertar em burn-rate de SLO, não em valor instantâneo.
  • 2 níveis: page (acorda alguém) e ticket (vê de manhã).
  • Cada alerta com runbook em URL no annotation.
  • Rotear por canal: severo → ntfy/PagerDuty; warn → Discord.

✗ O que NÃO fazer

  • "Tudo verde" sem alerta — significa que você não tem alertas, não que está tudo bem.
  • Alertar em CPU > 80% sem contexto — ruído puro.
  • Threshold mágico ("<100ms!") sem janela e sem error budget.
  • PagerDuty pago para um agente com 50 usuários — overkill.
# alertmanager.yml — ntfy + Discord
global:
  resolve_timeout: 5m

route:
  receiver: ntfy-warn
  group_by: [alertname, severity]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers: [severity="page"]
      receiver: ntfy-page
      repeat_interval: 30m
    - matchers: [severity="ticket"]
      receiver: discord-ops

receivers:
- name: ntfy-page
  webhook_configs:
    - url: https://ntfy.sh/hermes-prod-pages
      send_resolved: true
      http_config:
        authorization:
          type: Bearer
          credentials: tk_xxxxxxxxxxx

- name: ntfy-warn
  webhook_configs:
    - url: https://ntfy.sh/hermes-prod-warn

- name: discord-ops
  webhook_configs:
    - url: https://discord.com/api/webhooks/.../...
      send_resolved: true

⚠️ Cardinality bomb

NUNCA use user_id ou session_id como label de métrica Prometheus. Cada valor único cria uma nova série temporal — 10k usuários × 5 métricas = 50k séries só dessa label. Use user_id em traces e logs, nunca em métricas. Para top-N por usuário, agregue por hora num job offline.

6

🔍 Distributed tracing — Seguir request multi-hop

Um turno do Hermes pode envolver: webhook → router → agent loop (3 LLM calls) → sandbox spawn → tool exec → S3 read. Trace distribuído amarra tudo numa árvore com timing exato, então você vê o gargalo em 5 segundos no Grafana Tempo.

1

00:00:00 — Alerta dispara

P95 burn-rate > 14x

ntfy buzina no celular. Annotation linka direto para Grafana com janela já filtrada.

2

00:00:45 — Olho no Overview

Latência subiu, error rate normal

Heatmap mostra que só requests com tool web_search estão lentas. Hipótese: API externa degradou.

3

00:01:30 — Drill-down no Tempo

Trace de exemplo, span web_search = 8.4s

Child span http GET serpapi.com sozinho consome 8.1s. Confirmado: SerpAPI está lenta.

4

00:03:00 — Mitigar

Feature flag desabilita web_search

Agente passa a responder sem search. P95 volta a <2s em 1 ciclo de scrape. Abre ticket pós-mortem.

💡 Dica — Trace ID no log

Configure o logger para incluir trace_id e span_id em todo log estruturado (Python: opentelemetry-instrumentation-logging). No Grafana, clicar num log abre o trace correspondente em outra aba. Esse "logs ↔ traces correlation" é o maior multiplicador de produtividade de incident triage.

Resumo do Módulo

Logs, métricas, traces têm papéis distintos — instrumente os três via OTel.
Stack canônica: Hermes → OTel Collector → Loki/Prometheus/Tempo → Grafana.
SLOs com error budget (latência, erro, custo) viram a régua para alertas e para decidir o que priorizar.
Alertas por burn-rate, 2 severidades, runbook obrigatório, ntfy/Discord no início.
Cuidado com cardinality bomb: nunca user_id em label de métrica.
Trace distribuído reduz MTTR de horas para minutos — investimento mais alto de ROI da stack.

Próximo módulo:

5.4 — 🔁 CI/CD para Hermes Agent: GitHub Actions buildando, testando skills e fazendo deploy automático com canary e rollback.