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çõesExemplos 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 limpaExemplo 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émPadrã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) { ... }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.