Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

성능 향상과 비용 절감을 한 번에: Upstash Redis로 OpenAI API 응답 캐싱하기

OpenAI API를 사용해 본 적이 있다면 응답 속도가 상당히 느리고 때로는 아예 응답하지 않는 경우도 있다는 것을 알고 있을 겁니다. 특히 GPT-4 모델은 지연 시간이 긴 응답이 발생하기 쉽습니다. 게다가 받은 답변 하나하나마다 비용을 지불해야 하죠. 이런 이유들 때문에 API 응답을 그대로 클라이언트에게 전달하는 방식은 피하는 것이 좋습니다.

이러한 문제를 해결하려면 응답을 Upstash Redis에 저장하면 됩니다. 많은 클라이언트에게 동일한 응답을 제공하는 상황이라면 상당한 비용을 절약할 수 있고, 전역 배포 기능 덕분에 사용자가 최대한 빠르게 정보를 받을 수 있음을 보장할 수 있습니다.

이 글에서는 OpenAI API의 응답을 Upstash Redis 데이터베이스에 캐싱하는 과정을 단계별로 안내합니다.

만들려는 기능

방문자에게 역사 유머 목록을 보여주는 웹 앱을 만들어 보겠습니다.

  1. 매일 OpenAI API에 현재 날짜와 관련된 역사 유머를 요청합니다
  2. 받아온 유머를 나중에 제공할 수 있도록 캐싱합니다
  3. 저장된 모든 유머를 화면에 표시합니다

사용 기술

이 앱을 만들기 위해 다음과 같은 기술들을 사용합니다:

  • Node.js — HTTP 서버
  • OpenAI API — 역사 유머 생성
  • Upstash QStash — HTTP 서버에서 OpenAI API로 새 유머 요청을 트리거
  • Upstash Redis — 유머를 저장해 이후 활용

사전 준비 사항

  • AI에 접근하기 위한 API 권한이 있는 OpenAI 계정
  • OpenAI API로 요청을 전송하고 AI 응답을 Upstash Redis에 캐싱하기 위한 Upstash 계정
  • OpenAI와 Upstash API를 호출하기 위한 Node.js 설치 환경

구현

유머를 가져와 Upstash Redis에 저장하고 방문자에게 표시하는 HTTP 서버부터 만들어 보겠습니다.

프로젝트 설정

먼저 새 Node.js 프로젝트를 생성하고 필요한 의존성을 설치합니다.

$ mkdir history-jokes && cd history-jokes
$ npm init -y
$ npm i dotenv express axios @upstash/redis @upstash/qstash

서버 구현

서버를 구현하려면 index.js 파일을 만들고 아래 코드를 붙여넣습니다:

require("dotenv").config();
 
const axios = require("axios");
const express = require("express");
const { Redis } = require("@upstash/redis");
const { Receiver } = require("@upstash/qstash");
 
const redisClient = new Redis({
  url: process.env.UPSTASH_REDIS_URL,
  token: process.env.UPSTASH_REDIS_TOKEN,
});
 
const qstashReceiver = new Receiver({
  currentSigningKey: process.env.UPSTASH_QSTASH_CURRENT_SIGNING_KEY,
  nextSigningKey: process.env.UPSTASH_QSTASH_CURRENT_NEXT_KEY,
});
 
const openaiApiClient = axios.create({
  baseURL: "https://api.openai.com/v1",
  headers: {
    Authorization: "Bearer " + process.env.OPENAI_TOKEN,
    "Content-Type": "application/json",
  },
});
 
const server = express();
 
server.use("/generate", async (request, response, next) => {
  // return next()
  let validRequest = false;
  try {
    validRequest = await qstashReceiver.verify({
      signature: request.headers["upstash-signature"],
      body: "",
    });
  } catch (e) {}
  if (!validRequest) return response.status(403).end("Forbidden");
 
  return next();
});
 
