API de Chat
A API de Chat permite enviar mensagens e receber respostas do assistente IA com suporte a streaming.
Base URL
/api/chat
Endpoints
Enviar Mensagem (Streaming)
POST /api/chat
Cookie: better-auth.session_token=...
Content-Type: application/json
{
"messages": [
{
"role": "user",
"content": "Quais são os sintomas da HAP?"
}
],
"chatId": "chat_123"
}
Headers de Resposta:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Stream de Resposta:
data: {"type":"text","text":"Os principais"}
data: {"type":"text","text":" sintomas da"}
data: {"type":"text","text":" Hipertensão Arterial"}
data: {"type":"text","text":" Pulmonar incluem:"}
...
data: {"type":"done"}
Request Body
interface ChatRequest {
messages: Message[];
chatId?: string;
}
interface Message {
role: "user" | "assistant" | "system";
content: string;
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
messages | Message[] | ✅ | Histórico de mensagens |
chatId | string | ❌ | ID do chat existente |
Response Stream
O response é um stream de Server-Sent Events (SSE):
interface StreamEvent {
type: "text" | "done" | "error";
text?: string;
error?: string;
}
Implementação do Servidor
// src/app/api/chat/route.ts
import { streamText } from "ai";
import { azure } from "@ai-sdk/azure";
import { auth } from "@/server/auth";
export async function POST(request: Request) {
// Verificar autenticação
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
const { messages, chatId } = await request.json();
// Buscar documentos relevantes (RAG)
const lastMessage = messages[messages.length - 1];
const relevantDocs = await searchDocuments(lastMessage.content);
// Construir system prompt
const systemPrompt = buildSystemPrompt(relevantDocs);
// Gerar resposta com streaming
const result = await streamText({
model: azure("gpt-4o"),
messages,
system: systemPrompt,
});
// Guardar mensagens no histórico
if (chatId) {
await saveMessages(chatId, messages, result);
}
return result.toDataStreamResponse();
}
Uso no Cliente
Com fetch e ReadableStream
async function sendMessage(message: string, chatId?: string) {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({
messages: [{ role: "user", content: message }],
chatId,
}),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Processar chunk
console.log(chunk);
}
}
Com Vercel AI SDK
import { useChat } from "@ai-sdk/react";
function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: "/api/chat",
});
return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.role}: {m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
disabled={isLoading}
/>
<button type="submit">Enviar</button>
</form>
</div>
);
}
RAG (Retrieval-Augmented Generation)
Busca de Documentos
async function searchDocuments(query: string): Promise<Document[]> {
// Gerar embedding da query
const queryEmbedding = await embeddings.embedQuery(query);
// Buscar chunks similares
const results = await db
.select({
content: documentChunks.content,
documentTitle: documents.title,
similarity: cosineDistance(documentChunks.embedding, queryEmbedding),
})
.from(documentChunks)
.innerJoin(documents, eq(documentChunks.documentId, documents.id))
.where(eq(documents.isActive, true))
.orderBy(cosineDistance(documentChunks.embedding, queryEmbedding))
.limit(5);
return results;
}
System Prompt
function buildSystemPrompt(docs: Document[]): string {
const context = docs
.map((d) => `[${d.documentTitle}]\n${d.content}`)
.join("\n\n---\n\n");
return `Você é o APAH Assistant, um assistente especializado em Hipertensão Arterial Pulmonar (HAP).
DIRETRIZES:
- Forneça informações precisas baseadas em evidência científica
- Sempre recomende consultar um profissional de saúde
- Use linguagem clara e acessível
- Cite as fontes quando disponíveis nos documentos
CONTEXTO DOS DOCUMENTOS:
${context}
Se a informação não estiver disponível nos documentos, indique claramente que não tem essa informação específica.`;
}
Erros
| Código | Erro | Descrição |
|---|---|---|
| 401 | UNAUTHORIZED | Sessão inválida ou expirada |
| 400 | BAD_REQUEST | Formato de mensagens inválido |
| 429 | TOO_MANY_REQUESTS | Rate limit excedido |
| 500 | INTERNAL_ERROR | Erro ao processar mensagem |
Rate Limiting
10 requests/minute por utilizador
Após exceder o limite:
{
"error": "TOO_MANY_REQUESTS",
"message": "Limite de mensagens excedido. Tente novamente em 60 segundos.",
"retryAfter": 60
}