GPT 기능 구현 후 빌드 Error 해결 과정기

SeongHyeon Bae·2023년 10월 23일
2
post-thumbnail

프로젝트를 진행 중 OPEN AI의 API를 활용하여 GPT 기능을 넣어 보았다. 처음에는 비용적인 부분이 걱정이 되었지만 OPENAI의 가격정책을 보면 input, ouput이 1K token 라는 가정하에 0.0035$ 정도라 현재 유저가 많은 서비스가 아닌 만큼 크게 부담이 되지 않았다. OPEN AI의 API를 사용하여 GPT 기능을 구현하는 방법은 공식문서 이외에도 많은 글들이 존재해 이번에는 CI 환경에서 빌드 중 발생한 에러 해결과정 및 배운점을 적어 보려고 한다.

문제상황

로컬 환경에서는 에러 없이 잘 작동 하였는데 git action으로 build 테스트시에 다음과 같은 에러가 발생했다.

응? OPEN_API_KEY가 없다고? KEY값을 어디서 사용하더라...

에러 해결을 위해 사용되는 코드 부분을 찾아 보았다.

//open AI의 api 사용을 위한 전처리 파일 입니다.
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.NEXT_PUBLIC_OPEN_API,
  dangerouslyAllowBrowser: true,
});

async function getCodeReview({
  code,
  question,
}: {
  code: string;
  question: string;
}) {
  const completion = await openai.chat.completions.create({
    messages: [{ role: 'user', content: `${code} \n ${question} ` }],
    model: 'gpt-3.5-turbo',
    max_tokens: 1024,
  });

  return completion.choices[0].message.content;
}
export default getCodeReview;

해결시도 1 (env파일 추가)

아! 현재 API 키는 env.development에 저장되어 있고 gitignore에 env.development 파일이 설정되어 있기 때문에 빌드시에는 OPEN_API 값을 받아올 수 없기에 에러가 떴구나😅
그럼 빌드 전에 env 파일을 넣어주면 되지 않을까?

이러한 고민을 하여 yml 파일에 Generate Environment Variables File for Production 이름을 가진 코드를 추가하여 빌드 전 env 파일에 NEXT_PUBLIC_OPEN_API 값을 넣어주었다.

name: 'test-lint-build'

on:
  push:
  pull_request:

jobs:
  test:
    name: Test lint, build
    runs-on: ubuntu-latest
    env:
      working-directory: ./client

    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18'
      - name: Cache node modules
        uses: actions/cache@v2
        id: cache
        with:
          path: node_modules
          key: npm-packages-${{ hashFiles('**/package-lock.json') }}
          
      - name: Generate Environment Variables File for Production
        run: echo "NEXT_PUBLIC_OPEN_API=${{ secrets.NEXT_PUBLIC_OPEN_API }}" >> .env
        
      - name: Install Dependencies
        run: npm install
        working-directory: ${{ env.working-directory }}
      - run: npm run lint
        if: ${{ always() }}
        working-directory: ${{ env.working-directory }}
      - run: npm run build
        if: ${{ always() }}
        working-directory: ${{ env.working-directory }}

해결될것 같았지만 여전히 같은 에러가 발생하였다. 무엇이 문제일까? 고민하다 두가지 의문점이 들었다.

  1. NEXTJS 환경에서 env 환경변수에 NEXT_PUBLIC이 의미하는게 뭘까?
  2. 개발 환경에서는 .env.development 를 사용하고 있었는데 .env, .env.development 차이가 뭘까?

1. NEXT_PUBLIC

Non-NEXTPUBLIC environment variables are only available in the Node.js environment, meaning they aren't accessible to the browser (the client runs in a different environment).

In order to make the value of an environment variable accessible in the browser, Next.js can "inline" a value, at build time, into the js bundle that is delivered to the client, replacing all references to process.env.[variable] with a hard-coded value. To tell it to do this, you just have to prefix the variable with NEXTPUBLIC. NEXTJS

요약하면, 일반적으로 env 변수는 NodeJS 환경에서만 사용할 수 있는데, 만약 브라우저 환경에서도 사용하고 싶으면 환경변수 앞에 접두사로 NEXT_PUBLIC_을 붙여야 한다.

결론적으로 나는 브라우저 환경에서 GPT 기능을 사용하기 위해 NEXT_PUBLIC_ 을 추가적으로 붙여주었었고 이는 NODEJS 환경에서도 잘 작동하니 문제의 원인이 아니였다.

참고로 process.env.NODE_ENV 는 현재 실행 환경이 development,test,production 인지 구분해주는 내장 환경 변수이다. 이를 활용하여 환경에 따라 의도적으로 다른 작업을 할 수도 있을 것 같다.
(예를들면 개발환경, 배포환경에서 서버에 요청하는 URL를 다르게 한다던지???)

2. ENV

매번 프로젝트 할떄마다 env 파일을 사용하지만 여러 env 종류와 의미를 알지못해 학습해 보았다.

ENV 종류

.env: 기본 파일.
.env.local: .env를 덮어쓰는 파일. Test를 제외한 모든 환경에서 로딩

.env.development: 개발자 환경에서 로딩
.env.test: 테스트 환경에서 로딩
.env.production: 프로덕션 환경에서 로딩

