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認証以外の方法も試す必要が出てきた場合には、トライしてみたいと思います。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark