Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK

힘센캥거루
2025년 1월 3일(수정됨)
17
nextjs

Últimamente me he esforzado en decorar el blog y crear funcionalidades.

Al volver a mirar mi código me di cuenta de que no recordaba en absoluto cómo lo había hecho.

Fue un día en el que volví a darme cuenta de la importancia de los TIL.

Así que vuelvo a dejar constancia aquí.

Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-1

1. Documentación oficial de la API de ChatGPT

Primero, los ajustes básicos se pueden revisar en la documentación oficial.

El orden es el siguiente.

  1. Registrar la API key como variable de entorno. En el entorno de Nextjs, crea un archivo con la extensión .env.local en la carpeta raíz y añade OPENAI_API_KEY="api_key_here".

  2. Instalar la librería de openai con npm install openai.

  3. Escribir el código.

El código que propone la documentación oficial es el siguiente.

import OpenAI from "openai";
const openai = new OpenAI();

const completion = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [
    { role: "system", content: "You are a helpful assistant." },
    {
      role: "user",
      content: "Write a haiku about recursion in programming.",
    },
  ],
});

console.log(completion.choices[0].message);

Si pruebas esto en Nextjs con yarn dev, verás que funciona bien.

Así que parece que bastaría con crear una api route y ya estaría, pero el problema no es tan sencillo.

2. Probarlo una vez

En las primeras fases de desarrollo, es más rápido ignorar por completo el estilo y empezar por la funcionalidad.

Vamos a crear simplemente un formulario y hacer una ventana de chat sencilla usando FormData y fetch.

"use client";

import { useRef, useState } from "react";

export default function Page() {
  const [value, setValue] = useState("");
  const messages = useRef<HTMLDivElement>(null);
  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setValue("");
    const me = document.createElement("p");
    me.textContent = value;
    messages.current?.appendChild(me);
    const formData = new FormData();
    formData.append("content", value);
    const res = await fetch("/api/test", {
      method: "POST",
      body: formData,
    });
    const resJson = await res.json();
    const p = document.createElement("p");
    p.textContent = resJson.content;
    messages.current?.appendChild(p);
  }
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={value}
          onChange={(e) => setValue(e.currentTarget.value)}
        />
        <button type="submit"></button>
      </form>
      <div ref={messages} className="border-2">
        <p>대화창</p>
      </div>
    </div>
  );
}

Si haces esto, verás un input sencillo y una ventana de conversación.

Cuando escribes contenido en el input, se envía formdata a '/api/test' usando fetch y se procesa la respuesta.

Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-2

Ahora vamos a escribir el código del lado de la API.

import { NextResponse } from "next/server";
import OpenAI from "openai";
const openai = new OpenAI();

export async function POST(req: Request) {
  const formData = await req.formData();
  const completion = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      { role: "system", content: "You are a helpful assistant." },
      {
        role: "user",
        content: formData.get("content") as string,
      },
    ],
  });
  console.log(completion.choices[0].message);
  return NextResponse.json(completion.choices[0].message);
}

Se parsea el mensaje del request y se envía la respuesta usando la librería de openai, y se recibe.

Luego simplemente se devuelve esta respuesta como json y listo.

Ahora probemos conversar.

Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-3

Si haces la prueba, verás que cuando la respuesta es larga hay que esperar mucho tiempo.

Aquí es donde aparece el problema.

3. Problemas

El problema es que nuestra paciencia no es tan fuerte.

En la web, si la respuesta no llega rápido, uno se cansa con facilidad.

Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-4

Lo que hace que en ChatGPT los datos se envíen como si se estuviera tecleando es que la API responde y muestra la salida en unidades fragmentadas llamadas chunks.

Busqué con esmero la forma de implementar esto.

  1. Implementar chat en tiempo real usando sockets

  2. Devolver el objeto de respuesta usando el stream de openai

  3. Implementarlo usando el AI SDK de Vercel

De entre estas, la tercera parecía la más sencilla.

4. Probar el AI SDK de Vercel

Cuando no sabes algo, la búsqueda y la documentación oficial son lo mejor.

