IT's Jenna

Node.js로 GPT API를 활용한 번역 서비스 구현 본문

Backend/외부 API 활용

Node.js로 GPT API를 활용한 번역 서비스 구현

developer Jenna 2024. 6. 4. 09:15

이번 포스트에서는 Node.js 기반 서버에서 OpenAI의 GPT-3.5-turbo 모델을 사용해 텍스트를 번역하는 방법을 설명하겠습니다. 이 예제는 API 키를 여러 개 사용하는 기능도 포함하고 있습니다. 각 코드 파일의 역할과 구현 방법을 순서대로 살펴보겠습니다.

1. GPT API를 이용한 기본 번역 기능

먼저, server/components/gpt.js 파일에서는 GPT API를 사용해 텍스트를 번역하는 기본 기능을 구현합니다.

const axios = require('axios')

const API_KEY = process.env.GPT_KEY // 환경변수에서 API 키 가져오기
const ENDPOINT = 'https://api.openai.com/v1/chat/completions'

async function translateText(text) {
  if (!/[\u3131-\uD79D]/giu.test(text)) {
    // 한국어가 포함되어 있지 않으면 번역하지 않습니다.
    return text
  }
  try {
    const response = await axios.post(
      ENDPOINT,
      {
        model: 'gpt-3.5-turbo',
        messages: [
          {
            role: 'user',
            content: `Translate into English : ${text}`
          }
        ],
        temperature: 0.7
      },
      {
        headers: {
          Authorization: `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        }
      }
    )
    return response.data.choices[0].message.content
  } catch (error) {
    console.error('Translation error:', error)
    return text // 에러 발생 시 원본 텍스트 반환
  }
}

const getGPTResponse = async (input) => {
  if (typeof input === 'string') {
    // 문자열 처리
    return await translateText(input)
  }
  if (Array.isArray(input)) {
    // 배열 처리
    return Promise.all(input.map((item) => getGPTResponse(item)))
  }
  if (typeof input === 'object') {
    // 객체 처리
    const keys = Object.keys(input)
    const translationPromises = keys.map(async (key) => {
      const value = input[key]
      if (typeof value === 'string') {
        const translatedText = await translateText(value)
        return {key, translatedText}
      }
      return {key, translatedText: value} // 번역하지 않는 값은 그대로 반환
    })
    const translatedEntries = await Promise.all(translationPromises)
    return translatedEntries.reduce((acc, {key, translatedText}) => {
      acc[key] = translatedText
      return acc
    }, {})
  }
  return input // 다른 데이터 타입은 입력을 그대로 반환
}

module.exports = {getGPTResponse}

 

이 파일에서는 translateText 함수를 통해 텍스트를 영어로 번역합니다. getGPTResponse 함수는 입력 데이터가 문자열, 배열, 객체인지 확인하고 적절히 번역 작업을 수행합니다.

axios.post 메서드의 각 변수 설명

  • ENDPOINT: OpenAI API의 엔드포인트 URL입니다. 이 URL은 GPT 모델을 호출하는 주소입니다. 예를 들어, 'https://api.openai.com/v1/chat/completions'이 됩니다.
  • model: 사용할 GPT 모델의 이름입니다. 여기서는 'gpt-3.5-turbo' 모델을 사용하고 있습니다.
  • messages: GPT-3.5-turbo 모델에 전달할 메시지 배열입니다. 이 배열에는 하나 이상의 메시지 객체가 포함됩니다.
    • role: 메시지의 역할을 지정합니다. 'user'는 사용자 메시지를 나타내며, 'system'이나 'assistant' 역할도 있습니다. 여기서는 사용자가 보낸 메시지를 나타냅니다.
    • content: GPT 모델에 전달할 실제 텍스트 콘텐츠입니다. 여기서는 번역할 텍스트를 포함하고 있으며, Translate into English : ${text} 형식으로 영어로 번역하도록 요청하고 있습니다. 텍스트가 길어질수록 반응시간도 길어져서 최대한 간결한 텍스트를 입력했습니다.
  • temperature: 생성된 텍스트의 다양성을 조절하는 매개변수입니다. 값이 낮을수록 더 보수적인 응답이 생성되고, 값이 높을수록 더 다양한 응답이 생성됩니다. 여기서는 0.7로 설정하여 적당한 다양성을 유지합니다.
  • headers: HTTP 요청의 헤더 정보를 포함합니다. 이 요청에는 두 개의 헤더가 설정되어 있습니다.
    • Authorization: API 키를 포함하는 헤더입니다. Bearer ${API_KEY} 형식으로 API 키를 전달하여 인증을 수행합니다. 여기서 API_KEY는 환경 변수에서 가져온 OpenAI API 키입니다.
    • Content-Type: 요청 본문의 콘텐츠 유형을 지정합니다. 여기서는 JSON 형식의 데이터를 전송하기 위해 'application/json'으로 설정합니다.

2. 여러 API 키를 사용하는 번역 기능

API 요청량이 많을 경우 하나의 API 키로는 한계가 있을 수 있습니다. OpenAI API는 일정량 이상의 요청을 받으면 제한이 걸릴 수 있기 때문에, 여러 API 키를 순환적으로 사용하는 방법을 사용합니다. 이를 통해 각 키에 걸리는 부담을 분산시킬 수 있습니다. server/components/gptarr.js 파일에서는 이를 구현합니다.

 

const axios = require('axios')

// 환경변수에서 API 키 배열로 가져오기, API 키들은 콤마로 구분되어 있어야 합니다.
const API_KEYS = process.env.GPT_KEYS.split(',')
const ENDPOINT = 'https://api.openai.com/v1/chat/completions'

let keyIndex = 0 // 현재 API 키 인덱스

// 다음 API 키를 순환적으로 선택
function getNextApiKey() {
  const key = API_KEYS[keyIndex]
  keyIndex = (keyIndex + 1) % API_KEYS.length
  return key
}

async function translateText(text) {
  if (!/[\u3131-\uD79D]/giu.test(text)) {
    return text
  }
  try {
    const apiKey = getNextApiKey() // API 키 선택
    const response = await axios.post(
      ENDPOINT,
      {
        model: 'gpt-3.5-turbo',
        messages: [
          {
            role: 'user',
            content: `Translate into English : ${text}`
          }
        ],
        temperature: 0.7
      },
      {
        headers: {
          Authorization: `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        }
      }
    )
    return response.data.choices[0].message.content
  } catch (error) {
    // console.error('Translation error:', error)
    return text
  }
}

