mozak.tech Engenharia de IA Corporativa 92%

Parte V — Projetando Sistemas Inteligentes para Produção

5.2 — Arquiteturas de Agente Único: Reativo, com Memória e com Ferramentas (Single-agent)

Objetivo da Aula

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

Distinguir Reactive, Memory-Enhanced e Tool-Using agent architectures

Implementar MemoryEnhancedAgent.chat() com histórico FIFO e memória longa

Implementar toolUsingAgent() com agent loop completo (tool_use → result)

Completar os 2 TODOs do starter TypeScript

Decidir quando cada arquitetura é suficiente vs overengineering

Por que isso importa

Single-agent é a arquitetura mais simples que funciona para 80% dos casos de uso. Antes de construir sistemas multi-agent complexos, entenda o limite de cada nível: Reactive tem zero estado (cada chamada é nova), Memory-Enhanced lembra o contexto, Tool-Using executa ações no mundo real.

Conceitos Fundamentais

Arquitetura 1: Reactive (já implementado)

Input → LLM → Output

(sem estado, sem memória, sem ferramentas)

Quando usar: perguntas pontuais, transformações de texto, classificações.

TODO 1: Memory-Enhanced Agent

class MemoryEnhancedAgent {

  private memoriaCorta: { role: string; content: string }[] = []

  private memoriaLonga: string[] = []

  private readonly maxCurta = 10

  private contadorMensagens = 0



  async chat(mensagem: string): Promise<string> {

    // 1. Recuperar fatos relevantes da memória longa via keyword search

    const palavras = mensagem.toLowerCase().split(' ')

    const fatosRelevantes = this.memoriaLonga.filter(fato =>

      palavras.some(p => p.length > 3 && fato.toLowerCase().includes(p))

    )

    

    // 2. Montar system prompt com contexto de memória longa

    const systemPrompt = fatosRelevantes.length > 0

      ? `Você é um assistente com memória. Fatos relevantes da conversa anterior:\n${fatosRelevantes.join('\n')}`

      : 'Você é um assistente prestativo.'

    

    // 3. Chamar Claude com histórico + nova mensagem

    this.memoriaCorta.push({ role: 'user', content: mensagem })

    

    const response = await client.messages.create({

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

      max_tokens: 500,

      system: systemPrompt,

      messages: this.memoriaCorta as Anthropic.MessageParam[],

    })

    

    const assistente = response.content[0].type === 'text' ? response.content[0].text : ''

    this.memoriaCorta.push({ role: 'assistant', content: assistente })

    

    // 4. FIFO: remover mais antigas quando ultrapassa maxCurta

    while (this.memoriaCorta.length > this.maxCurta) {

      this.memoriaCorta.splice(0, 2)  // remover par user+assistant

    }

    

    // 5. A cada 5 mensagens: resumir e mover para memória longa

    this.contadorMensagens++

    if (this.contadorMensagens % 5 === 0) {

      const resumoResponse = await client.messages.create({

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

        max_tokens: 200,

        messages: [{

          role: 'user',

          content: `Resuma em 2-3 fatos importantes desta conversa:\n${this.memoriaCorta.map(m => `${m.role}: ${m.content}`).join('\n')}`,

        }],

      })

      const resumo = resumoResponse.content[0].type === 'text' ? resumoResponse.content[0].text : ''

      this.memoriaLonga.push(resumo)

    }

    

    return assistente

  }

}

TODO 2: Tool-Using Agent (Agent Loop)

async function toolUsingAgent(tarefa: string): Promise<string> {

  const mensagens: Anthropic.MessageParam[] = [{ role: 'user', content: tarefa }]

  

  while (true) {

    const response = await client.messages.create({

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

      max_tokens: 1000,

      tools: TOOL_USING_TOOLS,

      messages: mensagens,

    })

    

    // Adicionar resposta do assistente ao histórico

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

    

    // 1. Verificar se acabou

    if (response.stop_reason === 'end_turn') {

      const texto = response.content.find(b => b.type === 'text')

      return texto?.type === 'text' ? texto.text : 'Sem resposta'

    }

    

    // 2. Processar tool_use blocks

    if (response.stop_reason === 'tool_use') {

      const resultados: Anthropic.ToolResultBlockParam[] = []

      

      for (const bloco of response.content) {

        if (bloco.type === 'tool_use') {

          // 3. Executar tool

          const resultado = executarToolLocal(bloco.name, bloco.input as Record<string, string>)

          resultados.push({

            type: 'tool_result',

            tool_use_id: bloco.id,

            content: resultado,

          })

        }

      }

      

      // 4. Retornar resultados para o LLM continuar

      mensagens.push({ role: 'user', content: resultados })

    }

  }

}

