Supabase の Edge Functions で OpenAI API を叩く関数を実装する

Published
2023-04-30
Tags

Supabase と Deno の入門をしたくて、OpenAI APIを叩くサーバーレス関数を Supabase Edge Functions で実装してみたという記事。

この記事の内容としては、以下のYouTube動画の内容とほぼ同じ。

完成形のリポジトリはこちら

目次

Supabase とは?

Firebaseに似たオープンソースのクラウドサービス。データベース、認証、ストレージなど、多くの機能を提供している。Firebaseと比較して、よりカスタマイズ性が高く、価格もリーズナブル。

前準備

今回は、OpenAI の API を使用するので事前にAPIキーを作成しておく必要がある。詳細は省く。

Supabaseのセットアップ方法

  1. SupabaseのWebサイトにアクセスし、アカウントを作成する。
  2. Dashboardからプロジェクトを作成する。
  3. プロジェクトの設定画面で、APIキーを取得する。
  4. Edge Functionsを有効にする。
  5. 必要に応じて、データベースや認証、ストレージなどの機能を有効にする。
  6. 以上で、Supabaseのセットアップが完了する。


Supabase CLIのインストール

Supabase CLIをインストールする。

$ npm install -g supabase

Supabaseプロジェクトの作成

Supabaseにログインし、プロジェクトを作成する。(プロジェクト名は任意)

$ supabase init openai-edge-functions

Edge Functionsの作成

コマンド実行後に プロジェクト直下に supabase/functions/openai/index.ts が作成される。

$ supabase functions new openai

index.ts

// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.

import { serve } from "https://deno.land/[email protected]/http/server.ts"

console.log("Hello from Functions!")

serve(async (req) => {
  const { name } = await req.json()
  const data = {
    message: `Hello ${name}!`,
  }

  return new Response(
    JSON.stringify(data),
    { headers: { "Content-Type": "application/json" } },
  )
})

// To invoke:
// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
//   --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
//   --header 'Content-Type: application/json' \
//   --data '{"name":"Functions"}'


セットアップはこれで完了。セットアップ完了までのコミットはこちら

OpenAI APIを叩く関数の実装

OpenAI の APIキーを設定

プロジェクト直下に .env.local ファイルを作成して、環境変数にAPIキーを設定する。

※ APIキーなので、 .env.local.gitignore に追加しておくこと!!

OPENAI_API_KEY=xxxx

VSCode上での Denoセットアップ

ちなみに、VSCodeを使用して Deno を実装する場合は、以下の拡張が必須レベルで便利である。

この拡張機能を使うと、後述する Deno によるライブラリインポートのキャッシュ取得とエディター上のコード補完が可能となる。

筆者は、まずプロジェクト直下に以下の .vscode/setting.json を用意した。

 {
  "deno.enable": true,
  "deno.unstable": false
}

この後、Macなら、「Cmd + Shift + P」 で表示されたフォームに “Deno cache” と入力したら Deno: Cache Dependencies と表示されるのでそちらをクリックすると、VSCode上でライブラリがキャッシュとして取得され、エディター上でコード補完ができるようになる。

関数の実装

ここからいよいよ OpenAI Completion API を使って、LLM と対話ができる関数を Deno で実装していく。

まずは、ライブラリインポート部分を追加

import "https://deno.land/x/[email protected]/mod.ts"
import { getLogger } from "https://deno.land/[email protected]/log/mod.ts"
import { serve } from "https://deno.land/[email protected]/http/server.ts"
import { Configuration, OpenAIApi } from "https://esm.sh/[email protected]"

OpenAI APIクライアントの初期化

const configuration = new Configuration({
  apiKey: Deno.env.get("OPENAI_API_KEY"),
});
const openai = new OpenAIApi(configuration);

OpenAI APIをリクエストする通信部分の追加

serve(async (req) => {
  const { query } = await req.json();
  try {
    const response = await openai.createCompletion({
      model: "text-davinci-003",
      prompt: query,
      max_tokens: 256,
      temperature: 0,
    });

    const {
      data: {
        id,
        choices: [{ text }]
      },
    } = response;

    return new Response(
      JSON.stringify({ id, text }),
      { headers: { "Content-Type": "application/json" } },
    )
  } catch (error) {
    const logger = getLogger()
    logger.error(error)

    return new Response(
      JSON.stringify({ error }),
      { headers: { "Content-Type": "application/json" } },
    )
  }
})

実際の追加実装はこちら

ローカルで検証

実際にLLMと対話できるかローカル上で、検証する。

Supabase は、ローカル上で今回作成した関数をエミュレートできるので、実際に何か質問文をクエリパラメーターにしてリクエストしてみる。

まずは、関数を起動する

$ supabase start
Setting up Edge Functions runtime...
Seeding data supabase/seed.sql...
Started supabase local development setup.

         API URL: http://localhost:54321
     GraphQL URL: http://localhost:54321/graphql/v1
          DB URL: postgresql://postgres:postgres@localhost:54322/postgres
      Studio URL: http://localhost:54323
    Inbucket URL: http://localhost:54324
      JWT secret: super-secret-jwt-token-with-at-least-32-characters-long
        anon key: xxx
service_role key: xxx

$ supabase functions serve --env-file .env.local openai
Starting supabase/functions/openai
Serving supabase/functions/openai
Watcher Process started.
Check file:///home/deno/functions/openai/index.ts
Listening on <http://localhost:8000/>

LLMと対話できるか curl で叩いて検証

$ curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
  --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
  --header 'Content-Type: application/json' \
  --data '{"query":"日本で一番面積が大きい都道府県はどこですか?"}'
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
date: Sun, 30 Apr 2023 09:08:09 GMT
vary: Accept-Encoding
X-Kong-Upstream-Latency: 2178
X-Kong-Proxy-Latency: 7
Via: kong/2.8.1

{"id":"cmpl-7AxKKdp1u6RlgNQut7u0wE8Vgd1xa","text":"\n\n日本で一番面積が大きい都道府県は、北海道です。"}%

できた 🎉