CloudflareSaaS / FaaSTypeScript

Cloudflare Workersで、OpenAIなどの外部APIを呼び出す方法

npmにSDKを公開しているSaaSは数おおくありますが、Workersの性質上全てが動作するわけではありません。 今回はOpenAIのAPIを例に、SDKを使わずに外部APIを利用する方法を紹介します。 SDKを使わず […]

広告ここから
広告ここまで

npmにSDKを公開しているSaaSは数おおくありますが、Workersの性質上全てが動作するわけではありません。

今回はOpenAIのAPIを例に、SDKを使わずに外部APIを利用する方法を紹介します。

SDKを使わずに、fetch APIを利用する

SDKを利用してエラーが発生した場合、Fetch APIを利用する代替策をとります。

例えば次のようなクラスを作成しましょう。

export class OpenAI {
    private readonly apiURL = "https://api.openai.com"
    private readonly apiVersion = "v1"
    private readonly headers: HeadersInit = {}

    public constructor(apiKey: string) {
        this.headers = {
            "Authorization": `Bearer ${apiKey}`,
            "Content-Type": "application/json"
        }
    }

    private _createOpenAPIRequestURL(path: string) {
        const paths = `${this.apiVersion}/${path}`.replace(/\/\//, "/")
        return `${this.apiURL}/${paths}`
    }

    private async _fetch(input: RequestInfo, init: RequestInit) {
        init.headers = {
            ...this.headers,
            ...this.headers,
        }
        const response = await fetch(input, init)
        return response.json()
    }
}

TypeScriptの型情報だけSDKから取り出すことも

一部のSDKでは、型情報やヘルパー関数などだけを利用できるケースがあります。

もしwrangler devで動かしてみてエラーが出ない様子であれば、次のように型情報などだけをSDKから借りれます。

import { CreateChatCompletionRequest, CreateChatCompletionResponse } from "openai";


export class OpenAI {
    private readonly apiURL = "https://api.openai.com"
    private readonly apiVersion = "v1"
    private readonly headers: HeadersInit = {}

    public constructor(apiKey: string) {
        this.headers = {
            "Authorization": `Bearer ${apiKey}`,
            "Content-Type": "application/json"
        }
    }

    private _createOpenAPIRequestURL(path: string) {
        const paths = `${this.apiVersion}/${path}`.replace(/\/\//, "/")
        return `${this.apiURL}/${paths}`
    }

    private async _fetch(input: RequestInfo, init: RequestInit) {
        init.headers = {
            ...this.headers,
            ...this.headers,
        }
        const response = await fetch(input, init)
        return response.json()
    }

    public async createChatCompletion(body: CreateChatCompletionRequest): Promise<CreateChatCompletionResponse> {
        const result = await this._fetch(this._createOpenAPIRequestURL('chat/completions'), {
            method: "POST",
            body: JSON.stringify(body)
        })
        return result as CreateChatCompletionResponse
    }
}

abstractクラスを作って、汎用的に使う例

OpenAI以外にも、Basic認証を利用するAPIであれば同様の実装で対応できます。

そこで次のようなabstract classを作る方法もとれます。

import { CreateChatCompletionRequest, CreateChatCompletionResponse } from "openai";


export abstract class FetchCLient {
    protected readonly baseURL: string
    protected readonly headers: HeadersInit = {}

    public constructor(baseURL: string, apiKey: string) {
        this.baseURL = baseURL
        this.headers = {
            "Authorization": `Bearer ${apiKey}`,
            "Content-Type": "application/json"
        }
    }

    protected async _fetch<R = unknown>(input: RequestInfo, init: RequestInit): Promise<R> {
        init.headers = {
            ...this.headers,
            ...this.headers,
        }
        const response = await fetch(input, init)
        return response.json()
    }
}

利用したいAPIに応じて、先ほどのクラスをextendsして利用します。


export class OpenAI extends FetchCLient {
    public constructor(apiKey: string) {
        super("https://api.openai.com/v1", apiKey)
    }
    public async createChatCompletion(body: CreateChatCompletionRequest): Promise<CreateChatCompletionResponse> {
        const result = await this._fetch<CreateChatCompletionResponse>(`${this.baseURL}/chat/completions`, {
            method: "POST",
            body: JSON.stringify(body)
        })
        return result
    }

}

この方法であれば、複数の外部APIを利用する開発でもコードの量を減らすことができます。

より汎用的にするならば・・・

APIの認証方法はBasic認証以外にも複数存在します。

そのため、認証処理部分だけ別途interfaceclassを作成して抽象化すると、より幅広く利用できます。

class AnySaaS extends FetchClient {
  constructor(jwtToken: string) {
    super(“https://api.example.com”, {
      authorizer: new JWTAuthorizer(),
      token: jwtToken,
    })
  }
}

みたいな感じでしょうか。

今後Basic認証以外の方法も試す必要が出てきた場合には、トライしてみたいと思います。

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts