들어가며
Vercel 은 제가 주로 사용하는 리액트 프레임워크인 Next.js 를 만든 개발사인데요. AWS 처럼 클라우드 서비스를 제공하고 있는데 정적 사이트 빌드, 배포 및 서버리스 환경에 좀 더 특화되어 있는 것 같습니다. 간만에 접속해보니 2년 전에 빌드, 배포 테스트를 하느라고 만들어 놓은 블로그 앱이 하나 있네요. 그때도 블로그를 만드려고 시도했었나 봅니다. :)
오늘은 Vercel 에서 작년에 소개한 Vercel AI SDK 를 이용해서 OpenAI ChatGPT 를 흉내내보려고 합니다.
다음의 링크를 참고하였습니다. Introducing the Vercel AI SDK
ChatGPT 만들기
시작하기 전에 먼저 인터넷에서 찾은 chatgpt svg 파일을 다운받아 public 폴더로 옮겨놓습니다.
Next.js Project Setup & Install Package
# Next.js 14.0.4 으로 설치됨 (Typescript, Tailwindcss, `src/` directory, App Router 사용)
npx create-next-app@latest vercel-ai
# Vercel AI
npm i ai openai-edge
# OPENAI_API_KEY 설정용
npm i dotenv
# Icon
npm i @fortawesome/react-fontawesome @fortawesome/free-solid-svg-icons
API 키 설정 (.env)
OPENAI_API_KEY=sk-xxxxxxxxxxxx
dotenv 설정 (next.config.js)
require("dotenv").config();
... 이하 ...
TailwindCSS 설정 (src/app/globals.css)
@tailwind base;
@tailwind components;
@tailwind utilities;
기본 레이아웃 (src/app/layout.tsx)
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "Vercel AI",
description: "Chat GPT with Vercel AI",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ko">
<body>{children}</body>
</html>
);
}
API (src/app/api/chat/route.ts)
import { OpenAIStream, StreamingTextResponse } from "ai";
import { Configuration, OpenAIApi } from "openai-edge";
const config = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(config);
export const runtime = "edge";
export async function POST(req: Request) {
const { messages } = await req.json();
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
stream: true,
messages,
});
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
}
화면 (src/app/page.tsx)
"use client";
import { useChat } from "ai/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faUser } from "@fortawesome/free-solid-svg-icons";
import { useEffect, useRef } from "react";
export default function Chat() {
const contentRef = useRef<HTMLDivElement>(null);
const { messages, input, handleInputChange, handleSubmit } = useChat();
useEffect(() => {
if (contentRef.current) {
contentRef.current.scrollTop = contentRef.current.scrollHeight;
}
}, [messages]);
return (
<div className="flex flex-col justify-center items-center h-lvh">
<div className="flex justify-center items-center font-medium text-xl
fixed top-0 bg-white h-16 w-full border-b-[1px] border-b-black">
ChatGPT{" "}
<span className="text-gray-500 ml-2 text-lg">with Vercel AI</span>
</div>
<div className="w-full h-full flex flex-col justify-center items-center">
<div
ref={contentRef}
className="w-full h-full overflow-hidden overflow-y-scroll px-24 pt-20 pb-10"
>
{messages.map((m) => (
<div key={m.id} className="text-lg">
<div className="flex mt-5">
<div className="h-full mr-2 w-12 pt-[3px] min-w-12">
{m.role === "assistant" ? (
<img src="/gpt.svg" width="25" alt="gpt" />
) : (
<FontAwesomeIcon
icon={faUser}
size="lg"
className="pl-0.5"
/>
)}
</div>
<div>
{m.role === "assistant" ? (
<strong>ChatGPT</strong>
) : (
<strong>You</strong>
)}
<div>{m.content}</div>
</div>
</div>
</div>
))}
</div>
<form
onSubmit={handleSubmit}
className="flex items-center w-full h-14 bg-white p-2"
>
<input
value={input}
onChange={handleInputChange}
className="w-full h-10 border border-black focus:outline-none p-2 rounded-md"
placeholder="Message ChatGPT..."
/>
</form>
</div>
</div>
);
}
결과물
마무리
기본적인 기능만 구현하긴 했지만 별 수고로운 작업없이 useChat hook 하나로 정리가 되어 편리하네요. 응답을 데이터베이스에 저장하도록 콜백도 제공하고 Langchain, Anthropic, Hugging Face 도 지원하니 잘 활용해 보면 손쉽게 괜찮은 결과물이 나올 수 있을 것 같습니다.