server.post("/generate", async (_request, response) => {
  const today = new Date();
  const month = today.toLocaleString("default", { month: "long" });
  let day = today.toLocaleString("default", { day: "numeric" });
  day = day == 1 ? "1st" : day == 2 ? "2nd" : day == "3" ? "3rd" : day + "th";
 
  const { data } = await openaiApiClient.post("/chat/completions", {
    model: "gpt-3.5-turbo",
    messages: [
      {
        role: "system",
        content: "You are a comedian that tells short history jokes.",
      },
      {
        role: "user",
        content: `Please tell me a joke for ${month} the ${day}.`,
      },
    ],
  });
 
  const joke = JSON.stringify({
    date: month + " the " + day,
    text: data.choices[0].message.content,
  });
 
  await redisClient.lpush("jokes", joke);
 
  response.end();
});
 
server.get("/", async (_request, response) => {
  let html = "<h1>History Jokes for Every Day</h1>";
 
  const jokes = await redisClient.lrange("jokes", 0, -1);
 
  html +=
    "<ul>" +
    jokes
      .map(({ date, text }) => `<li><b>${date}</b><br><pre>${text}</pre></li>`)
      .join("") +
    "</ul>";
 
  response.setHeader("Content-Type", "text/html");
  response.end(html);
});
 
server.listen(3000);

이 코드에서 중요한 부분들을 살펴보겠습니다.

redisClient는 응답을 Upstash Redis에 저장하고, 화면에 표시하기 위해 다시 불러오는 데 사용됩니다.

qstashReceiver는 Upstash QStash로부터 오는 일일 호출을 검증하는 역할을 합니다. 이렇게 하면 /generate 엔드포인트가 QStash를 통해서만 호출되도록 보장할 수 있습니다.

Axios를 사용해 OpenAI API용 클라이언트를 생성하면, API를 호출할 때마다 baseUrlAuthentication 헤더를 반복해서 넘겨줄 필요가 없습니다.

그다음, qstashReceiver를 활용해 /generate 엔드포인트로 들어오는 모든 요청을 검증하는 Express 미들웨어를 만듭니다. QStash는 엔드포인트를 트리거하는 용도로만 사용하고 어떤 값도 전달하지 않으므로, body에는 빈 문자열을 사용할 수 있습니다.

참고: 미들웨어 코드에서 "// return next()" 줄의 주석을 해제하면 모든 요청이 허용됩니다. 이를 활용하면 localhost 환경에서 /generate 엔드포인트를 테스트할 수 있습니다.

/generate 엔드포인트는 POST 요청을 수신하며 실제 유머를 생성하고 저장합니다. 현재 month(월)와 day(일)를 계산한 뒤, 이를 바탕으로 AI에게 보낼 두 개의 프롬프트를 만듭니다. 첫 번째 프롬프트는 AI가 코미디언처럼 행동하도록 지시하고, 두 번째는 현재 월과 일에 관한 역사 유머를 요청합니다.

이 프롬프트들로 OpenAI API를 호출한 후, 결과를 Upstash Redis의 LIST에 저장합니다.

/ 엔드포인트는 GET 요청을 수신하며 저장된 유머들을 표시합니다. Upstash Redis에서 LIST를 불러온 뒤 HTML 리스트 요소 형태로 포맷팅하여 클라이언트에 전송합니다.

배포

서버 설정이 끝났으니 이제 클라우드 리소스를 생성하고, 이에 접근하기 위한 API 자격 증명을 입력해야 합니다.

자격 증명 파일 생성

자격 증명을 저장하기 위해 다음 내용으로 .env 파일을 생성합니다:

OPENAI_TOKEN=""
UPSTASH_REDIS_REST_URL=""
UPSTASH_REDIS_REST_TOKEN=""
UPSTASH_QSTASH_CURRENT_SIGNING_KEY=""
UPSTASH_QSTASH_NEXT_SIGNING_KEY=""

