기술은 나의 도구

Vercel AI SDK 로 나만의 ChatGPT 만들기

들어가며

Vercel 은 제가 주로 사용하는 리액트 프레임워크인 Next.js 를 만든 개발사인데요. AWS 처럼 클라우드 서비스를 제공하고 있는데 정적 사이트 빌드, 배포 및 서버리스 환경에 좀 더 특화되어 있는 것 같습니다. 간만에 접속해보니 2년 전에 빌드, 배포 테스트를 하느라고 만들어 놓은 블로그 앱이 하나 있네요. 그때도 블로그를 만드려고 시도했었나 봅니다. :)

오늘은 Vercel 에서 작년에 소개한 Vercel AI SDK 를 이용해서 OpenAI ChatGPT 를 흉내내보려고 합니다.

다음의 링크를 참고하였습니다. Introducing the Vercel AI SDK

ChatGPT 만들기

시작하기 전에 먼저 인터넷에서 찾은 chatgpt svg 파일을 다운받아 public 폴더로 옮겨놓습니다. gpt

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>
  );
}

결과물

vercel-ai-gpt

마무리

기본적인 기능만 구현하긴 했지만 별 수고로운 작업없이 useChat hook 하나로 정리가 되어 편리하네요. 응답을 데이터베이스에 저장하도록 콜백도 제공하고 Langchain, Anthropic, Hugging Face 도 지원하니 잘 활용해 보면 손쉽게 괜찮은 결과물이 나올 수 있을 것 같습니다.

  • #Vercel
  • #AI
  • #ChatGPT