mozak.tech Engenharia de IA Corporativa 13%

Parte I — Operações e Infraestrutura Potencializadas por IA

1.4 — Resolução de Incidentes com Agentes de Raciocínio em Produção (Incident Management)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Construir um agente ReAct que diagnostica incidentes P1 correlacionando logs, métricas e traces

Implementar buscar_traces() que retorna correlação realista entre serviços

Implementar sugerir_fix() que usa Claude para gerar plano de remediação estruturado

Analisar o incidente do lab: pool de conexões PostgreSQL → deadlock → cascata para api-gateway

Completar os 2 TODOs do starter: correlação de traces e fix automático

Por que isso importa

Em sistemas distribuídos, um incidente raramente tem causa única e visível. O incidente do lab é típico: o api-gateway retorna 503, mas o problema real está no PostgreSQL — um deadlock causado por slow queries sem índice que esgotou o pool de conexões do checkout-service, que por sua vez fez o api-gateway abrir o circuit breaker.

Um humano leva 20-45 minutos para correlacionar isso. O agente ReAct faz em segundos, porque pode chamar buscar_logs, obter_metricas, buscar_traces e verificar_dependencias em sequência, integrando todas as evidências em um diagnóstico coerente.

Conceitos Fundamentais

O Incidente do Lab

INCIDENTE = {

    'id': 'INC-2024-001',

    'titulo': 'API Gateway retornando 503 em 40% das requisições',

    'severidade': 'P1',

    'servicos_afetados': ['api-gateway', 'checkout-service', 'payment-service'],

}

A cadeia de causa raiz:

PostgreSQL (max_connections=100, slow_queries=5, deadlock)

    ↓ esgotou pool

checkout-service (db_connections=10/10, waiting=25, p99=12s)

    ↓ timeout + error rate 65%

api-gateway (circuit breaker OPEN, error_rate=40%, 503s)

O payment-service está OK (error_rate=2%) — confirmando que o problema não é infra geral.

TODO 1: buscar_traces(trace_id, servicos)

Distributed tracing (Jaeger, Tempo, X-Ray) correlaciona chamadas entre serviços por trace_id. O mock retorna uma correlação realista:

def buscar_traces(trace_id: str = 'recentes', servicos: list = None) -> str:

    # Mock de resposta Jaeger-like

    if servicos and 'checkout-service' in servicos:

        return '''Traces correlacionados:



trace_id: abc123 (P99)

  api-gateway        →  checkout-service:  8500ms [TIMEOUT]

  checkout-service   →  postgres:          8200ms [SLOW QUERY + DEADLOCK]

  

trace_id: def456 (típico)

  api-gateway        →  checkout-service:  250ms  [OK]

  checkout-service   →  postgres:           80ms  [OK]



ANÁLISE: latência 34x acima da baseline. Gargalo: postgres.

Operação lenta: SELECT * FROM orders WHERE user_id=? (sem índice em user_id)'''

    

    return '''Nenhum trace anômalo para os serviços especificados.

Todos os spans dentro do SLO (<500ms p99).'''

Na integração real com Jaeger:

import requests



def buscar_traces_jaeger(servico: str, limite_ms: int = 5000) -> str:

    response = requests.get(

        'http://jaeger:16686/api/traces',

        params={'service': servico, 'minDuration': f'{limite_ms}ms', 'limit': 10}

    )

    traces = response.json()['data']

    return formatar_traces(traces)

TODO 2: sugerir_fix(causa_raiz, servicos_afetados, urgencia)

A função já existe no starter e usa o LLM para gerar o plano. O stub não precisa de implementação — o que precisamos é completar o trace do mock com dados mais ricos. A função usa Claude diretamente:

def sugerir_fix(causa_raiz: str, servicos_afetados: list = None, urgencia: str = 'imediata') -> str:

    response = client.messages.create(

        model='claude-haiku-4-5-20251001',

        max_tokens=600,

        messages=[{

            'role': 'user',

            'content': f'''Gere um plano de remediação ESTRUTURADO para este incidente:



Causa raiz: {causa_raiz}

Serviços afetados: {', '.join(servicos_afetados or [])}

Urgência: {urgencia}



Formato obrigatório:

## Ações Imediatas (próximos 15 min)

- [lista numerada]



## Fix de Curto Prazo (próximas 4 horas)

- [lista numerada]



## Prevenção de Longo Prazo (próximo sprint)

- [lista numerada]



## Métricas de Verificação

- [como confirmar que o incidente foi resolvido]''',

        }],

    )

    return response.content[0].text

