Pular para o conteúdo principal

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;
}
CampoTipoObrigatórioDescrição
messagesMessage[]Histórico de mensagens
chatIdstringID 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ódigoErroDescrição
401UNAUTHORIZEDSessão inválida ou expirada
400BAD_REQUESTFormato de mensagens inválido
429TOO_MANY_REQUESTSRate limit excedido
500INTERNAL_ERRORErro 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
}