async function getGPTResponse(input) {
  if (typeof input === 'string') {
    return translateText(input)
  }
  if (Array.isArray(input)) {
    return Promise.all(input.map(getGPTResponse))
  }
  if (typeof input === 'object' && input !== null) {
    const keys = Object.keys(input)
    const translations = await Promise.all(
      keys.map(async (key) => {
        const value = input[key]
        if (key !== 'first_create_dt' && key !== 'last_update_dt') {
          const translatedText = await getGPTResponse(value)
          return {key, translatedText}
        }
        return {key, translatedText: value}
      })
    )
    return translations.reduce((acc, {key, translatedText}) => {
      acc[key] = translatedText
      return acc
    }, {})
  }
  return input
}

module.exports = {getGPTResponse}

 

이 코드에서는 여러 API 키를 순환적으로 선택해 사용합니다. getNextApiKey 함수는 현재 인덱스의 API 키를 반환하고, 다음 요청을 위해 인덱스를 업데이트합니다. 이를 통해 API 요청이 특정 키에 집중되지 않고, 여러 키에 분산되도록 합니다.

 

무료 계정의 경우 키 하나에 요청할 수 있는 제한이 다음과 같습니다. 따라서 여러 개의 키를 만들어서 제한을 피하도록 해둔 부분입니다.

  • 하루 요청 한도: 100,000 tokens (모델이 텍스트를 처리하는 단위, 사용량과 비용을 측정하는 데 사용, 텍스트를 문자, 공백, 부호 등으로 쪼개서 측정)
  • 1분당 요청 한도: 3 requests per minute (API 서버에 대한 HTTP 호출)

3. 번역 미들웨어

마지막으로, 번역 작업을 수행하는 미들웨어를 구현합니다. 이 미들웨어는 HTTP 응답을 가로채 번역 작업을 수행합니다. server/middlewares/translation.js 파일에서 이를 구현합니다.

const {getGPTResponse} = require('../components/gptarr')

const translation = async (req, res, next) => {
  const originalJson = res.json
  res.json = async (data) => {
    try {
      const language = req.headers['accept-language']
      if (language.startsWith('en') && data?.data) {
        const translatedText = await getGPTResponse(data.data)
        data.data = translatedText
        originalJson.call(res, data)
      } else {
        originalJson.call(res, data)
      }
    } catch (error) {
      originalJson.call(res, data)
    }
  }
  next()
}

module.exports = translation

 

이 미들웨어는 클라이언트가 Accept-Language 헤더를 통해 영어 응답을 요청한 경우, 응답 데이터를 번역합니다. 번역이 완료된 데이터를 클라이언트에 반환합니다.

 

이때 웹에서는 Accept-Language 헤더가 바로 들어왔지만 모바일에서는 해당 헤더값이 들어오지 않는 이슈가 있었습니다. 따라서 api 요청을 할때 헤더에 해당 키와 값을 임의로 추가해서 보내주어야 했습니다.

결론

이번 포스트에서는 Node.js 서버에서 GPT-3.5-turbo 모델을 사용해 텍스트를 번역하는 방법을 알아보았습니다. 기본적인 번역 기능에서 시작해, 여러 API 키를 순환적으로 사용하는 방법과 번역 미들웨어를 구현했습니다. 여러 API 키를 사용하는 이유는 API 요청 제한을 회피하고, 안정적인 서비스를 제공하기 위함입니다.

 

다만 이 방법으로는 api를 사용할때마다 번역과정을 거치면서 delay가 발생하기 때문에 이후에는 db에 언어별로 저장하여 해당 데이터를 가지고 오는 방식으로 변경하고자 합니다. 관련 내용이 업데이트되면 다시 블로그에 추가하도록 하겠습니다!

Comments