📘

Cloudflare WorkersでHono+Azure OpenAI Serviceを動かすメモ

2023/09/17に公開

前準備

気がむいたら書きます

  • Cloudflare Workersのアカウント作成
  • Azure OpenAI APIのアカウントとAPIキーの準備
  • wranglerなどは既にインストール済
  • wrangler loginは既に実施済

雛形プロジェクトの作成

$ npm create cloudflare@latest

using create-cloudflare version 2.2.3

╭ Create an application with Cloudflare Step 1 of 3
│
├ In which directory do you want to create your application?

プロジェクト名を入力。同名のディレクトリが作られる。

│ dir ./my-project
│
╰ What type of application do you want to create?

「Website or web app」→「Hono」を選択。h/jで上下移動できる。

│ type Website or web app
│
├ Which development framework do you want to use?
│ framework Hono
│
╰ Continue with Hono via `npx create-hono@0.2.6 my-project --template cloudflare-workers`


create-hono version 0.2.6
cloned honojs/starter#main to /Users/hnw/src/my-project
✔ Copied project files

╭ Configuring your application for Cloudflare Step 2 of 3
│
├ Installing wrangler A command line tool for building Cloudflare Workers
│ installed via `npm install wrangler --save-dev`
│
├ Adding command scripts for development and deployment
│ added commands to `package.json`
│
╰ Do you want to use git for version control?

ここまで結構待つ。ここでYesにするとローカルリポジトリに勝手にcommitされる。

│ yes git
│
├ Committing new files
│ git commit
│
╰ Application configured

╭ Deploy with Cloudflare Step 3 of 3
│
╰ Do you want to deploy your application?

ここでYesにするといきなりCloudflare Workersにデプロイされて全世界公開される。恐ろしい子…

│ yes deploy via `npm run deploy`
│
├ Logging into Cloudflare checking authentication status
│ logged in
│
├ Selecting Cloudflare account retrieving accounts
│ account ******@***.jp's Account
│
├ Deploying your application
│ deployed via `npm run deploy`
│
├  SUCCESS  View your deployed application at https://my-app.******.workers.dev
│
│ Navigate to the new directory cd my-project
│ Run the development server npm run dev
│ Deploy your application npm run deploy
│ Read the documentation https://developers.cloudflare.com/workers
│ Stuck? Join us at https://discord.gg/cloudflaredev
│
├ Waiting for DNS to propagate
│ DNS propagation complete.
│
├ Waiting for deployment to become available
│ deployment is ready at: https://my-app.******.workers.dev
│
├ Opening browser
│
╰ See you again soon!

ブラウザに表示されたページが今デプロイしたアプリケーションです。
デフォルトのWorker名はmy-appになってるので、気に入らなかったらwrangler.tomlを変更しましょう。

Azure OpenAI Service用のライブラリを動かしてみる

Azure OpenAI Serviceを動かすのに、APIに直接アクセスしてもいいのですが、公式ライブラリ @azure/openai を使ってみましょう。
Cloudflare WorkersはJSエンジンとしてNode.jsでなくV8を採用しているため、Node.js用ライブラリが動く保証はないのですが、まあやってみましょう。

$ cd my-project
$ npm install @azure/openai

@azure/openaiが正常動作する前提でコードを書いていきます。

src/index.ts
import { Hono } from 'hono'
import { OpenAIClient, AzureKeyCredential } from "@azure/openai"

const messages = [
  { role: "system", content: "あなたは有能なアシスタントです。英語で考えた内容を日本語で猫のように喋ります。" },
  { role: "user", content: "Can you help me?" },
  { role: "assistant", content: "ハイだニャ。なんでも聞くニャ。" },
  { role: "user", content: "What's the best way to train a parrot?" },
];

const app = new Hono()

app.get('/', async (c) => {
  const credential = new AzureKeyCredential(c.env!.AZURE_API_KEY as string);
  const client = new OpenAIClient(c.env!.ENDPOINT as string, credential);
  const deploymentId = "gpt-35-turbo";
  const result = await client.getChatCompletions(deploymentId, messages);
  for (const choice of result.choices) {
    return c.text(`${choice.message?.content}`);
  }
  return c.text('Hello Hono!')
})

export default app

環境変数っぽい値は.dev.varsに書いて使います。本ファイルはバージョン管理の対象外です。

.dev.vars
ENDPOINT = https://******/
AZURE_API_KEY = ******

