Openai api 와 Nextjs로 AI 단톡방 만들기 - 넘블 (Numble) 딥다이브 후기

Intro

얼마전 넘블에서 진행하는 사이드 프로젝트 Nextjs 로 AI 단톡방 만들어보기 를 진행했다. 넘블측에서는 각 프로젝트들을 딥다이브라는 명칭을 사용하는 듯 하다.

갑자기?

일단 이런 사이드 프로젝트를 진행하는 이유는 여러가지가 있겠지만 보통 성장 욕구를 채우기 위함일 것 이다. 나의 경우는 그것과 별개로 심심해서(?) 였다. 회사에서 하는 일은 그저 일이니까하는 느낌이라 재미보다는 의무나 책임감이 더 강하게 느껴졌다. 재미가 없어지면 좋아하던 일도 점점 하기싫어지는게 당연하기에 올해 초 부터 이런 상황을 해결 할 방법을 찾았는데 그중 하나가 바로 사이드 프로젝트였다. 늘 하고싶고 해야지 생각은 하지만 막상 하려면 뭘 할지 부터 어떤 서비스를 만들지 등등 정작 개발 시작단계 까지 가기도 전에 막혀버려 흐지부지 되는게 대부분 이었다. 그런 점에서 넘블이라는 서비스는 나에게 굉장히 매력적이었다. 일단 딥다이브 목록을 보고 재밌어보이는 걸 골라 해당 파트에서 제시하는 구현 항목들을 구현해 나가면 되기 때문에, 써보고 싶었던 프레임워크라던가 기술들의 스킬업에 집중 할 수 있었다.


그래서 뭐 했는데?

img01

결과물 배포 사이트 링크

개별적으로 api 키를 발급해서 사용해야한다.

요즘 핫한 Chat-GPT의 개발사인 Open-ai 의 api 를 이용하여 채팅 형식의 단톡방을 구현하면 되는 기능이다.

요구사항으로는 간단하게 API 키 검증, 채팅방 CRUD, 채팅 기능, AI 끼리의 채팅 구현 등이 있었다. 그중에서 채팅기능이 메인이기 때문에 해당 기능에 가장 많은 시간을 들였고, 가장 고전했던 부분이기도 하다.

참고로 이번 딥다이브는 총 21일의 시간이 주어졌었다. 그중 30시간 정도 개발 진행한듯?

코드는 github 레포에 있다.

사용한 기술 스택

  • Nextjs v13
  • @emotion/styled v11
  • typescript
  • openai v3.2.1

가장 많이 고민한 부분 및 아쉬운 부분

OpenAI API를 사용해 채팅을 구현하는건 어렵지 않았지만 문제는 AI끼리도 대화를 해야한다는 것. 처음에는 그냥 API client 여러개 생생해서 이렇게 저렇게(?) 하면 되겠지 간단히 생각했지만, 보기보다 쉽게 풀리지 않았다. 백엔드 서버를 따로 두지않고 Nextjs에서 제공하는 기본 기능으로만 구현 해야하기 때문에 실제 DB 서비스를 띄워서 데이터를 관리하기는 힘들었다. 대체재로 Indexed-DB를 사용했다. 처음 사용해보는 기술이라 처음에 삽질을 좀 했지만 어느 DB가 그렇듯 기본 CRUD정도는 방식이 얼추 비슷하다.

img02

이런식으로 브라우저 개발자 도구의 Application 탭에 들어가면 Indexed-DB를 확인 할 수 있다.

필요한 DB 스키마를 생성하고 데이터 조회에 필요한 index-key를 생성해 사용하면 된다. 나는 간단하게 채팅방 데이터를 관리한 ChatRoom 과 채팅 데이터를 관리할 Chat 스키마. 이렇게 두개를 생성해 사용했다.

채팅 기능을 구현 할 수 있는 API는 여러가지가 있겠지만, 일반적으로 Chat-completions-api 를 사용하면 된다. 해당 API 에서는 기본적으로 messages 라는 필드에 채팅 내용을 Array 형태로 전달하면 응답을 주는 식이다. Messages Array에 들어가는 채팅 데이터의 타입은 다음과 같다.

export interface ChatCompletionRequestMessage {
  /**
   * The role of the author of this message.
   * @type {string}
   * @memberof ChatCompletionRequestMessage
   */
  role: ChatCompletionRequestMessageRoleEnum;
  /**
   * The contents of the message
   * @type {string}
   * @memberof ChatCompletionRequestMessage
   */
  content: string;
  /**
   * The name of the user in a multi-user chat
   * @type {string}
   * @memberof ChatCompletionRequestMessage
   */
  name?: string;
}

export declare const ChatCompletionRequestMessageRoleEnum: {
  readonly System: "system";
  readonly User: "user";
  readonly Assistant: "assistant";
};

role 필드에는 system, user, assistant 세개 중 하나만 사용 가능하다.

보통 첫 데이터에 role: "system" 으로 시작해, 기본적인 정보와 명령을 할 수 있다.

