見出し画像

OpenAI APIのログを簡単に管理する

こんにちは、ニケです。
皆さん、OpenAI APIは使用されてますでしょうか。

2023年はGPT4の登場もあって様々なAIサービスがリリースされました。
この記事を読んでいる方の中にも、大小の規模を問わずAIアプリを作ってみた経験がある方は多いのではないでしょうか?

そして多くの方が利用しているのはOpenAIのAPIだと思います。一番メジャーですからね。

ところで、アプリ開発で重要なことってなんでしょう?
アプリ開発にはユーザーのニーズ理解、効率的なコーディング、セキュリティ対策、そしてユーザー体験の最適化など、様々な重要な要素があります。

しかし、どれも大事ですが、私は特にログ管理が大事だと思っています。
ログ管理により、アプリの不具合を早期に発見し改善することができ、そしてこれはアプリの安定性を保つだけでなく、最終的にユーザー満足度を高めるためにも不可欠です。

…そんな皆さん、朗報ですよ。

ということで今回は、OpenAI APIのリバース プロキシとして機能するPythonライブラリをうえぞうさんが公開してくれたので、その内のログ管理機能を試してみたという記事です。

始め方

READMEに載っているやり方をなぞります。

インストール

パッケージ化されているのでgitリポジトリをクローンする必要はありません。適当な環境で実行してください。

pip install aiproxy-python

サーバー起動

このコマンドを実行したフォルダにDBファイルが出力されます。

python -m aiproxy [--host host] [--port port] [--openai_api_key OPENAI_API_KEY]
 
# 特に追加設定が必要ないなら下記のみでOK
# デフォルトは、http://127.0.0.1:8000
 
python -m aiproxy

使用方法

あとは自身のAIアプリ内で、OpenAI APIのClientの設定を下記のようにします。

import openai

client = openai.Client(base_url="http://127.0.0.1:8000/", api_key="YOUR_API_KEY")
resp = client.chat.completions.create(
  model="gpt-3.5-turbo",
  messages=[{"role": "user", "content": "hello!"}]
)

print(resp)

OpenAIクラスを使う場合はこちら

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/", api_key="YOUR_API_KEY")
resp = client.chat.completions.create(
  model="gpt-3.5-turbo",
  messages=[{"role": "user", "content": "hello!"}]
)

print(resp)

これだけです。

出力ログ確認

上記のpythonスクリプトを実行すると、aproxy.db というファイルが作成されます。
SQLiteのデータベースファイルなので、適切な方法で開きます。

SQLiteファイルの開き方わからん、って人は aproxy.db ファイルと同じフォルダ内で下記のスクリプトを実行してください。

import sqlite3
import csv

# データベースに接続
conn = sqlite3.connect('tests/aiproxy.db')
c = conn.cursor()

# データベース内の全てのテーブル名を取得
c.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = c.fetchall()

