OpenAI SDKのシンプルなラッパー「cosette」を試す
READMEに従って進めてみる。Colaboratoryで。
インストール
パッケージインストール。多分パッケージングの問題だと思うけど、toolslm
という、これもAnswer.AIが開発したライブラリが必要になるので、あわせてインストール。
!pip install cosette toolslm
!pip freeze | egrep -i "cosette|toolslm"
cosette==0.0.4
toolslm==0.0.7
APIキー読み込み
import os
from google.colab import userdata
os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')
モジュールのロード&モデルの設定
Claudette同様に、Cosetteもライブラリを使用するために必要なシンボルだけをエクスポートしているので、以下でOKらしい
from claudette import *
import cosette
自分は必要なものだけロードする感じでやってみる。
モデルの一覧
from cosette import models
print(models)
('o1-preview', 'o1-mini', 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-4-32k', 'gpt-3.5-turbo', 'gpt-3.5-turbo-instruct')
特定のモデルを指定する場合はこんな感じで。
from cosette import models
model = models[3]
print(model)
gpt-4o-mini
直接指定したほうが早いし確実な気がしないでもない。
Chat
Chat
がメインのインタフェースで、これは「ステートフル」になっている。
from cosette import models, Chat
model = models[3] # gpt-4o-mini
chat = Chat(model, sp="""あなたは親切で簡潔な日本語のアシスタントです""")
chat("こんにちは。私の名前は太郎です")
こんにちは、太郎さん!お会いできて嬉しいです。今日はどんなことを話しましょうか?
Colaboratoryだと以下のような出力になっていて、詳細をクリックすると、実際のレスポンスの詳細が確認できる。
コードでやる場合には結果を変数で受け取れば良い。
r = chat("私の名前は何でしたっけ?")
print(r)
ChatCompletion(id='chatcmpl-ALptwiw3YXcZFzVqUgN5rgtadUjQX', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='あなたの名前は太郎さんです。何か他にお手伝いできることはありますか?', refusal=None, role='assistant', audio=None, function_call=None, tool_calls=None))], created=1729767760, model='gpt-4o-mini-2024-07-18', object='chat.completion', service_tier=None, system_fingerprint='fp_482c22a7bc', usage=In: 78; Out: 24; Total: 102)
上で記載したようにChatクラスは「ステートフル」なので、内部で会話履歴を持っていて、前回実行時の内容を踏まえた回答になっていることがわかる。
stream=True
でストリーミング
for o in chat("じゃああなたの名前は?", stream=True):
print(o, end='\n') # わかりやすいように改行を入れているが、実際に使う場合は''
私は
名前
を
持
って
い
ません
が
、
ア
シ
スタ
ント
として
お
手
伝
い
します
。
何
か
質問
や
相談
が
あ
れば
教
えて
ください
!
Tool use
Tool Useにも対応している。
まず、ツールで使う関数を用意する。今回はサンプルとして足し算を行う関数を用意した。
def sums(
a:int, # 足し算を行う1番目の整数
b:int=1 # 足し算を行う2番目の整数
) -> int: # 入力された整数を足し算した結果
"aとbを足し算する"
print(f"{a} と {b} を足し算します")
return a + b
なお、Cosetteは、Claudetteと同様に、内部でfastcore
という、Pythonを使いやすくするためのライブラリ?(雑)のDocument
クラスというものを使っているらしく、こういった型定義や各コメントがおそらくツール定義に反映されるっぽい。つまり指定必須。
では、Chatインスタンスを定義して、チャットしてみる。
# モデルはたまに「sumツールによると答えは…」のようなことを言うため、システムプロンプトに明記
sp="""あなたは親切で計算が得意な日本語のアシスタントです。どんなツールを使っているかについては、決して話さないでください。"""
chat = Chat(
model,
sp=sp,
tools=[sums]
)
r = chat("604542 たす 6458932 は?")
r
ツールが使用されていることがわかる。
604542 と 6458932 を足し算します
ただしまだ回答は得られていない。ここでもう一度Chatインスタンスを呼び出してみる。
chat()
604542 + 6458932 の合計は 7063474 です。
Tool Useは複数回のターンになるのが普通なので、ある意味ステップ実行されているようなイメージになる。
使用されたトークン量については以下のように確認できる
chat.use
In: 306; Out: 40; Total: 346
Tool loop
Chat.toolloop
を使うとParallel Function Callingができる。今度は足し算を行う関数に追加で、掛け算を行う関数を用意する。
def sums(
a:int, # 足し算を行う1番目の整数
b:int=1 # 足し算を行う2番目の整数
) -> int: # 入力された整数を足し算した結果
"a と b を足し算する"
print(f"{a} と {b} を足し算します")
return a + b
def mults(
a:int, # 掛け算を行う1番目の整数
b:int=1 # 掛け算を行う2番目の整数
) -> int: # 入力された整数を掛け算した結果
"a と b を掛け算する"
print(f"{a} と {b} を掛け算します")
return a * b
まず、Chat.toolloop
を使わない場合。
sp="""あなたは親切で計算が得意な日本語のアシスタントです。どんなツールを使っているかについては、決して話さないでください。"""
chat = Chat(
model,
sp=sp,
tools=[sums, mults]
)
r = chat("(604542 + 6458932) * 2 を計算して")
r
604542 と 6458932 を足し算します
2 と 1 を掛け算します
chat()
7063474 と 2 を掛け算します
chat()
( (604542 + 6458932) \times 2 ) の計算結果は ( 14126948 ) です。
Chat.toolloop
を使わない場合は、各ステップごとにFunction Callingが行われているのがわかる。
Chat.toolloop
を使って実行してみる。
sp="""あなたは親切で計算が得意な日本語のアシスタントです。どんなツールを使っているかについては、決して話さないでください。"""
chat = Chat(
model,
sp=sp,
tools=[sums, mults]
)
# ツール実行のトレースを行う関数
def pchoice(r):
print(r.choices[0])
r = chat.toolloop("(604542 + 6458932) * 2 を計算して", trace_func=pchoice)
r
計算の結果、(604542 + 6458932) * 2 の値は 14126948 です。
関数がまとめて実行されているのがわかる。
画像
画像をチャットに含める。cosetteのレポジトリにある以下の画像を使用する。
画像ファイルをダウンロードしてバイトで読み込む
import requests
from io import BytesIO
from PIL import Image
response = requests.get("https://raw.githubusercontent.com/AnswerDotAI/cosette/main/samples/puppy.jpg")
image_byte = response.content
# 確認用
display(Image.open(BytesIO(image_byte)))
こんな感じでプロンプトと一緒に渡してやると、回答が得られる。
chat = Chat(
model,
sp="あなたは親切で計算が得意な日本語のアシスタントです。",
)
r = chat([image_byte, "簡単に言うと、この画像には何が描かれていますか?"])
r
この画像には、花のそばに横たわっている子犬が描かれています。この犬の毛は白と茶色で、かわいらしい表情をしています。周囲は緑の草と紫色の花で彩られています。
画像だけ渡してあとはマルチターンで、ってのもOK。
chat = Chat(
model,
sp="あなたは親切で計算が得意な日本語のアシスタントです。日本語で話します。",
)
r = chat(image_byte)
r
かわいい子犬の写真ですね!フラワーと緑の草の中でリラックスしている様子がとても癒されます。子犬に関する質問や話題があれば教えてください!
r = chat("犬の種類は何?")
r
この犬はキャバリア・キング・チャールズ・スパニエルのように見えます。特徴的な丸い目と耳が特徴的ですね。何か他に知りたいことがあれば教えてください!
r = chat("花の種類と特徴を教えて。")
r
写真に見える花は、小さな紫色の花のようですが、具体的な種類は特定できません。ただし、一般的に紫色の花にはいくつかの種類があります。例えば:
- ラベンダー:甘い香りが特徴で、リラックス効果があると言われています。
- アステル:秋に咲く花で、外観は小さな星型の花弁が特徴です。
- ネモフィラ:薄い青紫色の花を持ち、広がるように群生します。
お好みの花や知りたい種類があれば、もっと詳しくお話しできますよ!
Azure OpenAIにも対応している様子。
OpenAI SDKのクライアントをそのまま読み込めるみたい。
ってことはベースURL書き換えたらOllamaいけるんじゃないか?と思って書き換えてみた。
from cosette import Chat, Client
from openai import OpenAI
c = OpenAI(base_url="http://localhost:11434/v1")
client = Client("vanilj/supernova-medius:q4_k_m", cli=c)
chat = Chat(sp="""あなたは親切で簡潔な日本語のアシスタントです""", cli=client)
while True:
user_input = input("USER: ")
if user_input.lower() == 'quit':
print("チャットを終了します。さようなら。")
break
r = chat(user_input)
answer = r.choices[0].message.content
print(f"ASSISTANT: {answer}")
実行するとこんな感じ。
USER: こんにちは!私の趣味は競馬です。覚えてくださいね。
ASSISTANT: もちろんです、あなたの趣味が競馬であることを覚えました。何か競马に関する質問や話題があればお気軽にどうぞ。今後も競馬についてのお手伝いができるように心がけています。
USER: そういえば明日の天気はどうなのかな?
ASSISTANT: すみません、現在の私の能力では実際の天気情報を提供することはできません。私はインターネットに接続しておらず、最新の天気予報を取得することができません。スマートフォンやパソコンで信頼できるウェブサイトやアプリを利用してご確認いただくのが最良です。ただし、天気が競馬のパフォーマンスに影響を与えることは多いので、出場前に必ず競走馬が走るための条件となる状況をチェックすることをお勧めします。
USER: 私の趣味ってなんだっけ?
ASSISTANT: あなたの趣味は競馬です。他に何か particularなことについて知りたい場合は教えてくださいね。
USER: quit
チャットを終了します。さようなら。
もはやOpenAI SDKそのままであるが、会話履歴の処理を一切しなくて済むので、シンプルにマルチターンで実装できるのがメリットかな。
Claudetteとほぼ同じ感じで使える。あとはOpenAIなので互換APIが使えるならローカルでも使えるのはメリットかな。
Answer.AI、なにげにいろいろ作ってるし、公式の記事も興味深い