ABOUT ME

💡 지식은 공유할 때 빛나는 법 💡 꾸준히 몰입, 백엔드 개발자가 되는 그날까지 최선을 다하자.

Today
Yesterday
Total
  • GPT-4로 인공지능 데이터 캐싱 서버 구축하기 feat. Promise Pool
    Project/OpenList 2024. 9. 25. 20:29
    반응형

    안녕하세요, Openlist 팀의 백엔드 기술에 관심을 가져주셔서 감사합니다! 

     

    기술 블로그 작성하는 게 정말 재미있네요! 오늘은 우리 팀의 이야기를 공유하기 위해, GPT-4를 활용한 인공지능 데이터 캐싱 서버를 구축한 경험을 중심으로 글을 써볼까 합니다. 

    Why GPT-4?

    일단 처음에는 기존의 Clova Studio를 사용하기로 했었어요. 우리에겐 20만 원이라는 크레딧이 있었거든요. 하지만 호기심에 GPT-4에 같은 질문을 넣어봤는데, CLOVA Studio랑 비교했을 때 놀라운 데이터 생성 능력을 직접 경험하고는 GPT-4를 사용하기로 결정했습니다.

    API 키 발급 받기

    GPT-4를 사용하려면 먼저 OpenAI 플랫폼에서 API 키를 발급받아야 해요. 이 과정은 비교적 간단해서 아래 문서만 보고 따라 하셔도 금방 하실 수 있습니다. 기본적으로 처음 사용하면 5달러 크레딧을 받게 되는데요. GPT-4 모델을 사용하려면 무료 크레딧으로는 사용할 수 없고, 한 번이라도 크레딧을 충전해야 사용할 수 있답니다. 참고해 주세요!

     

    https://platform.openai.com/docs/quickstart?context=python

     

    Node.js에 GPT-4 연동

    API 키를 발급받은 후에는 Node.js 환경에 GPT-4를 연동했어요. 서버가 복잡하지 않아서 간단하게 구현할 수 있었죠. dotenv 라이브러리를 사용하여 API key 값을 환경변수로 관리하고, OpenAI 라이브러리를 통해 데이터 생성을 하도록 코드를 짰습니다.

    import dotenv from 'dotenv';
    import OpenAI from 'openai';
    
    dotenv.config({ path: '.env' });
    
    const apiKey = process.env.OPENAI_SECRET_KEY;
    const organization = process.env.OPENAI_ORGANIZATION;
    
    // 응답이 유효한지 확인하는 함수
    const checkIfValidResponse = (response, count) => {
      // ...
    };
    
    // 카테고리를 받아서 GPT-4로 데이터 생성 후 PostgreSQL에 저장하는 함수
    export const generateGptData = async (category, count = 10) => {
      const openai = new OpenAI({ apiKey, organization });
      const [main, sub, minor] = category;
      console.log('main,sub,minor:', category);
      
      const content = `
        여기에는 system role 내용을 입력해주세요.
      `;
    
      // 에러가 나면 재시도
      let retryCount = 1;
      while (retryCount--) {
        try {
          const completion = await openai.chat.completions.create({
            messages: [
              {
                role: 'system',
                content,
              },
              { role: 'user', content: `${category}` },
            ],
            model: 'gpt-4-1106-preview',
            response_format: { type: 'json_object' },
          });
    
          console.log('gpt start');
          const response = completion.choices[0];
          console.log('response: ', response);
    
          const checklistItems = JSON.parse(
            response.message.content.replace(/\\\\n/g, ''),
          );
          console.log(checklistItems);
    
          checkIfValidResponse(checklistItems, count);
          return checklistItems;
        } catch (error) {
          console.error('Error during AI generation:', error);
          retryCount++;
        }
      }
    };

     

    ⚒️ 응답이 깨지는 문제 발생

    하지만 응답이 깨져서 들어오는 문제가 발생했어요. 😱

    해결

    기존 GPT-4 API에 요청을 보내는 함수 외에도, checkIfValidResponse() 함수를 통해 응답에 오류가 없는지 검증하고, while 문을 통해 재시도 옵션을 추가했습니다. 이렇게 하면 실패한 응답이 왔을 때 다시 요청을 보내도록 할 수 있죠.

     

    데이터베이스 테이블 생성

    다음으로, PostgreSQL 데이터베이스에 새로운 테이블을 생성했습니다. 이 테이블은 GPT-4로부터 생성된 데이터를 저장하는 데 사용되었어요.

    create table if not exists public.ai_checklist_item_model
    (
        "updatedAt"                 timestamp default now() not null,
        "createdAt"                 timestamp default now() not null,
        "aiChecklistItemId"         serial
            constraint "PK_8467caa2fd5d9cf9879673b4c05"
                primary key,
        content                     varchar                 not null,
        selected_count_by_user      integer   default 0     not null,
        selected_count_by_naver_ai  integer   default 0     not null,
        evaluated_count_by_naver_ai integer   default 0     not null,
        final_score                 integer   default 0     not null,
        "categoryId"                integer
            constraint "FK_25965092abe80a49babceefda5f"
                references public.category_model
    );
    

    Redis Pub/Sub과 Promise Pool 도입

    데이터 처리를 위해 Redis Pub/SubPromise Pool을 도입했어요. 이를 통해 비동기적으로 여러 데이터를 효율적으로 처리할 수 있었죠.

    📚 Promise Pool이란?

    Promise Pool은 동시에 여러 비동기 작업을 관리하고 제어하는 데 사용되는 툴입니다. 이를 통해 동시에 여러 요청을 처리하면서도 각각의 요청이 서로 간섭하지 않도록 할 수 있어요.

    🔄 Redis Pub/Sub의 역할

    Redis Pub/Sub 시스템은 메시지 기반 통신을 가능하게 합니다. 이를 통해 서버 간 메시지를 비동기적으로 주고받을 수 있으며, 효율적인 데이터 처리가 가능해집니다.

    import redis from 'redis';
    import { RedisPub } from './redis-pub.js';
    import { PromisePool } from '@supercharge/promise-pool';
    import dotenv from 'dotenv';
    import { generateGptData } from './generate-server.js';
    import { saveData } from './gpt-data-saver.js';
    
    dotenv.config({ path: '.env' });
    
    const subscriber = redis.createClient({
      url: process.env.REDIS_URL,
    });
    
    // 카테고리를 받아서 GPT-4로 데이터 생성 후 PostgreSQL에 저장하는 함수
    async function processCategory(category, publisher) {
      // ...
    }
    
    // Redis Pub/Sub을 통해 Promise Pool 방식으로 카테고리를 받아 GPT-4로 데이터 생성 후 PostgreSQL에 저장하는 함수
    async function init() {
      const publisher = new RedisPub();
      const redisSub = await subscriber.connect();
    
      redisSub.subscribe('ai_generate', async function (message) {
        console.log('message:', message);
    
        const parsedMessage = JSON.parse(message);
    
        if (parsedMessage.messageData === 'generateGptData') {
          const { messageData, categories } = parsedMessage;
          console.log('messageData:', messageData);
          console.log('categories:', categories);
    
          console.log('start');
    
          // Promise Pool 방식으로 processCategory 함수 실행
          const { results, errors } = await PromisePool.withConcurrency(5)
            .for(categories)
            .process(async (category) => {
              await processCategory(category, publisher);
            });
    
          // 오류 처리 또는 결과 확인
          if (errors.length) {
            publisher.send(
              'ai_generate',
              JSON.stringify({
                messageData: 'error',
                errorLog: errors,
              }),
            );
            console.error('Errors:', errors);
          }
    
          console.log('end');
        }
      });
    }
    
    init();

     

    위의 코드는 redis-sub.js 코드인데요. 지정된 메시지(generateGptData)를 subscribe하면, GPT-4 API를 호출하고, DB에 저장하는 로직이 순차적으로 진행되도록 했고, 이 부분에서 Promise Pool 방식을 적용했습니다.

     

    우리 팀은 @supercharge/promise-pool 라이브러리를 통해 Promise Pool 방식을 구현했어요.

    일단은 5개의 레일을 만들어 뒀고, DB 상황에 맞춰서 늘릴 예정입니다. 테스트해 보니 놀라울 정도로 속도가 빨라진 걸 경험할 수 있었죠.

     

    ⚒️ Temperature 이슈 해결

    프로젝트 중 한 가지 이슈가 발생했어요. GPT-4의 temperature 설정을 높게 하니, 응답 속도가 매우 느려졌거든요. 이 문제를 해결하기 위해 temperature 옵션을 제거했고, 결과적으로 훨씬 빠르고 만족스러운 결과를 얻을 수 있었습니다.

     

    우분투 서버에 배포

    우분투 서버에 배포하는 과정은 아래 포스팅을 참고해 주세요.

    Node.js 서버 인스턴스 세팅 및 PM2를 이용한 서버 자동화

     

    Node.js 서버 인스턴스 세팅 및 PM2를 이용한 서버 자동화 | Notion

    이 글에서는 우분투 환경에서 Node.js 프로젝트를 세팅하고, pm2를 활용하여 서버를 자동화하는 과정을 단계별로 설명하겠습니다. 먼저, Node.js를 설치하고, git을 사용하여 프로젝트를 복제한 후,

    msmspark.notion.site

     

    배포 후 node main.js로 publish를 해 보니 admin page에서 잘 실행됐다는 로그가 찍혔어요.

    아주 잘 작동하는 걸 확인했습니다. 😊


    마무리

    이렇게 생성 서버를 구축해 보았는데요. AI 기술과 서버 관리를 같이하면서 도전적이었지만, 그만큼 많이 배웠어요. 여러분도 이런 프로젝트에 도전해서 재미있는 AI 연동을 직접 경험해 보시길 바랍니다! 🌟👩‍💻👨‍💻


    이상으로 GPT-4를 활용한 인공지능 데이터 캐싱 서버 구축 프로젝트에 대한 이야기를 마치겠습니다.

    여러분의 소중한 피드백을 기다리고 있겠습니다! 

     

    비가 오는 날엔 ~
    RainyCode를 찾아와
    밤을 새워 기다릴게...
    반응형
Designed by Tistory.