🌟

開発時にモック版OpenAI APIサーバーを使用する

2023/04/17に公開

開発中にChat completion APIやEmbeddings APIを過渡に呼び出して課金されてしまうことを回避します。

また自動テスト内でOpenAI APIを呼び出す時も有効です。

https://github.com/openai/openai-openapi/

OpenAI公式リポジトリにOpenAPI仕様書が公開されているのでStoplight Prismでモックサーバーを実行します。

> npm install -g @stoplight/prism-cli
> prism mock https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml
curl -X "POST" "http://127.0.0.1:4010/chat/completions" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "model": "gpt-5",
  "messages": [
    {
      "content": "hello",
      "role": "user"
    }
  ]
}'
{"id":"string","object":"string","created":0,"model":"string","choices":[{"index":0,"message":{"role":"system","content":"string"},"finish_reason":"string"}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}

レスポンスを書き換えたい時はopenapi.yamlをチェックアウトし、exampleを追加して調整します。

diff --git a/openapi.yaml b/openapi.yaml
index 8962ccc..4ae9762 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -2313,6 +2313,7 @@ components:
         content:
           type: string
           description: The contents of the message
+          example: AIは人間の言語理解を助ける素晴らしいツールです。質問に答えたり、情報を提供することで日常生活を支援します。この応答は、AIによって生成されたサンプルです。
       required: 
         - role
         - content

OpenAIのPythonライブラリからAPIのアクセス先をモックに置き換えてみます。モックであることが明らかなように gpt-5 といいうモデルを指定してみます。

import openai

openai.api_base = "http://localhost:4010"

response = openai.ChatCompletion.create(model='gpt-5', messages=[
    {'role': 'user', 'content': 'これはモックサーバーへのリクエストです。何か返事をください。'},
])

print(response['choices'][0]['message']['content'])
> python main.py 
AIは人間の言語理解を助ける素晴らしいツールです。質問に答えたり、情報を提供することで日常生活を支援します。この応答は、AIによって生成されたサンプルです。

以上でChat completion APIのレスポンスを固定できたので、それを使った連携部分の開発ができるようになりました。

応用編(1): 動的レスポンスが返したい

OpenAPI Generatorを使ってサーバーのソースコードを生成するとGPTCacheのような独自のキャッシュレイヤーも構築できそうです。

ただ私が試したところまずは仕様書の記述を修正する必要がありそうでした

❯ openapi-generator-cli generate -i openapi.yaml  -g python-flask -o openai-api-mock

Did set selected version to 6.5.0
Exception in thread "main" org.openapitools.codegen.SpecValidationException: There were issues with the specification. The option can be disabled via validateSpec (Maven/Gradle) or --skip-validate-spec (CLI).
 | Error count: 5, Warning count: 0
Errors: 
        -attribute components.schemas.CreateFineTuneRequest.default is not of type `array`
        -attribute components.schemas.CreateAnswerRequest.default is not of type `object`
        -attribute components.schemas.CreateCompletionRequest.default is not of type `object`
        -attribute components.schemas.CreateChatCompletionRequest.default is not of type `object`
        -attribute components.schemas.CreateClassificationRequest.default is not of type `object`

        at org.openapitools.codegen.config.CodegenConfigurator.toContext(CodegenConfigurator.java:620)
        at org.openapitools.codegen.config.CodegenConfigurator.toClientOptInput(CodegenConfigurator.java:647)
        at org.openapitools.codegen.cmd.Generate.execute(Generate.java:479)
        at org.openapitools.codegen.cmd.OpenApiGeneratorCommand.run(OpenApiGeneratorCommand.java:32)
        at org.openapitools.codegen.OpenAPIGenerator.main(OpenAPIGenerator.java:66)

応用編(2): Embeddings APIを置き換えたい

Embeddings APIのtext-embedding-ada-002モデルによって変換された埋め込み表現をインデックスとして保持しておく処理をよく作りますが、開発時は課金対象のEmbeddings APIを呼ばずに別のモデルを使いたいというケースがあります。

その場合、sentence-transformersを使ってHugging Faceで公開されているモデルを利用するのが良さそうです。

日本語インデックス向けにはoshizo/sbert-jsnli-luke-japanese-base-liteを使う例をよく見かけます。

https://note.com/oshizo/n/n137aaa2c29d4

Discussion