パフォーマンスの向上とコスト削減:Upstash RedisでOpenAI APIのレスポンスをキャッシュする方法
OpenAI APIを使ったことがある方なら、その応答がかなり遅く、場合によってはまったく応答しないことにお気づきかもしれません。特にGPT-4モデルはレイテンシ(応答遅延)が高くなりがちです。さらに、取得した回答ごとに課金されます。これらは、APIのレスポンスをそのままユーザーに提供すべきではない理由です。
こうした問題を回避するには、Upstash Redisにレスポンスを保存するのが有効です。同じレスポンスを多数のクライアントに配信する場合、かなりのコストを節約でき、グローバルに展開されたデータベースにより、ユーザーは可能な限り迅速に情報を取得できます。
本記事では、OpenAI APIのレスポンスをUpstash Redisデータベースにキャッシュする手順を解説します。
アプリの機能
今回は、訪問者に歴史ネタ(ヒストリージョーク)の一覧を表示するWebアプリを作成します。
- 毎日OpenAI APIにクエリを送信し、現在の日付に関連する歴史ジョークを取得する
- 取得したジョークをキャッシュし、以降の配信に備える
- すべてのジョークを一覧表示する
使用する技術
このアプリの構築には、以下の技術を使用します。
- Node.js:HTTPサーバーとして使用
- OpenAI API:歴史ジョークの生成に使用
- Upstash QStash:HTTPサーバーからOpenAI APIへのジョーク生成リクエストを定期実行
- Upstash Redis:ジョークを保存し、後で利用できるようにする
前提条件
- AIへアクセスするためのAPIキーを持つOpenAIアカウント
- OpenAI APIへのリクエスト送信とAIレスポンスのキャッシュを行うためのUpstashアカウント
- OpenAI APIと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を呼び出すたびにbaseUrlやAuthorizationヘッダーを渡す必要がなくなります。
次に、qstashReceiverを使って/generateエンドポイントへのすべてのリクエストを検証するExpressミドルウェアを作成します。QStashはエンドポイントのトリガーにのみ使用し、値を渡さないため、bodyには空文字列を指定できます。
注意:「// return next()」の行のコメントを解除すると、ミドルウェアはすべてのリクエストを許可します。localhost上で/generateエンドポイントをテストする際に活用できます。
/generateエンドポイントはPOSTリクエストを受け付け、実際のジョークを生成・保存します。現在のmonth(月)とday(日)を計算し、それをもとにAIへの2つのプロンプトを作成します。1つ目はAIにコメディアンとして振る舞うよう指示し、2つ目は現在の月日に関する歴史ジョークを求めます。
プロンプトを使って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トークンの作成
まずはOpenAI APIから始めましょう。既存のAPIに対してキーが必要なだけで、新たにデプロイするものはありません。
OpenAIのウェブコンソールにアクセスし、「create new secret key」ボタンをクリックします。キーに名前を付けて「Create secret key」を押し、生成されたキーをコピーして.envファイルのOPENAI_TOKENの値として貼り付けてください。
QStashで定期リクエストを設定する
QStashに毎日1回/generateエンドポイントへリクエストを送信させるには、「Request Builder」を使用します。これはUpstashコンソール内にあります。図1に設定内容を示します。

図1:QStash Request Builder
<HOSTNAME>は、サーバーをホストしているドメインに置き換えてください。QStashが動作するには、公開アクセス可能なホスト名が必要です。
QStashが使用する署名鍵(Signing Keys)は、Upstashコンソールの「Request Builder」セクションにあります。灰色の「Signing Keys」ドロップダウンをクリックし、各キーをコピーして.envファイルの該当箇所に貼り付けてください。
Upstash Redisデータベースのデプロイ
Upstash Redisデータベースを作成するには、Upstashコンソールにアクセスし、「Create Database」ボタンをクリックします。
データベース設定を入力するダイアログが表示されます。図2に推奨設定値を示します。

図2:Upstash Redisの設定
作成が完了したら、ページを下にスクロールして「REST API」セクションを探します。「UPSTASH_REDIS_REST_URL」と「UPSTASH_REDIS_REST_TOKEN」の2つのボタンがあります。それぞれのボタンをクリックして認証情報をコピーし、.envファイルの正しい箇所に貼り付けてください。
Webサイトのテスト
ローカルマシンでWebサイトをテストするには、index.jsファイル内の「// return next()」行のコメントを解除して、QStash検証ミドルウェアを無効化します。
以下のコマンドでサーバーを起動します。
$ node .
ブラウザで/ルートを開くと、まだジョークが生成されていないため、「History Jokes for Every Day」という見出しだけが表示されるはずです。
ジョークを生成するには、/generateエンドポイントにPOSTリクエストを送信する必要があります。以下のコマンドで実行できます。
$ curl -X POST https://localhost/generate
ブラウザのページを更新すると、図3のようなジョークが表示されるはずです。

図3:ジョークWebサイト
このジョークはUpstash Redisにキャッシュされているため、誰かがサイトを訪れてもOpenAI APIへのリクエストは発生しません。これにより、サブ秒レベルの高速なレスポンスを実現しながら、コストも大幅に節約できます。
まとめ
AI APIは、私たちの知るインターネットを変革しうる多用途なツールですが、現時点では低速かつ高コストです。これらのAPIを活用するうえでキャッシュは不可欠であり、Upstash Redisを使えば、わずか数回のクリックでスタックにキャッシュ機能を追加できます。
-
Redis PUNSUBSCRIBEコマンドの使い方 – Pub/Subで複数パターンの購読を解除する方法
このチュートリアルでは、redis-cliを使ってRedisのメッセージブローカー(Pub/Sub)システム上で、複数のパターンから購読解除(退会)する方法について解説します。 PUNSUBSCRIBEコマンドとは PUNSUBSCRIBEコマンドは、クライアントを1つ以上の指定したパターンから購読解除するために使用します。パターンを何も指定しなかった場合は、そのクライアントが現在購読中のすべてのパターンから一括で解除されます。解除されたパターンの数だけ応答メッセージが返されます。 構文 redis host:post> PUNSUBSCRIBE <pattern> [ &l
-
Node.js・Socket.IO・Redisで構築するスケーラブルなリアルタイム対戦型三目並べ(Tic-Tac-Toe)ゲーム
このチュートリアルでは、Node.js、Socket.IO、Redisを組み合わせて、リアルタイムのマルチプレイヤー対応三目並べ(Tic-Tac-Toe)ゲームを作成します。2人のプレイヤーが異なるブラウザタブから接続し、交互に手を打ちながら、盤面の変化をリアルタイムに確認できるゲームです。Redisを活用して複数のWebSocketサーバー間でゲーム状態を同期することで、スケーラブルなアプリケーションを実現します。 この記事を読み終える頃には、実際に動作するリアルタイムゲームが完成しているだけでなく、WebSocketとRedisを使ってスケーラブルなリアルタイムアプリケーションを構築するた