Back

Criando Agentes de IA do Zero: Function Calling e Padrões que Funcionam

O mundo da IA mudou completamente. Saímos da era "pergunta e resposta" pra era dos agentes de IA — sistemas que pensam sozinhos, planejam e executam tarefas complexas.

Já tentou criar um? Provavelmente descobriu que não é só "chamar a API e pronto". 😅 Tem que gerenciar loops, definir ferramentas, tratar erros... a lista não acaba.

Nesse artigo vamos construir um agente de IA do zero — um que dá pra usar em produção de verdade.

Chatbot vs Agente: qual a diferença?

Chatbot responde. Agente age.

A chave é o loop agêntico:

  1. Observar — O que tão pedindo?
  2. Pensar — Como resolvo?
  3. Agir — Chamar ferramentas/APIs
  4. Avaliar — Deu certo? Próximo passo?
  5. Repetir — Até terminar

Isso permite tarefas tipo "pesquisa concorrentes e faz relatório" ou "acha voo barato e reserva". Não só gera texto — orquestra ações.

┌─────────────────────────────────────────────────────┐
│                    LOOP AGÊNTICO                    │
├─────────────────────────────────────────────────────┤
│    ┌──────────┐                                     │
│    │ OBSERVAR │ ◄── Solicitação / Ambiente          │
│    └────┬─────┘                                     │
│         ▼                                           │
│    ┌──────────┐                                     │
│    │  PENSAR  │ ◄── Raciocínio do LLM              │
│    └────┬─────┘                                     │
│         ▼                                           │
│    ┌──────────┐                                     │
│    │   AGIR   │ ◄── Execução de Ferramentas        │
│    └────┬─────┘                                     │
│         ▼                                           │
│    ┌──────────┐                                     │
│    │ REFLETIR │ ◄── Avaliar Resultado              │
│    └────┬─────┘                                     │
│         ▼                                           │
│    ┌────────────────┐                               │
│    │ Tarefa Pronta? │── Sim ──► Retornar Resultado │
│    └──────┬─────────┘                               │
│           │ Não                                     │
│           └─────────► Voltar a OBSERVAR             │
└─────────────────────────────────────────────────────┘

Entendendo Function Calling: A Base do Uso de Ferramentas

Function calling é o mecanismo que permite aos LLMs solicitar a execução de funções externas. Em vez de gerar texto, o modelo produz uma solicitação estruturada para chamar uma função específica com argumentos específicos.

Como Function Calling Funciona

Quando você envia uma solicitação a um LLM com definições de funções, o modelo pode escolher:

  1. Responder com texto (comportamento de chatbot tradicional)
  2. Solicitar uma chamada de função (comportamento agêntico)

Aqui está a anatomia de uma definição de função para OpenAI:

const tools = [ { type: "function", function: { name: "get_weather", description: "Obtém o clima atual para um local específico", parameters: { type: "object", properties: { location: { type: "string", description: "Nome da cidade, ex., 'São Paulo, Brasil'" }, unit: { type: "string", enum: ["celsius", "fahrenheit"], description: "Unidade de temperatura" } }, required: ["location"] } } } ];

Os campos description são críticos—são o que o LLM usa para decidir quando e como chamar a função.

Function Calling com Claude (Anthropic)

Claude usa uma estrutura similar, mas ligeiramente diferente:

const tools = [ { name: "get_weather", description: "Obtém o clima atual para um local específico", input_schema: { type: "object", properties: { location: { type: "string", description: "Nome da cidade, ex., 'São Paulo, Brasil'" }, unit: { type: "string", enum: ["celsius", "fahrenheit"] } }, required: ["location"] } } ];

A diferença chave é input_schema em vez de parameters, mas o conceito é idêntico.

Construindo Seu Primeiro Agente: Implementação Completa

Vamos construir um agente prático que pode pesquisar na web, ler documentos e realizar cálculos.

Passo 1: Defina Sua Interface de Ferramentas

interface Tool { name: string; description: string; parameters: { type: "object"; properties: Record<string, { type: string; description: string; enum?: string[]; }>; required: string[]; }; execute: (args: Record<string, unknown>) => Promise<string>; }

Passo 2: Implemente Suas Ferramentas