다음 단계에서 각각의 빈 문자열을 채워 넣겠습니다.

OpenAI API 토큰 생성

기존 API에 키만 있으면 되므로 별도로 배포할 것은 없습니다. 먼저 OpenAI API부터 시작하겠습니다.

OpenAI 웹 콘솔로 이동해 "create new secret key" 버튼을 클릭합니다. 키 이름을 지정하고 "Create secret key"를 누른 뒤, 생성된 키를 복사해 .env 파일의 OPENAI_TOKEN 값으로 붙여넣습니다.

QStash로 반복 요청 배포

QStash에게 매일 한 번씩 /generate 엔드포인트로 요청을 보내도록 알려주려면 "Request Builder"를 사용합니다. Upstash 콘솔에서 찾을 수 있으며, 설정 세부 사항은 그림 1에 나와 있습니다.

성능 향상과 비용 절감을 한 번에: Upstash Redis로 OpenAI API 응답 캐싱하기

그림 1: QStash Request Builder

<HOSTNAME> 부분은 서버가 호스팅되는 도메인으로 교체하세요. QStash가 작동하려면 공개적으로 접근 가능한 호스트명이 필요합니다.

QStash가 사용하는 서명 키(Signing Keys)는 Upstash 콘솔의 "Request Builder" 섹션에서 확인할 수 있습니다. 회색 "Signing Keys" 드롭다운을 클릭하고 각 키를 복사해 .env 파일의 올바른 위치에 붙여넣습니다.

Upstash Redis 데이터베이스 배포

Upstash Redis 데이터베이스를 생성하려면 Upstash 콘솔로 이동해 "Create Database" 버튼을 클릭합니다.

데이터베이스 설정값을 입력하는 대화상자가 나타나는데, 사용해야 할 값들은 그림 2에 정리되어 있습니다.

성능 향상과 비용 절감을 한 번에: Upstash Redis로 OpenAI API 응답 캐싱하기

그림 2: Upstash Redis 설정

생성이 완료되면 아래로 스크롤해 "REST API" 섹션으로 이동합니다. 여기에는 "UPSTASH_REDIS_REST_URL"과 "UPSTASH_REDIS_REST_TOKEN" 두 개의 버튼이 있는데, 각각 클릭해 해당 자격 증명을 복사한 후 .env 파일의 올바른 위치에 붙여넣으면 됩니다.

웹사이트 테스트

로컬 머신에서 웹사이트를 테스트하려면 index.js 파일에서 "// return next()" 줄의 주석을 해제해 QStash 검증 미들웨어를 비활성화합니다.

다음 명령으로 서버를 실행합니다:

$ node .

브라우저에서 / 경로를 열면 아직 생성된 유머가 없기 때문에 "History Jokes for Every Day" 문구만 표시될 것입니다.

유머를 생성하려면 /generate 엔드포인트로 POST 요청을 보내야 합니다. 다음 명령으로 가능합니다:

$ curl -X POST https://localhost/generate

브라우저 페이지를 새로 고침하면 그림 3처럼 유머가 표시될 것입니다.

성능 향상과 비용 절감을 한 번에: Upstash Redis로 OpenAI API 응답 캐싱하기

그림 3: 유머 웹사이트

이 유머는 이제 Upstash Redis에 캐싱되었기 때문에, 누군가 웹사이트를 방문해도 OpenAI API 요청이 발생하지 않습니다. 덕분에 비용을 크게 아끼면서도 1초 미만의 빠른 응답 시간을 확보할 수 있습니다.

마무리

AI API는 우리가 아는 인터넷의 모습을 바꿔놓을 강력한 도구입니다. 하지만 현재로서는 느리고 비용이 많이 드는 편입니다. 따라서 이러한 API를 사용할 때 캐싱은 필수적이며, Upstash Redis는 몇 번의 클릭만으로 스택에 캐싱 기능을 추가할 수 있는 가장 간편한 방법입니다.