Aprofundamento Técnico

Por que FIFO e não LRU para memória curta?

FIFO (First In, First Out) remove as mensagens mais antigas. LRU removeria as menos usadas. Em conversas, o contexto recente é mais relevante — o que foi dito 5 mensagens atrás sobre o assunto X é mais útil do que o que foi dito 20 mensagens atrás sobre outro assunto Y.

Exceção: se o usuário retornar a um tópico antigo, memória longa captura esse contexto via keyword search.

Agent Loop: Invariantes

// Sempre inclua toda a conversa, incluindo tool results:

mensagens.push({ role: 'assistant', content: response.content })  // inclui tool_use blocks

mensagens.push({ role: 'user', content: resultados })              // inclui tool_results



// NÃO faça:

mensagens.push({ role: 'user', content: resultado_string })  // perde estrutura



// Proteção contra loop infinito:

let iteracoes = 0

while (iteracoes++ < 20) {  // max 20 iterações

Exemplos Anotados

Exemplo 1: Memory-Enhanced em ação

Turn 1: "Me chamo João"

  → Salva na memoriaCorta: [{user: "Me chamo João"}, {assistant: "Olá João!"}]



Turn 5: (dispara resumo)

  → memoriaLonga: ["Usuário se chama João, perguntou sobre X, Y, Z"]



Turn 10: "Você sabe meu nome?"

  → keyword search em memoriaLonga encontra "João"

  → system prompt: "Fatos: Usuário se chama João..."

  → resposta correta mesmo com memoriaCorta limpa

Exemplo 2: Tool loop com calcular + formatar

Tarefa: "Quanto é 125 × 37 + 892? Formate em markdown"



Loop 1:

  Claude → tool_use: calcular("125 * 37 + 892")

  Tool → "Resultado: 5517"



Loop 2:

  Claude → tool_use: formatar_resultado("5517", "markdown")

  Tool → "[MARKDOWN] 5517"



Loop 3:

  Claude → end_turn: "O resultado é **5517** em formato markdown."

Padrões e Armadilhas

Padrões

Padrão 1: Sempre incluir response.content inteiro no histórico

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

// Não apenas o texto, pois response.content contém tool_use blocks também

Padrão 2: Keyword search na memória longa antes de chamar LLM

const relevantes = this.memoriaLonga.filter(fato =>

  palavrasChave.some(p => p.length > 3 && fato.toLowerCase().includes(p))

)

Armadilhas

⚠️ Armadilha 1: tool_use_id incorreto

// ERRADO: criar ID novo

{ type: 'tool_result', tool_use_id: 'meu-id', content: resultado }



// CORRETO: usar bloco.id do Claude

{ type: 'tool_result', tool_use_id: bloco.id, content: resultado }

⚠️ Armadilha 2: Splice remove em pares

// memoriaCorta sempre tem pares user+assistant

while (this.memoriaCorta.length > this.maxCurta) {

  this.memoriaCorta.splice(0, 2)  // remover 2, não 1

}

⚠️ Armadilha 3: Loop infinito sem proteção

let iteracoes = 0

while (iteracoes++ < 20) { ... }
⚗ 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

TODO 1 — MemoryEnhancedAgent.chat(): keyword search em memoriaLonga, build system prompt, call Claude, atualizar memoriaCorta com FIFO, resumir a cada 5 mensagens para memoriaLonga.

TODO 2 — toolUsingAgent(): agent loop com while(true), verifique stop_reason === 'end_turn' vs 'tool_use', execute executarToolLocal, retorne tool_result com bloco.id.

Agora você está pronto para o lab.