for table_name in tables:
    table_name = table_name[0]
    print(f"Processing Table: {table_name}")
    
    # テーブルのカラム名を取得
    c.execute(f"PRAGMA table_info({table_name});")
    columns = c.fetchall()
    column_names = [column[1] for column in columns]

    # 各テーブルから全てのデータを取得
    c.execute(f"SELECT * FROM {table_name};")
    rows = c.fetchall()

    # データをCSVファイルに出力
    with open(f'{table_name}.csv', 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(column_names)  # カラム名を出力
        writer.writerows(rows)  # データを出力

# 接続を閉じる
conn.close()

accesslog.csv というcsvファイルが出力されるので中身を確認します。

id,request_id,created_at,direction,content,function_call,tool_calls,raw_body,raw_headers,model,prompt_tokens,completion_tokens,request_time,request_time_api
1,c049cdc5-9c55-4584-89ca-6ed42ab0b546,2023-12-09 21:35:26.500663,request,hello!,,,"{""messages"": [{""role"": ""user"", ""content"": ""hello!""}], ""model"": ""gpt-3.5-turbo""}","{""host"": ""127.0.0.1:8000"", ""accept-encoding"": ""gzip, deflate, br"", ""connection"": ""keep-alive"", ""accept"": ""application/json"", ""content-type"": ""application/json"", ""user-agent"": ""OpenAI/Python 1.3.8"", ""x-stainless-lang"": ""python"", ""x-stainless-package-version"": ""1.3.8"", ""x-stainless-os"": ""MacOS"", ""x-stainless-arch"": ""arm64"", ""x-stainless-runtime"": ""CPython"", ""x-stainless-runtime-version"": ""3.9.13"", ""authorization"": ""Bearer sk-Ko*****Qn"", ""x-stainless-async"": ""false"", ""content-length"": ""79""}",gpt-3.5-turbo,,,,
2,c049cdc5-9c55-4584-89ca-6ed42ab0b546,2023-12-09 21:35:27.513504,response,Hello! How can I assist you today?,,,"{""id"": ""chatcmpl-8TzGoUOVkH6CN6MahttTkXOMhHRv5"", ""choices"": [{""finish_reason"": ""stop"", ""index"": 0, ""message"": {""content"": ""Hello! How can I assist you today?"", ""role"": ""assistant"", ""function_call"": null, ""tool_calls"": null}}], ""created"": 1702157726, ""model"": ""gpt-3.5-turbo-0613"", ""object"": ""chat.completion"", ""system_fingerprint"": null, ""usage"": {""completion_tokens"": 9, ""prompt_tokens"": 9, ""total_tokens"": 18}}",,gpt-3.5-turbo-0613,9,9,1.033057689666748,1.032876968383789

ぱっと見でわかりやすいようにスクショも置いておきますね。
Id: 1がユーザー入力、Id: 2がアシスタントの入力です。

メッセージ内容、使用モデル、入出トークン数、リクエストにかかった時間などが保存されています。便利ですね。

ちなみにstreamにも対応しています。
下記のスクリプトを実行した結果も載せておきます。

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/", api_key="YOUR_API_KEY")
stream = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Say this is a test"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="")
id,request_id,created_at,direction,content,function_call,tool_calls,raw_body,raw_headers,model,prompt_tokens,completion_tokens,request_time,request_time_api
1,256670c1-d3a5-42c7-aa3e-709db7ac759d,2023-12-09 22:10:16.561955,request,Say this is a test,,,"{""messages"": [{""role"": ""user"", ""content"": ""Say this is a test""}], ""model"": ""gpt-4"", ""stream"": true}","{""host"": ""127.0.0.1:8000"", ""accept-encoding"": ""gzip, deflate, br"", ""connection"": ""keep-alive"", ""accept"": ""application/json"", ""content-type"": ""application/json"", ""user-agent"": ""OpenAI/Python 1.3.8"", ""x-stainless-lang"": ""python"", ""x-stainless-package-version"": ""1.3.8"", ""x-stainless-os"": ""MacOS"", ""x-stainless-arch"": ""arm64"", ""x-stainless-runtime"": ""CPython"", ""x-stainless-runtime-version"": ""3.9.13"", ""authorization"": ""Bearer sk-Ko*****Qn"", ""x-stainless-async"": ""false"", ""content-length"": ""99""}",gpt-4,,,,
2,256670c1-d3a5-42c7-aa3e-709db7ac759d,2023-12-09 22:10:18.082565,response,This is a test.,,,"[{""id"": ""chatcmpl-8TzoWLmZR43i2lCFFnPwyEBGcBZnk"", ""choices"": [{""delta"": {""content"": """", ""function_call"": null, ""role"": ""assistant"", ""tool_calls"": null}, ""finish_reason"": null, ""index"": 0}], ""created"": 1702159816, ""model"": ""gpt-4-0613"", ""object"": ""chat.completion.chunk"", ""system_fingerprint"": null}, {""id"": ""chatcmpl-8TzoWLmZR43i2lCFFnPwyEBGcBZnk"", ""choices"": [{""delta"": {""content"": ""This"", ""function_call"": null, ""role"": null, ""tool_calls"": null}, ""finish_reason"": null, ""index"": 0}], ""created"": 1702159816, ""model"": ""gpt-4-0613"", ""object"": ""chat.completion.chunk"", ""system_fingerprint"": null}, {""id"": ""chatcmpl-8TzoWLmZR43i2lCFFnPwyEBGcBZnk"", ""choices"": [{""delta"": {""content"": "" is"", ""function_call"": null, ""role"": null, ""tool_calls"": null}, ""finish_reason"": null, ""index"": 0}], ""created"": 1702159816, ""model"": ""gpt-4-0613"", ""object"": ""chat.completion.chunk"", ""system_fingerprint"": null}, {""id"": ""chatcmpl-8TzoWLmZR43i2lCFFnPwyEBGcBZnk"", ""choices"": [{""delta"": {""content"": "" a"", ""function_call"": null, ""role"": null, ""tool_calls"": null}, ""finish_reason"": null, ""index"": 0}], ""created"": 1702159816, ""model"": ""gpt-4-0613"", ""object"": ""chat.completion.chunk"", ""system_fingerprint"": null}, {""id"": ""chatcmpl-8TzoWLmZR43i2lCFFnPwyEBGcBZnk"", ""choices"": [{""delta"": {""content"": "" test"", ""function_call"": null, ""role"": null, ""tool_calls"": null}, ""finish_reason"": null, ""index"": 0}], ""created"": 1702159816, ""model"": ""gpt-4-0613"", ""object"": ""chat.completion.chunk"", ""system_fingerprint"": null}, {""id"": ""chatcmpl-8TzoWLmZR43i2lCFFnPwyEBGcBZnk"", ""choices"": [{""delta"": {""content"": ""."", ""function_call"": null, ""role"": null, ""tool_calls"": null}, ""finish_reason"": null, ""index"": 0}], ""created"": 1702159816, ""model"": ""gpt-4-0613"", ""object"": ""chat.completion.chunk"", ""system_fingerprint"": null}, {""id"": ""chatcmpl-8TzoWLmZR43i2lCFFnPwyEBGcBZnk"", ""choices"": [{""delta"": {""content"": null, ""function_call"": null, ""role"": null, ""tool_calls"": null}, ""finish_reason"": ""stop"", ""index"": 0}], ""created"": 1702159816, ""model"": ""gpt-4-0613"", ""object"": ""chat.completion.chunk"", ""system_fingerprint"": null}]",,gpt-4-0613,12,5,1.4459269046783447,1.444814920425415

その他

カスタマイズすることで下記のようなことができるようになります。

  • PostgreSQL などの他の RDBMS を使用

  • 出力カラムの追加

今回はログ出力機能だけに絞って紹介しましたが、このライブラリの実態はリバースプロキシなので、特定のリクエストを検知し出力結果をコントロールしたりすることもできるようです。

便利過ぎる…!!
ここらへんの機能まだ手元で試せていないので、追々確認していきたいと思います。

詳細な使用方法などはリポジトリに記載してあるのでこちらからどうぞ。

この記事が気に入ったらサポートをしてみませんか?