⚡ AutomationsAI|Portal de Cursos →

Verificando acesso...

MÓDULO 2.3

🔬 Anatomia de uma requisição (trace end-to-end)

Trace passo-a-passo de uma mensagem do Telegram até a resposta voltar, com timing real de cada hop. O exercício mais clarificador da arquitetura — saindo daqui você sabe onde otimizar, onde cachear e onde NÃO mexer.

6
Tópicos
~50
Minutos
Avanç.
Nível
Deep
Tipo

⏱️ Timeline completa do trace (alvo: < 3s para "oi tudo bem?")

Toda mensagem percorre 6 hops. Em condições normais (modelo quente, FTS5 indexado, sem tool calls pesadas), o budget é:

t=0ms     ┌─ Telegram emite update
t=30ms    │  H1: Webhook recebe POST           (rede + TLS handshake reuso)
t=35ms    │  H2: Gateway router → toolset      (lookup em RAM)
t=180ms   │  H3: FTS5 lookup + context build   (SQLite + Honcho cache)
t=2100ms  │  H4: LLM inference (Sonnet 4.6)    (TTFT ~400ms + streaming)
t=2150ms  │  H5: Tool exec (skip se sem tool)  (0ms neste caso)
t=2200ms  └─ H6: Resposta + memory write       (Telegram sendMessage)
              TOTAL: ~2.2s (P50) · 4-6s (P95) · 10s+ (P99 cold)

Os próximos 6 tópicos abrem cada hop com latências medidas em produção, sequence diagram, e os 1-2 lugares onde 90% das pessoas erram a otimização.

1

📨 Hop 1 — Telegram → Webhook (~30ms)

O Telegram Bot API faz POST HTTPS para o endpoint registrado em setWebhook. Esse é o único hop que você não controla — o tempo é dominado pela distância geográfica entre os datacenters da Telegram (Holanda/Singapura) e seu servidor.

Telegram DC          Seu webhook
     │                    │
     │── POST /hermes ───▶│   t=0    (TCP já estabelecido — keep-alive)
     │   {update_id,      │
     │    message:{       │
     │      text:"oi",    │
     │      from:{id:42}, │
     │      chat:{id:99}  │
     │    }}              │
     │                    │── parse JSON  t=2ms
     │                    │── ack 200 OK  t=3ms  ◀── DEVE responder <5s
     │◀── 200 ────────────│
     │                    │── enfileira no gateway (async)

📊 Latências reais (medido com HERMES_TRACE=1)

  • P50: 28ms (servidor em Frankfurt, Telegram NL)
  • P95: 85ms (renegociação TLS, pico de tráfego)
  • P99: 320ms (cold connection, packet loss)
  • Timeout do Telegram: 60s — se não devolver 200 em <5s ele retry com mesmo update_id

✓ O que FAZER

  • Responder 200 OK imediatamente e processar em background (fila/asyncio task)
  • Deduplicar por update_id — Telegram retry em caso de timeout
  • Validar secret_token no header X-Telegram-Bot-Api-Secret-Token
  • HTTP/2 + keep-alive para reduzir 100ms de handshake

✗ O que NÃO fazer

  • Processar o LLM antes de devolver 200 — Telegram vai retry e você processa 2x
  • Expor webhook sem secret token (qualquer um envia POST falso)
  • Long polling em produção — gateway perde mensagens em restart
  • Hospedar em região distante do seu público (lat 300ms+ é norma)

💡 Dica

Ative HERMES_TRACE=1 e cada hop loga hop=H1 dur_ms=28 update_id=12345. Plot esses números num histograma semanal — você descobre cold connection peaks antes do usuário reclamar.

2

🚪 Hop 2 — Gateway router → toolset (~5ms)

O gateway normaliza o update do Telegram em um Message interno, identifica o canal/usuário e resolve qual toolset carregar. Tudo em RAM — esse hop só estoura se você tiver lookup em DB remoto (anti-padrão).

Webhook handler           Gateway              ToolsetRegistry
     │                       │                        │
     │── Message(             │                        │
     │     channel="tg",     │                        │
     │     user_id=42,       │                        │
     │     text="oi"         │                        │
     │   ) ──────────────────▶                        │
     │                       │── route(user=42) ─────▶│
     │                       │                        │── lookup
     │                       │◀── ["chat","memory"] ──│   profile=default
     │                       │                        │   tools=2 (cached)
     │                       │── attach context ──────│   t=5ms
     │                       │      (RAM lookup)

