CloudflareのAI Gateway + Workersで記事の自動翻訳APIを作る

2024.01.05

どうも、ベルリンオフィスの小西です。

昨年発表されたCloudflare AI Gateway + OpenAIを使って自動翻訳APIを作ってみました。

Cloudflare AI Gatewayとは

Cloudflare AI Gatewayは、自前のアプリケーションからOpenAIなどのAPIに接続する際、リクエスト/レスポンスを仲介してくれるゲートウェイサービスです。

AI Gatewayが発行するエンドポイントを経由することで、キャッシュ、レート制限、リクエストの再試行、AIモデル間のフォールバック、使用状況の分析、リアルタイムログなどの機能を簡単に追加できます。

本記事執筆時点で対応しているAIモデルプロバイダーは下記です。

  • Workers AI
  • Amazon Bedrock
  • Azure OpenAI
  • OpenAI
  • HuggingFace
  • Replicate

今回やりたいこと

今回は、CMSなどからの利用を想定した、記事執筆時に自動翻訳をリクエストできるAPIを作ってみたいと思います。

ユーザーがリクエストするAPI自体はCloudflare Workersで作成し、そのエンドポイントに対するPOSTリクエストbodyで記事本文を渡し、指定した言語で翻訳した結果を返却するようにします。

WorkersからはOpenAIに直接リクエストするのではなく、API Gatewayにリクエストするようにします。

Cloudflare AI Gatewayの有効化

まずはAI Gatewayを有効化します。 Cloudflareコンソールの左ナビゲーションから [AI Gateway] に移動します。

[Create Gateway] から新規作成します。

任意の名称とスラッグを入力して作成します。

その後発行されるAPI Endpointを取得します。 今回は [OpenAI] を選択してエンドポイントをコピーします。

Cloudflare Workersの準備

Workersプロジェクトを新規作成します。

% npx wrangler init

In which directory do you want to create your application?
dir ./my-sample-app

What type of application do you want to create?
type "Hello World" Worker

Do you want to use TypeScript?
yes typescript

Do you want to use TypeScript?
yes typescript

Do you want to use git for version control?
yes git

Do you want to deploy your application?
no deploy via `npm run deploy`

APPLICATION CREATED

OpenAIパッケージをインストールします。

% cd my-sample-app
% npm i openai

接続用の環境変数のために .dev.vars をルートディレクトリに作成します。 ※実際の値に置き換えてください。

OPENAI_API_KEY=Your_OpenAI_API_Key
OPENAI_ENDPOINT=https://gateway.ai.cloudflare.com/v1/.../.../openai

Workersの処理を記述します。

src/worker.ts

import OpenAI from "openai";

export interface Env {
    OPENAI_API_KEY: string;
    OPENAI_ENDPOINT: string;
};

export interface JsonBody {
    content: string;
}

const promptTranslate = (content: string, lang: string) => {
    const prompt = `
        Article: ${content}
        Translate the provided article above to ${lang}.
        Do not miss any information in the article.
        Do not add any information to the article.
        Your answer format must be only the translated text itself.
    `;
    return prompt;
}

export default {
    async fetch(request: Request, env: Env): Promise<Response> {
        try {
            if (request.method !== "POST") {
              return new Response("Only POST requests are allowed", { status: 405 });
            }

            const contentType = request.headers.get("content-type");
            if (!contentType?.includes("application/json")) {
                return new Response("Only JSON content-type is allowed", { status: 415 });
            }

            const { pathname, search } = new URL(request.url);
            const params = new URLSearchParams(search);
            const { content }: JsonBody = await request.json();
            const openai = new OpenAI({
                apiKey: env.OPENAI_API_KEY,
                baseURL: env.OPENAI_ENDPOINT,
            });

            let prompt = "";
            if (/^\/translate\/?$/.test(pathname)) {
                const lang = params.get("lang") || "en";
                prompt = promptTranslate(content, lang);
            } else {
                return new Response('Unknown URL pathname.', { status: 404 });
            }

            const chatCompletion = await openai.chat.completions.create({
                model: "gpt-3.5-turbo-0613",
                messages: [{role: "user", content: prompt}],
                max_tokens: 100,
            });

            const response = chatCompletion.choices[0].message.content;

            return new Response(JSON.stringify({ response }), { headers: { 'Content-Type': 'application/json' } });
        } catch (err) {
          return new Response(`Error: ${err.message || ""}`, { status: 500 });
        }
    },
};
  • 今後リクエストパスごとに異なる機能を持たせる想定で、今回は /translate パスでリクエストを受け取って処理しています。
    • 例えば /summary パスでは異なるプロンプトを投げて記事の要約を提供する、なども可能。
  • Workersからのリクエスト先はAI Gateway(gateway.ai.cloudflare.com)です。
  • ユーザーからのリクエストURLのlangパラメータで翻訳したい言語を指定します。指定されない場合は英語になります。

ローカルで動かしてみる

ローカルでWorkersを立ち上げる。

% npx wrangler dev

テストPOSTリクエストを送ってみる。

% curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"content": "私の夢はお腹いっぱいご飯を食べることです。"}' \
  "http://localhost:8787/translate?lang=en"

レスポンス。成功したようです。

{"response":"My dream is to eat a full meal."}

便利な機能

ユニバーサルエンドポイント

上記の例ではOpenAIにリクエストするエンドポイントを利用していますが、複数モデルへまとめて接続できるユニバーサルエンドポイントも発行できます。

リクエストが失敗した場合に別モデルに対して再試行/フォールバックすることが可能になります。

下記の例では、まず huggingface へのアクセスを試行し、失敗した場合に OpenAI へアクセスするようになります。

curl https://gateway.ai.cloudflare.com/v1/ACCOUNT_TAG/GATEWAY -X POST \
  --header 'Content-Type: application/json' \
  --data '[
    {
     "provider": "huggingface",
     "endpoint": "bigcode/starcoder",
     "headers": {
        "Authorization": "Bearer $TOKEN",
        "Content-Type": "application/json"
       },
     "query": {
         "input": "What is Cloudflare?"
     }
    },
    {
     "provider": "openai",
     "endpoint": "chat/completions",
       "headers": {
        "Authorization": "Bearer $TOKEN",
        "Content-Type": "application/json"
       },
     "query": {
         "model": "gpt-3.5-turbo",
         "stream": true,
         "messages": [
             {
                 "role": "user",
                 "content": "What is Cloudflare?"
             }
         ]
     }
    }
]'

キャッシュの設定

AI Gatewayは同一のリクエストに対するAIプロバイダーからの回答をキャッシュすることができ、それにより2回目以降の回答をコスト不要で返却することができます。 キャッシュのTTL(有効期限)も指定が可能です。

使用状況のアナリティクス

リクエスト数、キャッシュ、モデルごとのトークン使用量、エラーなどを分析できます。

コンソールからの確認、もしくはGraphQLでも使用状況データをクエリできます。

なおコスト値は現在OpenAI GPT限定で、リクエストで送受信されたトークン数に基づく推定値とのことです。

レート制限の設定

下記の例では 50リクエスト/分 までに制限しています。

まとめ

まだBetaのCloudflare AI Gatewayですが、すでに色々な機能が開放されており、今後対応するLLMも増えていくそうです。

今回はローカルで動かしただけですが、Workersでデプロイしてカスタムドメインを充て、Workers API自体にWAFやアクセス制限をかけたりもできます。

クラスメソッドはCloudflareのパートナーです。Cloudflareのご利用に興味のある方は是非一度クラスメソッドにご相談ください

参照情報