O Problema de Integrar LLMs do Zero
Construir uma interface de IA do zero envolve lidar com uma série de complexidades: streaming de texto token a token, serialização de histórico de mensagens, tratamento de erros de rede, tool calls, cancelamento de requisições — tudo isso antes mesmo de pensar na UI.
O Vercel AI SDK resolve esse problema com um conjunto de abstrações que tornam a integração de LLMs em aplicações Next.js simples e tipada.
npm install ai @ai-sdk/openai
# ou para usar Claude
npm install ai @ai-sdk/anthropicConfigurando o Primeiro Endpoint de Streaming
O SDK expõe um helper streamText que funciona nativamente com Route Handlers do Next.js:
// app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
system: 'Você é um assistente técnico especializado em desenvolvimento web.',
});
return result.toDataStreamResponse();
}Três linhas de lógica real. O SDK cuida de serializar o stream no formato esperado pelo cliente.
Consumindo o Stream no Cliente
O hook useChat sincroniza automaticamente o estado da conversa com o endpoint:
'use client';
import { useChat } from 'ai/react';
export function ChatInterface() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
});
return (
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={message.role === 'user' ? 'text-right' : 'text-left'}
>
<span className="inline-block px-4 py-2 rounded-lg bg-white/5 border border-white/10">
{message.content}
</span>
</div>
))}
{isLoading && (
<div className="text-purple-400 animate-pulse">Gerando resposta...</div>
)}
</div>
<form onSubmit={handleSubmit} className="p-4 border-t border-white/10">
<input
value={input}
onChange={handleInputChange}
placeholder="Faça uma pergunta..."
className="w-full bg-white/5 rounded-lg px-4 py-2"
/>
</form>
</div>
);
}O hook gerencia automaticamente o histórico de mensagens, o estado de loading e o streaming token a token na interface.
Tool Calls: Dando Superpoderes ao Modelo
A feature mais poderosa do SDK é a integração de tools — funções que o modelo pode chamar para buscar dados externos, executar cálculos ou interagir com APIs:
import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
buscarPrecoAcao: tool({
description: 'Busca o preço atual de uma ação na bolsa',
parameters: z.object({
ticker: z.string().describe('O código da ação, ex: PETR4, VALE3'),
}),
execute: async ({ ticker }) => {
// Chama sua API de finanças aqui
const preco = await fetchStockPrice(ticker);
return { ticker, preco, timestamp: new Date().toISOString() };
},
}),
calcularJurosCompostos: tool({
description: 'Calcula juros compostos dado capital, taxa e período',
parameters: z.object({
capital: z.number(),
taxaMensal: z.number().describe('Taxa em decimal, ex: 0.01 para 1%'),
meses: z.number(),
}),
execute: async ({ capital, taxaMensal, meses }) => {
const montante = capital * Math.pow(1 + taxaMensal, meses);
return { montante: montante.toFixed(2), juros: (montante - capital).toFixed(2) };
},
}),
},
maxSteps: 5, // permite loops de raciocínio multi-step
});
return result.toDataStreamResponse();
}O modelo decide autonomamente quando chamar cada tool com base no contexto da conversa — sem instrução explícita do usuário.
Streaming Estruturado com generateObject
Além de texto livre, o SDK permite gerar objetos JSON estruturados com validação via Zod, o que é ideal para geração de conteúdo:
import { generateObject } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
const PostSchema = z.object({
titulo: z.string(),
resumo: z.string().max(160),
tags: z.array(z.string()).max(5),
secoes: z.array(z.object({
titulo: z.string(),
conteudo: z.string(),
})),
nivelTecnico: z.enum(['iniciante', 'intermediario', 'avancado']),
});
export async function gerarRascunhoPost(tema: string) {
const { object } = await generateObject({
model: anthropic('claude-opus-4-6'),
schema: PostSchema,
prompt: `Crie a estrutura de um post técnico sobre: ${tema}`,
});
// `object` é completamente tipado via TypeScript
return object;
}O retorno é inferido automaticamente a partir do schema — zero type assertions.
Gerenciando Contexto Longo com trimMessages
LLMs têm limite de contexto. Para conversas longas, é necessário comprimir o histórico sem perder informação crítica:
import { streamText, trimMessages } from 'ai';
export async function POST(req: Request) {
const { messages } = await req.json();
// Mantém as últimas N mensagens + sempre inclui a system message
const trimmedMessages = trimMessages(messages, {
maxTokens: 8000,
tokenCounter: (msg) => Math.ceil(msg.content.length / 4), // estimativa simples
strategy: 'last', // mantém as mais recentes
});
const result = streamText({
model: openai('gpt-4o'),
messages: trimmedMessages,
});
return result.toDataStreamResponse();
}Persistindo Conversas no Banco
Para aplicações reais, as mensagens precisam ser persistidas. O SDK fornece hooks de ciclo de vida para isso:
import { streamText } from 'ai';
import { db } from '@/lib/db';
export async function POST(req: Request) {
const { messages, sessionId } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
onFinish: async ({ text, usage }) => {
// Chamado após o stream completar
await db.message.createMany({
data: [
{
sessionId,
role: 'user',
content: messages.at(-1)?.content ?? '',
},
{
sessionId,
role: 'assistant',
content: text,
tokensUsed: usage.totalTokens,
},
],
});
},
});
return result.toDataStreamResponse();
}Vale a Pena?
O Vercel AI SDK abstrai o que realmente não precisa ser reinventado: o protocolo de streaming, a serialização de mensagens, o loop de tool calls. O resultado é código focado em lógica de negócio, não em infraestrutura de IA.
Pontos de atenção:
- Custo de tokens cresce rapidamente em conversas longas — implemente
trimMessagesdesde o início - Tool calls são poderosas mas adiciona latência por round-trip ao modelo — use com moderação
- Rate limits dos provedores exigem retry logic — o SDK não faz isso automaticamente
Para aplicações que precisam ir além do chat simples, vale explorar o novo modelo de AI Agents com generateText em loops, onde o modelo toma decisões sequenciais até atingir um objetivo. Esse é o próximo passo rumo a sistemas verdadeiramente autônomos.
A linha entre "app com IA" e "app que é IA" está ficando cada vez mais tênue. O SDK só acelera isso.