📊 Latências P50/P95/P99

  • P50: 3ms (toolset em RAM, registry warm)
  • P95: 12ms (primeira mensagem do usuário após restart)
  • P99: 80ms (lookup de profile em SQLite + parse YAML)
  • Anti-padrão observado: lookup em Postgres remoto → 200ms+ (não faça isso)

💡 Dica

O registry é lazy-loaded mas cacheia para sempre. Se você editar config.yaml e adicionar um toolset, precisa /reload ou reiniciar — não há TTL.

3

🧠 Hop 3 — Memory FTS5 lookup + context build (~50-200ms)

Aqui o Hermes monta o contexto que vai para o LLM: últimas N mensagens da sessão, fatos relevantes via FTS5, user representation do Honcho, e prompts de sistema. É o hop mais variável — onde mora 80% da otimização.

Gateway        ContextBuilder      SQLite FTS5       Honcho
   │                 │                   │                │
   │── build(msg) ──▶│                   │                │
   │                 │── recent(N=20) ──▶│  t=8ms         │
   │                 │◀── messages ──────│                │
   │                 │── fts5 query ────▶│  t=15ms        │
   │                 │   "oi tudo bem"    │  BM25 rank     │
   │                 │◀── 3 facts ───────│                │
   │                 │── user_repr ─────────────────────▶│  t=120ms
   │                 │   (dialectic agent → LLM call)     │  ⚠️ HOTSPOT
   │                 │◀── "prefere PT-BR, técnico" ──────│
   │                 │── assemble prompt                  │
   │                 │   (sys + repr + facts + history)   │
   │◀── ChatRequest ─│   tokens=2400  t=180ms             │

📊 Latências reais — onde o tempo vai

  • FTS5 recent (N=20): P50 8ms · P95 25ms — SQLite local, índice quente
  • FTS5 search (BM25): P50 15ms · P95 60ms — depende do tamanho do corpus
  • Honcho user_repr local: P50 45ms · P95 180ms — chama LLM Haiku para sintetizar
  • Honcho cloud: P50 200ms · P95 800ms — rede + inferência remota
  • Assemble final: P50 2ms (concatenação de string)
  • TOTAL hop 3: P50 70ms (local) · 250ms (cloud)

✓ O que FAZER para otimizar

  • Cachear user_repr por sessão (TTL 5min) — economiza 150ms por turno
  • Limitar FTS5 a top-K=5 com BM25 rank — mais não ajuda o LLM
  • Indexar com tokenize='porter unicode61' para PT-BR
  • Prompt caching do provider — sys+repr é estável, candidato perfeito

✗ O que NÃO fazer

  • Recomputar user_repr em todo turno (200ms+ de Honcho desperdiçados)
  • Mandar TODO o histórico — passa de 32K tokens, custo explode
  • Buscar embeddings vetoriais se FTS5 já resolve 90% dos casos
  • Bloquear na call do Honcho — use timeout 500ms e fallback vazio
4

🤖 Hop 4 — LLM inference (latência variável)

O hop dominante. Cada modelo tem TTFT (time-to-first-token) e tokens/s distintos. O total é TTFT + output_tokens / tokens_per_sec. Streaming não muda o total mas melhora UX porque o usuário vê o primeiro token em <500ms.

📊 Latência por modelo (medido via OpenRouter, prompt=2K, output=200 tokens)

ModeloTTFT P50tok/sTotal P50$/1M out
claude-haiku-4.5280ms1201.9s$1.25
claude-sonnet-4.6420ms802.9s$15
claude-opus-4.7650ms455.1s$75
gpt-5380ms952.5s$10
gemini-2.5-pro450ms1401.9s$5
deepseek-v3.2520ms603.8s$1.10
llama-4-maverick (local)120ms2001.1s$0 + GPU

⚠️ Atenção: cold-start em providers serverless

Modelos open-source pouco usados no OpenRouter (qualquer :free ou :nitro com baixo throughput) sofrem cold-start de 5-15s no primeiro request. Você vê P99 disparar para 20s+. Solução: warm-up cron a cada 4min OU não use tier free em produção.

💡 Dica de roteamento por intenção

Configure model.router no YAML: chat trivial → Haiku, raciocínio → Sonnet, código difícil → Opus. Economiza 60-80% do custo mantendo qualidade percebida.

5

🛠️ Hop 5 — Tool execution + (opcional) re-prompt

Quando o LLM retorna tool_calls em vez de texto, o gateway executa as ferramentas (em paralelo quando possível) e re-prompta o modelo com os resultados. Cada round-trip = mais um Hop 4. É aqui que requests "rápidos" viram 15s.