const webSearchTool: Tool = { name: "web_search", description: "Pesquisa na web por informações atuais. Use para eventos recentes ou dados não presentes no treinamento.", parameters: { type: "object", properties: { query: { type: "string", description: "A consulta de pesquisa" } }, required: ["query"] }, execute: async (args) => { const { query } = args as { query: string }; const response = await fetch(`https://api.search.example/search?q=${encodeURIComponent(query)}`); const data = await response.json(); return JSON.stringify(data.results.slice(0, 5)); } }; const calculatorTool: Tool = { name: "calculator", description: "Realiza cálculos matemáticos. Suporta aritmética básica e porcentagens.", parameters: { type: "object", properties: { expression: { type: "string", description: "Expressão matemática, ex., '(100 * 1.15) + 50'" } }, required: ["expression"] }, execute: async (args) => { const { expression } = args as { expression: string }; try { const result = Function(`"use strict"; return (${expression})`)(); return `Resultado: ${result}`; } catch { return `Erro: Expressão inválida`; } } }; const readUrlTool: Tool = { name: "read_url", description: "Lê e extrai conteúdo de texto de uma URL.", parameters: { type: "object", properties: { url: { type: "string", description: "A URL para ler" } }, required: ["url"] }, execute: async (args) => { const { url } = args as { url: string }; try { const response = await fetch(url); const html = await response.text(); const text = html.replace(/<[^>]*>/g, ' ').slice(0, 5000); return text; } catch (error) { return `Erro ao ler URL: ${error}`; } } };

Passo 3: O Loop Agêntico

Aqui é onde a mágica acontece:

import OpenAI from 'openai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); interface Message { role: "system" | "user" | "assistant" | "tool"; content: string; tool_calls?: Array<{ id: string; type: "function"; function: { name: string; arguments: string }; }>; tool_call_id?: string; } async function runAgent( userMessage: string, tools: Tool[], maxIterations: number = 10 ): Promise<string> { const toolDefinitions = tools.map(tool => ({ type: "function" as const, function: { name: tool.name, description: tool.description, parameters: tool.parameters } })); const messages: Message[] = [ { role: "system", content: `Você é um assistente de IA útil com acesso a ferramentas. Use as ferramentas quando necessário para responder perguntas com precisão. Sempre explique seu raciocínio antes de usar uma ferramenta.` }, { role: "user", content: userMessage } ]; for (let i = 0; i < maxIterations; i++) { const response = await openai.chat.completions.create({ model: "gpt-4-turbo-preview", messages: messages, tools: toolDefinitions, tool_choice: "auto" }); const assistantMessage = response.choices[0].message; messages.push(assistantMessage as Message); if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) { return assistantMessage.content || "Não consegui gerar uma resposta."; } for (const toolCall of assistantMessage.tool_calls) { const tool = tools.find(t => t.name === toolCall.function.name); if (!tool) { messages.push({ role: "tool", tool_call_id: toolCall.id, content: `Erro: Ferramenta '${toolCall.function.name}' não encontrada` }); continue; } try { const args = JSON.parse(toolCall.function.arguments); const result = await tool.execute(args); messages.push({ role: "tool", tool_call_id: toolCall.id, content: result }); } catch (error) { messages.push({ role: "tool", tool_call_id: toolCall.id, content: `Erro ao executar ferramenta: ${error}` }); } } } return "Número máximo de iterações atingido."; }

Passo 4: Usando Seu Agente

const tools = [webSearchTool, calculatorTool, readUrlTool]; const result = await runAgent( "Qual é a população atual de Tóquio e que porcentagem isso representa da população total do Japão?", tools ); console.log(result);

O Padrão ReAct: Raciocínio e Ação

O padrão ReAct (Reasoning + Acting) torna os agentes mais confiáveis. O agente raciocina explicitamente sobre cada passo:

const REACT_SYSTEM_PROMPT = `Você é um assistente que segue o padrão ReAct. Para cada passo: 1. THOUGHT: Raciocine sobre a situação atual 2. ACTION: Escolha uma ação (usar ferramenta ou resposta final) 3. OBSERVATION: Analise o resultado Formato: THOUGHT: [Seu raciocínio] ACTION: [nome_ferramenta com argumentos OU "FINAL_ANSWER"] OBSERVATION: [Resultado da ferramenta] Quando estiver pronto para responder: THOUGHT: Tenho informação suficiente. ACTION: FINAL_ANSWER [Sua resposta completa]`;

Tratamento de Erros e Padrões de Confiabilidade

1. Retentativas com Backoff Exponencial