Aprofundamento Técnico

ReAct em Diagnóstico de Incidentes

O padrão ReAct (Reasoning + Acting) é especialmente eficaz em troubleshooting porque o agente pode adaptar sua investigação baseado no que encontra:

Thought: "O api-gateway está com 503. Vou começar pelos logs."

Action: buscar_logs(servico='api-gateway', nivel='ERROR')

Observation: "circuit breaker OPEN for checkout-service (50% error rate)"



Thought: "Circuit breaker aberto → problema vem do checkout-service."

Action: buscar_logs(servico='checkout-service', nivel='ERROR')

Observation: "database connection pool exhausted; deadlock detected on table orders"



Thought: "Pool de DB esgotado + deadlock. Vou verificar as métricas do postgres."

Action: obter_metricas(servico='postgres')

Observation: {"active_connections": 100, "max_connections": 100, "long_queries": 5}



Thought: "100/100 conexões. Limite atingido. Vou buscar traces para confirmar."

Action: buscar_traces(trace_id='recentes', servicos=['checkout-service', 'postgres'])

Observation: "SELECT * FROM orders WHERE user_id=? 8200ms — sem índice"



Thought: "Causa raiz: query sem índice → deadlock → pool esgotado → cascata."

Action: sugerir_fix(causa_raiz='Query sem índice em orders.user_id causou deadlock...')

O agente não vai em linha reta — ele usa cada observação para decidir a próxima ação.

Correlação: A Diferença entre Sintoma e Causa Raiz

Sintoma observado

Indicador real

api-gateway 503s

circuit breaker aberto

circuit breaker OPEN

checkout-service error_rate > 50%

checkout-service erros

pool DB esgotado

pool DB esgotado

slow queries + deadlock

slow queries

query sem índice em user_id

A causa raiz é a query sem índice — não o api-gateway, não o checkout-service. Sem correlação, você restart o pod errado.

Circuit Breaker como Sinal Diagnóstico

O log do api-gateway:

14:32:02 WARN  circuit breaker OPEN for checkout-service (50% error rate)

Isso é crucial: o circuit breaker OPEN significa que o api-gateway parou de tentar chamar o checkout-service após 50% de erros. É um sinal de proteção — e de que o problema upstream é severo o suficiente para acionar o breaker.

Exemplos Anotados

Exemplo 1: Loop do Agente Diagnóstico Completo

import anthropic, json, os



client = anthropic.Anthropic()



TOOLS_DIAG = [

    {'name': 'buscar_logs', ...},

    {'name': 'obter_metricas', ...},

    {'name': 'buscar_traces', ...},

    {'name': 'verificar_dependencias', ...},

    {'name': 'sugerir_fix', ...},

]



def agente_diagnostico(incidente: dict) -> str:

    descricao = f"""

INCIDENTE {incidente['id']} — {incidente['titulo']}

Severidade: {incidente['severidade']}

Serviços afetados: {', '.join(incidente['servicos_afetados'])}



Diagnostique a causa raiz e sugira o plano de remediação.

"""

    mensagens = [{'role': 'user', 'content': descricao}]

    

    for _ in range(10):  # máximo 10 iterações

        response = client.messages.create(

            model='claude-haiku-4-5-20251001',

            max_tokens=1500,

            tools=TOOLS_DIAG,

            # instrução ReAct explícita

            system='Você é um SRE sênior. Use o padrão ReAct: Thought (analise) → Action (tool) → Observation (resultado). Identifique a causa raiz ANTES de sugerir fix.',

            messages=mensagens,

        )

        

        if response.stop_reason == 'end_turn':

            return next((b.text for b in response.content if hasattr(b, 'text')), 'Diagnóstico concluído.')

        

        tool_blocks = [b for b in response.content if b.type == 'tool_use']

        if not tool_blocks:

            break

        

        mensagens.append({'role': 'assistant', 'content': response.content})

        

        results = []

        for block in tool_blocks:

            resultado = executar_tool_diag(block.name, block.input)

            results.append({

                'type': 'tool_result',

                'tool_use_id': block.id,

                'content': resultado,

            })

        mensagens.append({'role': 'user', 'content': results})

    

    return 'Diagnóstico interrompido: máximo de iterações atingido'

Exemplo 2: TODO 1 — buscar_traces com dados ricos

