Vercel AI SDK — Módulo de Text Generation
Un módulo autocontenido para generación de texto usando el Vercel AI SDK con Google (Gemini) como proveedor. Cubre generación de texto, texto con streaming, prompts de sistema, temperatura/configuración y salida estructurada.
Dependencias
Este módulo usa:
ai— el core del Vercel AI SDK (generateText,streamText,generateObject,APICallError)@ai-sdk/google— el adaptador del proveedor Google (Gemini)zod— se usa para definir los schemas de la salida estructurada (peer dependency degenerateObject)
Estructura de archivos
types.ts Shared types, default model config, rate limit config
service.ts Internal logic: calls the AI SDK, normalizes errors — do not modify
actions.ts Server Actions — import into Server Components
client.ts Client-side streaming helper — import into Client Components
stream/handler.ts Internal streaming logic used by the route handler
stream/route.ts Route handler — copy to app/api/ai-sdk/stream/route.ts
.env.local Environment variable for the Google API key
Configuración
-
Copiá
stream/route.tsa tu proyecto en:app/api/ai-sdk/stream/route.ts(sus imports de
../servicey../typesvan a resolverse entonces a losservice.ts/types.tsde este módulo; dejástream/handler.tsal lado, o ajustá los imports relativos si ubicás los archivos de otra forma) -
Agregá tu API key de Google AI en
.env.local. Podés conseguir una en https://aistudio.google.com/apikey -
Importá desde
actions.ts(servidor) oclient.ts(cliente) como se muestra abajo.
Uso
Generar texto (servidor)
import { generateText } from "@/lib/ai/actions";
const result = await generateText({
prompt: "Write a haiku about the ocean",
system: "You are a concise, poetic assistant.",
temperature: 0.8,
});
if (result.success) {
console.log(result.data.text);
} else {
// result.error: { code, message, retryAfter? }
console.error(result.error.message);
}
Salida estructurada (servidor)
import { generateStructuredOutput } from "@/lib/ai/actions";
import { z } from "zod";
const schema = z.object({
title: z.string(),
tags: z.array(z.string()),
});
const result = await generateStructuredOutput({
prompt: "Summarize this article into a title and tags: ...",
schema,
});
if (result.success) {
console.log(result.data.object.title, result.data.object.tags);
}
Texto con streaming (cliente)
"use client";
import { useState } from "react";
import { streamText } from "@/lib/ai/client";
export function ChatDemo() {
const [text, setText] = useState("");
const [error, setError] = useState<string | null>(null);
async function handleSubmit() {
setText("");
setError(null);
await streamText(
{ prompt: "Explain quantum computing simply" },
{
onChunk: (chunk) => setText((prev) => prev + chunk),
onError: (err) => setError(err.message),
onFinish: () => console.log("done"),
}
);
}
return (
<div>
<button onClick={handleSubmit}>Generate</button>
{error ? <p role="alert">{error}</p> : <p>{text}</p>}
</div>
);
}
Manejo de errores
Cada función devuelve (o entrega, en el caso del streaming) un error con formato normalizado en lugar de un error crudo del SDK/proveedor:
{
code: "RATE_LIMITED" | "INVALID_REQUEST" | "PROVIDER_ERROR" | "UNKNOWN",
message: string, // safe to show in the UI
retryAfter?: number, // seconds, only present for RATE_LIMITED
}
Usá code para ramificar el comportamiento de la UI (por ejemplo, deshabilitar el botón de envío y mostrar una cuenta regresiva cuando está presente retryAfter) y message como texto para el usuario.
Rate limiting
Dos capas, ambas devueltas como errores RATE_LIMITED:
- Guard local (
types.ts→RATE_LIMIT_CONFIG): un limitador simple de ventana deslizante en memoria (por defecto: 20 requests / minuto por instancia del servidor) que frena los requests antes de que lleguen al proveedor. Es solo por instancia: no se comparte entre varias instancias del servidor. - Rate limit del proveedor: si la propia API de Google devuelve un 429, se captura y se normaliza de la misma manera, incluyendo
retryAftercuando el proveedor envía un headerretry-after.
Limitación conocida
Los errores del proveedor a mitad del streaming se incrustan como el último fragmento del texto transmitido (el protocolo de text-stream del AI SDK no soporta señalización de errores fuera de banda). client.ts lo detecta con una verificación de mejor esfuerzo sobre el último chunk recibido. Es una simplificación: si esto llega a ser un problema, pasar al protocolo data-stream del AI SDK permitiría partes de error estructuradas, a costa de más complejidad de parseo en el cliente.