Ú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í.

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.
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".Instalar la librería de openai con
npm install openai.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.

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.

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.

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.
Implementar chat en tiempo real usando sockets
Devolver el objeto de respuesta usando el stream de openai
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 zodAquí, 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.

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: "하고싶은 말 입력해봐",
},
],
});
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.

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.

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,
...
});
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.
댓글을 불러오는 중...