messages : [
  {
    role: "system"
    content: "you are helpful assistant. you should reply to user message. you can request more information to user for advanced reply."
  },
  ...
]

이런식으로 AI에게 정체성을 미리 인지시켜주고 채팅을 시작하면 그에 맞게 행동하게된다.

응답으로 오는 정보들을 입맛에 맞게 선택하거나, 사용자 입력을 받아 다시 messages Array에 넣어서 요청하면 채팅처럼 동작하게 된다.

여러명의 AI들과 대화하거나 AI 들 끼리 대화하게 해야하는 구현조건이 있었는데 이 기능은 해당 API의 name필드를 이용했다. name필드의 존재 이유가 정의가된 것 처럼 multi user를 위한 값이기 떄문에, 이 부분을 indentifier 로 사용했다. 예컨데 assistant-1, assistant-2 이런식이다.

사실 요청해서 응답하는 AI 는 하나지만, 해당 필드만 바꿔주면 마치 여렷의 AI가 서로 대화하는 느낌을 줄 수 있다.

어떻게 AI가 대답할 타이밍인지를 인지할까?

조금 애매한 부분이긴 한데, 여러 assistant들이 매번 사용자(role: user)의 입력을 기다리기만 해서는 안되고 AI끼리의 대화도 가능해야 한다. 그러려면 어느 시점에 API 요청을 보내 응답을 가져 올 것인가?에 대한 고민이 생긴다. 이부분에 대한 정확한 명세나 구현조건이 명시되어 있지 않아서 힘들었다. 일단 주관적인 해석으로 구현했다.

AI가 응답하는 조건을 다음과 같이 설정했다.

  1. 첫 메세지는 무조건 사용자가 보낸다.
  2. 1대 1 대화의 경우 사용자 입력이 있을 때만 AI가 응답한다.
  3. AI응답 이후 사용자입력이 일정시간 없다면 다른 AI가 응답한다.

여기서 중요한 건 사용자 입력에 대한 정의와 일정시간이란 어느정도의 시간으로 할 것인가? 이다.

나는 키보드 입력, 스크롤 이벤트, 클릭 이벤트 발생 시 사용자입력이 있는 것으로 간주했고, 7초의 대기시간을 가졌다. 7초동안 사용자 입력이 없다면 AI가 응답하는것.

이정도 조건으로 구현하니 나름 그럴싸하게 동작했다.


미구현 및 아쉬운 부분

기본적인 기능은 구현을 했지만 여전히 여러 버그들이 존재하고 아직 해결하지 못한 문제들이 있다. 회사와 병행해 진행하여 시간이 부족했었다는 핑계를 대지만 사실 더 노력할 수 있었던 건 사실이니 아쉬운 부분이다.

  1. 각종 에러처리 미흡

예를들어 openai 요청에대한 에러에따라 각각 handling해줘야 하지만 그런 부분이 많이 빠져있어 처리하지 못한 에러 발생 시 사용자는 왜 멈췄는지, 왜 응답이 오지 않는지 알 방법이 없다.

  1. storybook 미개발

구현 조건에 각 컴포넌트들에 대한 storybook작성이 조건이었지만 귀찮(?)기도하고, 기능 구현이 우선이라 생각해 좀 소홀히 했던 부분이다.

  1. test-code 미개발

이부분도 1,2 번내용과 이어지는데 openai 라는 외부 API를 사용하는 상황에서 테스트코드 작성이 중요한 것은 알고있지만 기능 구현에 급급하여 챙기지 못한 부분이다.

  1. etc

완성도를 더 높일 수 있지 않았나..


Outro

나름 재미있게 개발을 진행했지만 역시나 시간에 쫓기는건 스트레스다. 아직 넘블 측 진행 프로세스라던가 진행 방식에 체계가 덜 잡혀있는 느낌이 좀 들었지만,,

디스코드로 공지사항 등 정보교류 및 전달을 진행하는데, 여러 딥다이브를 하나의 채널에 관리해서 정신이 없다거나

img03

명세서에 웬 뜬금없는 블록체인이 들어가 있다던가 기능개발에 대한 질의응답에 시간이 너무 많이 걸린다던가

하지만 많은 사람들을 하나의 목표로 묶어 이런 서비스를 제공하는 것 만으로도 나는 충분히 대단하다고 생각한다. 넘블측에서 팀 빌딩도 해주는데, 참가자의 나이나 연차등을 고려해서 나름 세심하게 짜 준 것 같다. 그리고 이런 사이드 프로젝트 특성상 대부분 성장 욕구가 강한 사람들이나 열정이 넘치는 사람들이 지원하기 때문에 동기부여도 잘 되고 시너지가 좋은 것 같다. (조별과제 희망편)

참가비가 3만원인가 4만원 이었던 걸로 기억하는데 그정도 값어치는 충분히 한다고 느꼈다. 일단 재미있었으니까 ㅎㅎ

다음에 또 재미있는 프로젝트가 보이면 다시 참가 할 것 같다.