def buscar_traces(trace_id: str = 'recentes', servicos: list = None) -> str:

    # Dados mock de traces Jaeger-like

    traces_db = [

        {

            'trace_id': 'abc123',

            'spans': [

                {'service': 'api-gateway', 'operation': 'POST /checkout', 'duration_ms': 8500, 'status': 'error'},

                {'service': 'checkout-service', 'operation': 'createOrder', 'duration_ms': 8200, 'status': 'error'},

                {'service': 'postgres', 'operation': 'SELECT orders', 'duration_ms': 8100, 'status': 'slow'},

            ]

        }

    ]

    

    resultado = []

    for trace in traces_db:

        resultado.append(f"trace_id: {trace['trace_id']}")

        for span in trace['spans']:

            status_icon = '✓' if span['status'] == 'ok' else '⚠' if span['status'] == 'slow' else '✗'

            resultado.append(f"  {status_icon} {span['service']} → {span['operation']}: {span['duration_ms']}ms [{span['status']}]")

    

    return '\n'.join(resultado) or 'Nenhum trace encontrado'

Padrões e Armadilhas

Padrões

Padrão 1: Começar pelos logs do serviço que disparou o alerta, não pela causa raiz O alerta vem do api-gateway → agente começa por api-gateway → descobre circuit breaker → vai para checkout-service. Não assuma a causa raiz antes de investigar.

Padrão 2: verificar_dependencias antes de sugerir_fix

# Sequência correta

buscar_logs → obter_metricas → buscar_traces → verificar_dependencias → sugerir_fix

verificar_dependencias confirma que o postgres está DEGRADADO — sem isso, o fix pode ser impreciso.

Padrão 3: sugerir_fix com causa_raiz específica

# VAGO — gera plano genérico

sugerir_fix(causa_raiz='banco de dados com problemas')



# ESPECÍFICO — gera plano acionável

sugerir_fix(

    causa_raiz='Query SELECT * FROM orders WHERE user_id=? sem índice causa slow query 8s → deadlock → pool esgotado (100/100 conexões)',

    servicos_afetados=['checkout-service', 'postgres'],

    urgencia='imediata'

)

Armadilhas

⚠️ Armadilha 1: Parar no primeiro serviço com erros (api-gateway) O api-gateway tem erros mas não é a causa raiz — é a vítima. Sempre siga a cadeia de dependências até encontrar o serviço sem dependências problemáticas upstream.

⚠️ Armadilha 2: Confiança excessiva no diagnóstico do LLM

# Sempre verificar empiricamente com métricas

diagnostico_llm = "causa raiz: api-gateway sobrecarregado"

metricas = obter_metricas('api-gateway')

# → error_rate=40%, rps=1200 (normal), p99=8500ms

# Métricas confirmam: api-gateway é vítima, não causa

⚠️ Armadilha 3: sugerir_fix chamado muito cedo (antes de verificar_dependencias) Se o agente chamar sugerir_fix antes de descobrir que o postgres está em 100/100 conexões, o fix vai sugerir “reiniciar o checkout-service” em vez de “adicionar índice em orders.user_id + aumentar max_connections”.

⚗ Laboratório prático — mozak.tech
Se não for realizar o laboratório, pule para o próximo capítulo.

Ponte para o Lab

2 TODOs no starter.py:

TODO 1 — buscar_traces(trace_id, servicos) (linha ~123): Substitua o placeholder por uma resposta mock estruturada:

def buscar_traces(trace_id: str = 'recentes', servicos: list = None) -> str:

    return '''Traces distribuídos correlacionados:



trace_id: abc123 [P99 — anomalia]

  api-gateway  →  checkout-service: 8500ms [TIMEOUT]

  checkout-service  →  postgres: 8200ms [SLOW QUERY]

  Operação: SELECT * FROM orders WHERE user_id=? (sem índice)



trace_id: def456 [baseline normal]

  api-gateway  →  checkout-service: 250ms [OK]

  checkout-service  →  postgres: 80ms [OK]



Diagnóstico: latência 34x acima da baseline. Gargalo identificado: postgres (query sem índice)'''

TODO 2 — a função sugerir_fix já usa LLM real — verifique que o prompt inclui o formato estruturado com seções “Ações Imediatas”, “Fix de Curto Prazo” e “Prevenção de Longo Prazo”.

Rode com python starter.py e observe o agente correlacionar logs → métricas → traces → dependências → fix.

Agora você está pronto para o lab.