上記コードを試しに動かしてみましょう。

$ npm run dev

> dev
> wrangler dev src/index.ts

 ⛅️ wrangler 3.8.0
------------------
wrangler dev now uses local mode by default, powered by 🔥 Miniflare and 👷 workerd.
To run an edge preview session for your Worker, use wrangler dev --remote
⎔ Starting local server...
[mf:inf] Ready on http://0.0.0.0:8787
[mf:inf] - http://127.0.0.1:8787
[mf:inf] - http://192.168.1.105:8787
⎔ Reloading local server...
[mf:inf] Updated and ready on http://0.0.0.0:8787
[mf:inf] - http://127.0.0.1:8787
[mf:inf] - http://192.168.1.105:8787
Trace: RestError: Error sending request: The 'credentials' field on 'RequestInitializerDict' is not implemented.
    at getError (index.js:3318:12)
    at FetchHttpClient.sendRequest (index.js:3229:13) {
  name: RestError,
  code: REQUEST_SEND_ERROR,
  statusCode: undefined,
  request: PipelineRequestImpl,
  response: undefined
  ...
}
    at logConsoleMessage (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:127017:25)
    at WebSocket2.<anonymous> (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:126712:13)
    at callListener (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:107229:18)
    at WebSocket2.onMessage (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:107164:13)
    at WebSocket2.emit (node:events:526:35)
    at Receiver2.receiverOnMessage (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:108232:24)
    at Receiver2.emit (node:events:514:28)
    at Receiver2.dataMessage (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:106514:18)
    at Receiver2.getData (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:106443:21)
    at Receiver2.startLoop (/Users/hnw/src/my-project/node_modules/wrangler/wrangler-dist/cli.js:106184:26)
[mf:inf] GET / 500 Internal Server Error (12ms)

はい、エラーが出ました。下記URLによれば、workerdで実装しているfetchの一部機能が未実装のようです。

行儀は悪いんですが、node_modules以下のファイルを直接編集して2行コメントアウトするとCloudflare Workersでも動くようになります。

@azure/core-rest-pipeline/dist-esm/src/fetchHttpClient.js(抜粋)
        const headers = buildFetchHeaders(request.headers);
        const { streaming, body: requestBody } = buildRequestBody(request);
        const requestInit = {
            body: requestBody,
            method: request.method,
            headers: headers,
            signal: abortController.signal,
//            credentials: request.withCredentials ? "include" : "same-origin",
//            cache: "no-store",
        };

Cloudflare Wokersにデプロイ

開発中は.dev.varsに書いていた値を暗号化してアップロードして使います。

$ npx wrangler secret put ENDPOINT
 ⛅️ wrangler 3.8.0
------------------
✔ Enter a secret value: … **********************************************************
🌀 Creating the secret for the Worker "my-app"
✨ Success! Uploaded secret ENDPOINT

$ npx wrangler secret put AZURE_API_KEY
 ⛅️ wrangler 3.8.0
------------------
✔ Enter a secret value: … ********************************************************************************************************************************
🌀 Creating the secret for the Worker "my-app"
✨ Success! Uploaded secret AZURE_API_KEY

ではデプロイしてみましょう。

$ npm run deploy

> deploy
> wrangler deploy --minify src/index.ts

 ⛅️ wrangler 3.8.0
------------------
Total Upload: 80.74 KiB / gzip: 25.64 KiB
Uploaded my-app (0.96 sec)
Published my-app (0.32 sec)
  https://my-app.******.workers.dev
Current Deployment ID: ******

無事アップロードできたので、URLにアクセスしてみましょう。

無事に動きましたね。
リクエストを返すまで15秒くらいかかる(!)んですが、WorkersのCPU timeは5ms程度のようです。

Cloudflare WorkersはCPU timeの制約が厳しいんですが、この程度なら十分動かせそうですね。

まとめ

  • Cloudflare Workers上でAzure OpenAI Service用ライブラリを動かしてみた
    • ライブラリ @azure/openai は少しだけ修正が必要だった
    • workerdがもう少し頑張ってくれたら動くライブラリがかなり増えそう
  • Cloudflare Workersでそれなりの規模のNode.jsライブラリを使っても現実的に動くことがわかった
  • Node.js用ライブラリを使っても今回のアプリ程度の規模ならfree accountの上限値を超えない模様
    • 圧縮後サイズ25.64 KB(上限1MB)
    • CPU時間5ms(上限10ms)

Discussion