Si entras en la dirección de arriba, verás que ya existen componentes preparados para Nextjs.

Entonces, si los colocamos en nuestro proyecto sin cambios, podremos hacer streaming.

Primero instalemos el AI SDK.

yarn add ai @ai-sdk/openai zod

Aquí, zod es una herramienta para validar la entrada.

Luego configuramos la ruta en la carpeta del siguiente directorio.

import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai("gpt-4o"),
    messages,
  });

  return result.toDataStreamResponse();
}

En este código, maxDuration es el tiempo máximo de respuesta.

Si no termina de responder en 30 segundos, se detiene el streaming.

Y se devuelve al cliente un objeto para hacer streaming de datos.

Ahora veamos el componente page.

"use client";

import { useChat } from "ai/react";

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: "/api/aisdk",
  });
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map((m) => (
        <div key={m.id} className="whitespace-pre-wrap">
          {m.role === "user" ? "User: " : "AI: "}
          {m.content}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={handleInputChange}
        />
      </form>
    </div>
  );
}

Lo que antes habría que manejar de forma compleja con useState o handlers de eventos, se resuelve todo con un único useChat.

Fíjate bien en dónde y cómo se usan las variables desestructuradas de useChat.

Dentro de useChat se pueden pasar varios parámetros como objeto.

En este caso, hemos puesto la dirección de la API.

Ahora ejecutémoslo.

Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-5

Realmente, con solo dos copias y pegas hemos implementado de forma sencilla un chatgpt con streaming.

En la documentación oficial también hay un proceso para crear una IA relacionada con el clima, así que puedes consultarla.

5. useChat y streamText

Los parámetros de cada función también se pueden encontrar, como era de esperar, en la documentación oficial.

Solo voy a escribir algunos de los que he usado.

1. useChat

Dentro de useChat se pueden introducir mensajes iniciales.

Si lo haces, el mensaje aparecerá en cuanto entres en la página.

const { messages, input, handleInputChange, handleSubmit } = useChat({
  api: "/api/aisdk",
  initialMessages: [
    {
      id: "first-message",
      role: "assistant",
      content: "하고싶은 말 입력해봐",
    },
  ],
});
Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-6

2. streamText

En streamText se pueden definir las funciones necesarias para la respuesta deseada.

Si le dices a chatgpt: "힘센캥거루를 메롱으로 변환해줄래?" (¿Puedes convertir ‘힘센캥거루’ en ‘메롱’?), no responderá correctamente.

Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-7

En estos casos, puedes definir una función llamada 메롱 y colocarla dentro de streamText.

import { openai } from "@ai-sdk/openai";
import { streamText, tool } from "ai";
import { z } from "zod";

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai("gpt-4o"),
    messages,
    tools: {
      reverse: tool({
        description: "(이름)를 메롱으로 변환하면?",
        parameters: z.object({
          person: z.string().describe("이름"),
        }),
        execute: async ({ person }) => {
          const newPerson = person.split("").reverse().join("");
          return {
            newPerson,
          };
        },
      }),
    },
  });
  return result.toDataStreamResponse();
}

Dentro de la función tool, description es la descripción de la función, parameters encuentra en la conversación el contenido que corresponde al nombre y lo pasa como parámetro, y este parámetro se procesa en execute y luego se devuelve.

Si escribes el código así y haces la pregunta, te devolverá la cadena que has introducido invertida.

Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-8

También se puede definir la longitud máxima de la respuesta.

Si especificas maxTokens como abajo, la longitud de la respuesta se vuelve muy corta.

 const result = streamText({
    ...
    maxTokens:10,
    ...
  });
Implementación de una ventana de chat de ChatGPT usando Nextjs y AI SDK-9

6. Conclusión

Pensaba que crear un servicio usando chatgpt sería tremendamente difícil, pero no lo ha sido tanto.

Como hay muchas buenas librerías, si uno se lo propone puede crear lo que quiera.

Quiero crear pronto un chatbot educativo y lanzarlo como servicio.

댓글을 불러오는 중...