.env.development.local, .env.test.local, .env.production.local: 각각 env.* 를 덮어쓰는 파일 참고

ENV 실행 우선순위

npm start: .env.development.local > .env.development > .env.local > .env
npm run build: .env.production.local > .env.production > .env.local, .env
npm test: .env.test.local > .env.test > .env (note .env.local is missing)

정확히 알고 사용한 것은 아니였지만 개발환경에서는 .env.development 파일로, git action CI 빌드 환경에서는 .env 파일로 사용하여 이 또한 문제가 아니였다. (만약 빌드 환경에서 .env.development로 만들었다면 npm run build 실행 시 파일을 읽지 못했을 것이고 이를 원인이라고 생각했을 수도 있겠다.)

해결시도 2 (OPEN AI 코드 탐색)

env 설정은 잘해주었는데 왜 OPEN_API 값을 못찾을까?? 도대체 어떤 방식으로 코드를 짰길래...

이러한 생각으로 OPEN_AI의 에러 처리 방식을 라이브러리를 뜯어보며 찾아보았다.
open-ai/src/index.ts

//open-ai/src/index.ts
...
  constructor({
    apiKey = Core.readEnv('OPENAI_API_KEY'),
    organization = Core.readEnv('OPENAI_ORG_ID') ?? null,
    ...opts
  }: ClientOptions = {}) {
    if (apiKey === undefined) {
      throw new Errors.OpenAIError(
        "The OPENAI_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenAI client with an apiKey option, like new OpenAI({ apiKey: 'My API Key' }).",
      );
    }
...

open-ai/src/core.ts

//open-ai/src/core.ts
...
export const readEnv = (env: string): string | undefined => {
  if (typeof process !== 'undefined') {
    return process.env?.[env] ?? undefined;
  }
  if (typeof Deno !== 'undefined') {
    return Deno.env?.get?.(env);
  }
  return undefined;
};
...

이 코드를 보면서 OEPN AI의 코드 구현 방식을 생각해 보았다.

  1. 생성자 인자로 apiKey가 들어오면 그 값을 사용
  2. 없는 경우는 readEnv 함수를 통해 env파일에 OPENAI_API_KEY 변수가 있나 확인하고 있으면 그대로 사용, 없으면 undefined 반환
  3. apiKey가 undefined일 경우 Error 던짐

나는 현재 아래와과 같이 NEXT_PUBLIC_OPEN_API(open ai에서 탐색하는 OEPNAI_API_KEY가 아님을 주의) 값을 설정해 놨었고 2번으로 분기 되지 않겠다는 것을 생각했다. 그럼 결론적으로 빌드 환경에서 OPENAI의 인자로 apiKey값이 들어가기는 할텐데 3번이(에러) 발생한다는 것은 apiKey가 undefined 라는 의미이고, 결국 아직도 build 환경에서는 env를 못찾는구나.

//open AI의 api 사용을 위한 전처리 파일 입니다.
...
const openai = new OpenAI({
  apiKey: process.env.NEXT_PUBLIC_OPEN_API,
  dangerouslyAllowBrowser: true,
});
...

해결 방식 (working-directory)

너무나도 허무하게 문제는 env 파일 생성 경로를 잘못 지정해 주었기 때문이다.(그러니 계속해서 env 파일을 못찾았지...😭) 이 프로젝트 폴더 구조는 root-client 구조로 되어있어 프로젝트를 실행하기 위해서는 clinet 폴더로 변경하여 run을 실행시켜야했다. 하지만 Generate Environment Variables File for Production 에서는 working-directory를 변경하지 않아 env 파일은 root 폴더에 npm build는 client에서 실행되어 문제가 발생했던 것이다.

그래서 다음과 같이 working-directory를 추가한 yml 파일로 CI build 테스팅을 성공시킬 수 있었다.

name: 'test-lint-build'

on:
  push:
  pull_request:

jobs:
  test:
    name: Test lint, build
    runs-on: ubuntu-latest
    env:
      working-directory: ./client

    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18'
      - name: Cache node modules
        uses: actions/cache@v2
        id: cache
        with:
          path: node_modules
          key: npm-packages-${{ hashFiles('**/package-lock.json') }}
          
      - name: Generate Environment Variables File for Production
        run: echo "NEXT_PUBLIC_OPEN_API=${{ secrets.NEXT_PUBLIC_OPEN_API }}" >> .env
        working-directory: ${{ env.working-directory }}
        
      - name: Install Dependencies
        run: npm install
        working-directory: ${{ env.working-directory }}
      - run: npm run lint
        if: ${{ always() }}
        working-directory: ${{ env.working-directory }}
      - run: npm run build
        if: ${{ always() }}
        working-directory: ${{ env.working-directory }}

이번 에러를 통해 개발자의 성장은 에러를 해결하는 과정에서 가장 크다고 느꼈다. 만약 working-directory 설정을 안해줬다는것을 바로 알아차렸다면 해결은 빠르게 했을지 몰라도 NEXT_PUBLIC의 의미, ENV 종류와 특징, OPEN_AI가 API를 받아오는 과정을 학습할 수 없었을 것이다. 삽질하는 과정을 너무 부정적으로만 생각하지 말고 성장의 영양제라고 생각하자!😊

profile
FE 개발자

0개의 댓글