@dariomvg/create
vercel-ai-sdktext-generationgeminizodintegracion

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 de generateObject)

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

  1. Copiá stream/route.ts a tu proyecto en:

    app/api/ai-sdk/stream/route.ts
    

    (sus imports de ../service y ../types van a resolverse entonces a los service.ts / types.ts de este módulo; dejá stream/handler.ts al lado, o ajustá los imports relativos si ubicás los archivos de otra forma)

  2. Agregá tu API key de Google AI en .env.local. Podés conseguir una en https://aistudio.google.com/apikey

  3. Importá desde actions.ts (servidor) o client.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 retryAfter cuando el proveedor envía un header retry-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.