- Artigos
- Desenvolvimento
- Criando um agente de IA do zero

A maioria dos tutoriais mostra como chamar uma API de LLM e para por aí. Isso é um chatbot. Um agente de IA é diferente: ele persegue um objetivo em várias etapas, decide quando usar ferramentas, lida com falhas e encerra quando a tarefa termina. Construir um do zero é uma das melhores formas de entender o que os frameworks escondem — e de saber quando você realmente precisa deles.
Este post percorre um agente mínimo, porém pensado para produção, em TypeScript. As mesmas ideias valem em rotas server do Laravel ou do Nuxt; o que importa é a camada de orquestração, não o runtime.
O que transforma algo em um agente?
Um modelo mental útil tem quatro partes:
- Objetivo — uma tarefa clara, não uma conversa aberta.
- Ferramentas — funções que o agente pode chamar: requisições HTTP, consultas ao banco, leitura de arquivos, envio de e-mail.
- Memória — histórico da conversa e qualquer estado estruturado persistido entre etapas.
- Loop de controle — o código que pergunta repetidamente ao modelo o que fazer a seguir até o objetivo ser atingido ou um limite ser alcançado.
Sem o loop e as ferramentas, você tem autocomplete com passos extras. Com eles, você tem software capaz de agir no mundo em nome do usuário.
Comece com um caso de uso restrito
Resista à tentação de construir um assistente genérico no primeiro dia. Escolha um fluxo que seus usuários já fazem manualmente. Bons primeiros agentes incluem: resumir tickets de suporte e rascunhar respostas, pesquisar um tema e produzir um briefing, ou atualizar um registro no CRM a partir de anotações não estruturadas.
Escreva os critérios de sucesso em linguagem simples antes de codificar. Exemplo: Dado um e-mail de cliente, produzir um rascunho de resposta e registrar a categoria do ticket. Essa frase vira a âncora do system prompt e o seu teste de aceitação.
Arquitetura em poucas linhas
Em tempo de execução, seu agente funciona assim:
- A entrada do usuário chega ao orquestrador.
- O orquestrador monta o prompt com instruções de sistema, definições de ferramentas e memória.
- O LLM devolve uma resposta final ou uma chamada de ferramenta.
- Se for chamada de ferramenta, seu código executa, anexa o resultado à memória e repete o loop.
- Se for resposta final, você retorna e, opcionalmente, roda validadores.
Mantenha o orquestrador determinístico. O modelo decide; seu código impõe limites, permissões e novas tentativas.
Defina ferramentas como funções tipadas
Ferramentas são o contrato entre raciocínio probabilístico e código determinístico. Defina cada uma com nome, descrição, JSON Schema de parâmetros e implementação em TypeScript.
interface Tool {
name: string;
description: string;
parameters: Record<string, unknown>; // JSON Schema
execute: (args: unknown) => Promise<unknown>;
}
const searchTickets: Tool = {
name: "search_tickets",
description: "Busca tickets de suporte por palavra-chave e status",
parameters: {
type: "object",
properties: {
query: { type: "string" },
status: { type: "string", enum: ["open", "closed"] }
},
required: ["query"]
},
execute: async ({ query, status }) => {
// Chame sua API Laravel ou camada de DB aqui
return await ticketService.search(query, status);
}
};As descrições importam mais que os nomes dos parâmetros. O modelo lê linguagem natural para escolher ferramentas. Seja explícito sobre restrições: "Retorna no máximo 10 resultados" ou "Exige perfil admin — lança erro se não autorizado."
O loop do agente
O coração do agente é um while loop com proteções:
async function runAgent(goal: string, tools: Tool[]) {
const messages: Message[] = [
{ role: "system", content: buildSystemPrompt(tools) },
{ role: "user", content: goal }
];
for (let step = 0; step < MAX_STEPS; step++) {
const response = await llm.chat({ messages, tools });
if (response.toolCalls?.length) {
for (const call of response.toolCalls) {
const tool = tools.find(t => t.name === call.name);
if (!tool) {
messages.push(toolError(call.name));
continue;
}
try {
const result = await tool.execute(call.arguments);
messages.push({ role: "tool", name: call.name, content: JSON.stringify(result) });
} catch (err) {
messages.push({ role: "tool", name: call.name, content: String(err) });
}
}
continue;
}
return response.content; // Resposta final
}
throw new Error("Agent exceeded max steps");
}Três detalhes separam demos de brinquedo de algo que você pode colocar em produção:
- Máximo de passos — evita loops infinitos e custo fora de controle.
- Erros de ferramenta como mensagens — permitem que o modelo se recupere em vez de derrubar a requisição.
- Logging estruturado — registre cada passo, chamada de ferramenta, latência e uso de tokens.
Construa o system prompt com cuidado
Seu system prompt deve responder: quem é o agente, o que ele não pode fazer, como usar ferramentas e quando parar. Mantenha curto. Prompts longos diluem a atenção e aumentam o custo.
Inclua exemplos de bom uso de ferramentas quando o fluxo não for óbvio. Para um agente de suporte, mostre uma sequência ideal: buscar tickets → ler detalhes → rascunhar resposta → categorizar. Exemplos few-shot dentro do prompt costumam superar lógicas de roteamento complexas no início.
Memória: menos é mais
Jogar histórico inteiro em toda requisição fica caro rápido. Use memória em camadas:
- Memória de trabalho — mensagens apenas da tarefa atual.
- Resumo de sessão — comprima turnos antigos em um parágrafo quando o contexto crescer.
- Armazenamento externo — Redis, Postgres ou banco vetorial para fatos que o agente precise lembrar depois.
Em agentes estilo RAG, a recuperação é só mais uma ferramenta. Deixe o modelo decidir quando buscar, em vez de injetar chunks cegamente.
Valide antes de confiar na saída
Nunca repasse a saída do agente direto para ações destrutivas sem verificação. Adicione uma camada de validação:
- Validação de schema com Zod ou JSON Schema para saídas estruturadas.
- Human-in-the-loop para envios, exclusões e pagamentos.
- Detecção de duplicidade — já respondemos este ticket?
Agentes são bons em rascunhar; sua aplicação deve continuar sendo a autoridade sobre efeitos colaterais.
Observabilidade desde o primeiro dia
Quando algo der errado, você vai perguntar: qual ferramenta falhou, o que o modelo viu e por que loopou cinco vezes? Guarde traces por execução: objetivo de entrada, cada requisição/resposta ao LLM, entradas/saídas de ferramentas (redija segredos), contagem de passos e resultado final.
OpenTelemetry ou uma tabela simples de logs JSON já bastam no começo. Se você não consegue reproduzir uma execução falha, não consegue melhorar o prompt nem as ferramentas.
Testar agentes é diferente
Teste unitariamente suas ferramentas como sempre. Para o agente em si, use testes estilo eval: objetivos fixos com sequências de ferramentas ou padrões de saída esperados. Rode no CI com LLM mockado que devolve chamadas canned, mais um job noturno pequeno contra o modelo real para pegar regressões.
Testes instáveis sinalizam prompts ou ferramentas ambíguos. Corrija o contrato, não o limiar do teste.
Quando usar um framework
Construa do zero quando precisar de controle fino, superfície pequena ou integração profunda com um app Laravel/Nuxt existente. Adote LangGraph, helpers de agente do Vercel AI SDK ou similares quando precisar de execução paralela de ferramentas, grafos com ramificações complexas ou quando velocidade do time importa mais que dependências mínimas.
Começar enxuto ensina o que esses frameworks automatizam. Você vai escolhê-los de propósito, não por padrão.
Um primeiro marco realista
Entregue um fluxo com agente único, domínio único, três a cinco ferramentas, no máximo dez passos, logging completo e aprovação humana em ações externas. Isso já é valioso em produção — e pequeno o suficiente para raciocinar sobre.
A partir daí, adicione planejamento multi-etapa, sub-agentes ou memória mais rica só quando métricas mostrarem um gargalo claro. Os melhores agentes são propositalmente "chatos": loops previsíveis, ferramentas explícitas e condições claras de parada. Essa é a base sobre a qual todo o resto se constrói.