LLM            ToolExecutor       Tool (ex: web_search)
 │                 │                      │
 │── tool_calls ──▶│                      │
 │  [search("X"),  │── parallel.gather ──▶│  t=600ms
 │   fetch("Y")]   │                      │  (HTTP + parse)
 │                 │◀── results ──────────│
 │                 │── re-prompt          │
 │◀── full ctx ────│   (tokens cresceram)
 │   + tool out
 │── final text ──▶ resposta              t+=2.5s (Hop 4 de novo)
A

Sem tool call

"oi tudo bem?" → resposta direta

Hop 5 = 0ms. Total fica em ~2.2s. Caso ideal.

B

1 tool call (read_file)

"o que tem em README.md?"

Hop 5 = ~10ms exec + ~2s re-prompt. Total ~5s.

C

Chain de 3 tools (search → fetch → summarize)

"pesquise X e me resuma"

3 round-trips de LLM + 3 execs. Total 10-18s. Stream feedback intermediário ou usuário abandona.

💡 Paralelismo é grátis

Se o LLM retorna 3 tool_calls independentes, o executor roda os 3 em asyncio.gather(). 3x600ms vira 600ms. Mas só funciona se as tools forem independentes — não passe output de uma como input de outra na mesma rodada.

6

✅ Hop 6 — Resposta → Telegram + memory write (~80ms)

Resposta finalizada: chama sendMessage do Telegram, persiste turno na memória (INSERT em SQLite + trigger FTS5), e dispara reflexão assíncrona se o threshold de salvamento for atingido. Memory write deve ser fire-and-forget — usuário não espera por ele.

Gateway        Telegram API        SQLite          ReflectionQueue
   │                │                  │                  │
   │── sendMessage ▶│  t=45ms          │                  │
   │                │  (chat_id=99,    │                  │
   │                │   text="...")    │                  │
   │◀── 200 ────────│                  │                  │
   │                                   │                  │
   │── async write ───────────────────▶│  t=3ms (INSERT)  │
   │   (não bloqueia)                  │  + FTS5 trigger  │
   │                                   │  t=8ms total     │
   │── enqueue reflect ──────────────────────────────────▶│
   │   (se turnos % 10 == 0)                              │  background

📊 Latências P50/P95/P99

  • sendMessage: P50 45ms · P95 180ms · P99 1.2s (rate-limit ou rede)
  • INSERT + FTS5 trigger: P50 8ms · P95 25ms (com WAL ativado)
  • Reflection enqueue: <1ms (só append em fila in-memory)
  • Reflection real (async): 3-8s — não conta no trace do usuário

✓ O que FAZER

  • Streaming via editMessageText a cada N tokens (UX excelente)
  • SQLite com PRAGMA journal_mode=WAL (leituras concorrentes)
  • Memory write em asyncio.create_task — não await
  • Retry exponencial em sendMessage com 429/502 (max 3x)

✗ O que NÃO fazer

  • Awaitar o write — usuário sente +10ms grátis
  • Editar a mensagem 100x/s — Telegram dá 429 (limit ~1x/s por chat)
  • Rodar reflection inline — bloqueia próximo turno
  • Persistir conteúdo bruto com PII sem hash/redação

💡 Trace de produção

Ative HERMES_TRACE=1 ou exporte para OpenTelemetry — cada hop vira um span. Veja docs e repo. Em produção, P99 acima de 10s = investigar (cold model, FTS5 fragmentado, ou tool lenta).

Resumo do Módulo

6 hops, budget P50 ~2.2s

H1 webhook 30ms · H2 router 5ms · H3 contexto 70-250ms · H4 LLM 1.5-5s · H5 tools variável · H6 envio 80ms.

H3 e H4 dominam a latência

Otimize cacheando user_repr e escolhendo modelo certo por intenção. Não micro-otimize H1/H2/H6.

Tool calls multiplicam o tempo

Cada re-prompt = +1 Hop 4. Paralelize tools independentes; faça streaming feedback.

Memory write é fire-and-forget

Nunca await. Reflection roda em background quando o threshold dispara.

Cold-start é o vilão de P99

Providers serverless com baixo throughput → 5-15s extras. Warm-up cron ou pague pelo tier dedicado.

Próximo módulo:

Módulo 2.4 — 🛡️ Modelo de segurança e privacidade: onde dados vivem, o que vaza, prompt injection, exfiltration via tool e supply chain de skills.