async function executeWithRetry<T>( fn: () => Promise<T>, maxRetries: number = 3, baseDelay: number = 1000 ): Promise<T> { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (error) { if (attempt === maxRetries - 1) throw error; const delay = baseDelay * Math.pow(2, attempt); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error("Retentativa falhou"); }

2. Timeout na Execução

async function executeWithTimeout<T>( fn: () => Promise<T>, timeoutMs: number = 30000 ): Promise<T> { return Promise.race([ fn(), new Promise<T>((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs) ) ]); }

3. Degradação Elegante

async function safeToolExecute(tool: Tool, args: Record<string, unknown>): Promise<string> { try { return await executeWithTimeout( () => executeWithRetry(() => tool.execute(args)), 30000 ); } catch (error) { return `Ferramenta "${tool.name}" falhou: ${error}. Tente outra abordagem.`; } }

Padrão Avançado: Orquestração Multi-Ferramenta

interface ToolResult { toolName: string; input: Record<string, unknown>; output: string; timestamp: Date; } class AgentContext { private history: ToolResult[] = []; private cache: Map<string, string> = new Map(); async executeToolWithCache(tool: Tool, args: Record<string, unknown>): Promise<string> { const cacheKey = `${tool.name}:${JSON.stringify(args)}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey)!; } const result = await tool.execute(args); this.cache.set(cacheKey, result); this.history.push({ toolName: tool.name, input: args, output: result, timestamp: new Date() }); return result; } getHistory(): ToolResult[] { return [...this.history]; } }

Execução Paralela de Ferramentas

Quando as ferramentas são independentes, execute-as em paralelo:

async function executeToolsInParallel( toolCalls: Array<{ tool: Tool; args: Record<string, unknown> }> ): Promise<Map<string, string>> { const results = new Map<string, string>(); const promises = toolCalls.map(async ({ tool, args }) => { const result = await safeToolExecute(tool, args); results.set(tool.name, result); }); await Promise.all(promises); return results; }

Monitoramento e Observabilidade

interface AgentTrace { traceId: string; startTime: Date; endTime?: Date; steps: Array<{ type: "thought" | "action" | "observation"; content: string; timestamp: Date; }>; status: "running" | "completed" | "failed"; } function createTracer() { const trace: AgentTrace = { traceId: crypto.randomUUID(), startTime: new Date(), steps: [], status: "running" }; return { trace, addStep: (type: "thought" | "action" | "observation", content: string) => { trace.steps.push({ type, content, timestamp: new Date() }); }, complete: () => { trace.endTime = new Date(); trace.status = "completed"; } }; }

Erros Comuns e Como Evitá-los

1. Loops Infinitos

function detectLoop(messages: Message[], threshold: number = 3): boolean { const recentToolCalls = messages .filter(m => m.tool_calls) .slice(-threshold) .map(m => JSON.stringify(m.tool_calls)); return new Set(recentToolCalls).size === 1 && recentToolCalls.length === threshold; }

2. Estouro de Contexto

async function summarizeHistory(messages: Message[]): Promise<Message[]> { if (messages.length <= 10) return messages; const toSummarize = messages.slice(1, -5); const summary = await openai.chat.completions.create({ model: "gpt-4-turbo-preview", messages: [ { role: "system", content: "Resuma esta conversa de forma concisa." }, { role: "user", content: JSON.stringify(toSummarize) } ] }); return [ messages[0], { role: "assistant", content: `Contexto anterior: ${summary.choices[0].message.content}` }, ...messages.slice(-5) ]; }

3. Descrições Ambíguas

// ❌ Ruim description: "API de clima" // ✅ Bom description: "Obtém condições climáticas de uma cidade. Use quando perguntarem sobre clima ou temperatura. Funciona apenas para cidades, não países."

Considerações de Segurança

1. Validação de Entrada

function validateToolArgs(schema: Tool['parameters'], args: Record<string, unknown>): boolean { for (const required of schema.required) { if (!(required in args)) return false; } for (const [key, value] of Object.entries(args)) { const propSchema = schema.properties[key]; if (propSchema?.enum && !propSchema.enum.includes(value as string)) { return false; } } return true; }

2. Execução em Sandbox

import ivm from 'isolated-vm'; async function safeEval(code: string): Promise<string> { const isolate = new ivm.Isolate({ memoryLimit: 128 }); const context = await isolate.createContext(); try { const result = await context.eval(code, { timeout: 5000 }); return String(result); } finally { isolate.dispose(); } }

3. Rate Limiting

class RateLimiter { private requests: number[] = []; constructor(private limit: number, private windowMs: number) {} async check(): Promise<boolean> { const now = Date.now(); this.requests = this.requests.filter(t => t > now - this.windowMs); if (this.requests.length >= this.limit) return false; this.requests.push(now); return true; } }

Conclusão

Agentes de IA = dominar o loop: observar, pensar, agir, avaliar, repetir.

  1. Começa simples — Function calling básico primeiro
  2. Descrições claras — LLM só usa o que entende
  3. Erros vão rolar — Se prepara
  4. Logs são vida — Sem eles, debugging é inferno
  5. Coloca limites — Nada de loop infinito
  6. Segurança primeiro — Não confia cegamente no LLM

Próximo nível: multi-agente, memória persistente, agentes que aprendem... tudo em cima disso. Bora codar! 🚀

AILLMAgentsFunction CallingTypeScriptOpenAIClaudeAgentic AI

Explore ferramentas relacionadas

Experimente estas ferramentas gratuitas do Pockit