Closed7

Anthropic Claude SDKのシンプルなラッパー「Claudette」を試す

kun432kun432

byaldiのnotebookで出てきた。

レポジトリ
https://github.com/AnswerDotAI/claudette

公式ドキュメント
https://claudette.answer.ai/

Claudette

Claudetteは、Claudeの友だちです。

注: GitHubのreadmeでこれをお読みの場合は、代わりに、このチュートリアルのより見やすいフォーマットのドキュメントをお読みになることをお勧めします。

Claudetteは、AnthropicのPython SDKのラッパーです。

SDKはよく機能しますが、かなり低レベルであり、開発者が多くの作業を手動で行う必要があります。これは余分な作業や定型作業が非常に多く発生することを意味します。Claudetteは、自動化できる作業はほぼすべて自動化し、完全な制御を提供します。提供される機能には以下のようなものがあります。

  • ステートフルダイアログを作成するChatクラス
  • prefillのサポートにより、Claudeが応答の最初の数語として何を入力するかを指示
  • 便利な画像サポート
  • Claudeの新しいTool Use APIのシンプルで便利なサポート。

このライブラリを使用するには、ANTHROPIC_API_KEY 環境変数を Anthropic から提供されたキーに設定する必要があります。

このライブラリは、史上初の「literate nbdev」プロジェクトであることにご留意ください。つまり、このライブラリの実際のソースコードは、注釈やヒント、HTMLテーブルや画像、詳細な説明、コードがそのように書かれている理由や方法などを含むレンダリングされたJupyter Notebookです。Anthropic Python SDKやClaude APIをこれまで一度も使用したことがない場合でも、ソースコードを読むことができます。 Claudette's Source をクリックしてソースコードを読み、または git リポジトリをクローンしてノートブックを実行し、作成プロセスの各ステップを実際に確認してください。以下のチュートリアルには、ソースの関連部分に移動する API 詳細へのリンクが含まれています。このプロジェクトが新しい種類のリテラルプログラムである理由は、私たちがKnuthの行動への呼びかけを真剣に受け止めているからです。すなわち、*「読みにくいプログラム」を絶対に書かないという「道徳的な誓約」*を私たちは持っているのです。そして、読みやすいプログラミングと、簡単で快適な体験を提供するという誓約を私たちは持っています。(このことについて詳しくは、Hamel Husainのこの講演をご覧ください。)

「プログラムの構築に対する従来の姿勢を変えましょう。コンピュータに何をすべきかを指示することが主な仕事であると考えるのではなく、むしろ、コンピュータに何をさせたいかを人間に説明することに集中しましょう。」Donald E. Knuth著『Literate Programming』(1984年)

kun432kun432

Install & Getting Startedにしたがってやってみる。Colaboratoryで。

https://claudette.answer.ai/#install

インストール

パッケージインストール

!pip install claudette

APIキーの読み込み

import os
from google.colab import userdata

os.environ['ANTHROPIC_API_KEY'] = userdata.get('ANTHROPIC_API_KEY')

モジュールのロード&モデルの設定

モジュールのロードはドキュメントだと

from claudette import *
import claudette

みたいな感じで書いてあるけど、とりあえず自分は必要なものを必要なときにロードする方向でやってみる。

モデルは以下のようにしてアクセスできる。

from claudette import models

print(models)

model = models[2]
print(model)
('claude-3-opus-20240229', 'claude-3-5-sonnet-20240620', 'claude-3-haiku-20240307')
claude-3-haiku-20240307

今回はHaikuを使用する

Chat

Chatがメインのインタフェースで、これは「ステートフル」になっている。

from claudette import Chat

chat = Chat(model, sp="""あなたは親切で簡潔な日本語のアシスタントです。""")
chat("こんにちは。私の名前は太郎です。")

はい、こんにちは太郎さん。私は人工知能のアシスタントです。どのようなお手伝いができますでしょうか?

Colaboratoryだと以下のような出力になっていて、詳細をクリックすると、実際のレスポンスの詳細が確認できる。

コードでやる場合には結果を変数で受け取れば良い。

r = chat("私の名前は何でしたっけ?")
print(r)
Message(id='msg_01At6Wbnu4EnpGoQuBHCigaP', content=[TextBlock(text='太郎さんと仰っていましたね。私はあなたの名前が太郎さんだと覚えています。', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 111; Out: 39; Total: 150)

上で記載したようにChatクラスは「ステートフル」なので、会話履歴を持っていることがわかる。

あと、自分はぜんぜん知らなかったのだが、Claudeにはプリフィルというものがあるらしい。

https://docs.anthropic.com/ja/docs/build-with-claude/prompt-engineering/prefill-claudes-response

Claudeを使用する際、Assistantメッセージをプリフィルすることで、応答を導くことができるユニークな機能があります。この強力なテクニックにより、Claudeのアクションを指示し、前置きをスキップし、JSONやXMLなどの特定のフォーマットを強制し、ロールプレイシナリオでキャラクターの一貫性を維持するのに役立ちます。

プリフィルするには、Assistantメッセージに希望する初期テキストを含めます(Claudeの応答は、Assistantメッセージが終了した時点から続きます):

import anthropic

client = anthropic.Anthropic()
response = client.messages.create(
   model="claude-3-5-sonnet-20240620",
   max_tokens=1024,
   messages=[
       {"role": "user", "content": "あなたの好きな色は何ですか?"},
       {"role": "assistant", "content": "AIアシスタントとして、私に好きな色はありません。しかし、あえて選ぶとすれば、緑でしょう。なぜなら、"}  # ここでプリフィル
   ]
)

ChatじゃないCompletionモデルだとよくやるよね。

で上記を実行してレスポンスの中身を見てみると、上記の「なぜなら」の続きから出力されていることがわかる。

print(response.content[0].text)

緑は自然や生命力を連想させる色だからです。多くの人にとって緑は落ち着きや安らぎを与える色でもあります。あなたの好きな色は何ですか?それはどんな理由からでしょうか?

で、Claudetteではこのプリフィルに対応している。

chat("簡潔に答えて。人生の意味とは何?", prefill='ダグラス・アダムスによると、')

ダグラス・アダムスによると、人生の意味は42だそうです。ただし、その答えを理解するには、より深い洞察が必要だと思います。人生の意味は、一人一人が自分なりに見出していくものだと考えています。

公式SDKだとプリフィルとその出力を結合する必要があるけども、Claudetteだとそのあたりもやってくれるのでこれは便利。

stream=Trueでストリーミング

for o in chat("簡潔に答えて。それはどの本に載っていたのでしょうか?", prefill='その内容の出典は、', stream=True):
    print(o, end='\n')  # わかりやすいように改行を入れているが、実際に使う場合は''

その内容の出典は、
ダグラス
・アダム
スの小
説「
銀河


チハイク


イド」です

非同期

非同期の場合はAsyncChatを使う。

from claudette import AsyncChat

# notebookの場合に必要
import nest_asyncio
nest_asyncio.apply()

chat = AsyncChat(model)
await chat("私の名前は花子です。")

はじめまして、花子さん。私はアシスタントのクララです。よろしくお願いします。 花子さんのお話を聞かせていただけますか?どのようなことでも構いません。私はできる限りお役に立てるよう努めさせていただきます。

非同期のストリーミング。プリフィルも指定してある。

async for o in await chat(
    "簡潔に答えて。人生の意味とは何?",
    prefill='ダグラス・アダムスによると、', stream=True):
    
    print(o, end='\n')

ダグラス・アダムスによると、
人生の意
味は

42
」だ

うです。
ただし、その

えを

解するには、
より
深い問
いを

てる必要
があるか
もしれません
。人生の
意味は
、一
人一人が
自分な
りに


してい

もの
だと思
います。

kun432kun432

プロンプトキャッシング

Claudetteはプロンプトキャッシングにも対応している

https://www.anthropic.com/news/prompt-caching

https://zenn.dev/kun432/scraps/77174bedc28472

mk_msg()で、プロンプトキャッシングを有効にするメッセージに対してキャッシュを有効化する。

上の自分の記事で取り上げた青空文庫の「走れメロス」の内容をキャッシュさせてみる。

https://www.aozora.gr.jp/cards/000035/card1567.html

手順の説明は上の記事で書いてあるので割愛。コードは以下。

青空文庫の「走れメロス」をダウンロードしてテキスト化する
! wget https://www.aozora.gr.jp/cards/000035/files/1567_ruby_4948.zip
! unzip 1567_ruby_4948.zip
import re

with open('hashire_merosu.txt', 'r', encoding='SJIS') as f:
    data = f.read()
    data = re.sub(r'-------------------------------------------------------.*?-------------------------------------------------------\n', '', data, flags=re.DOTALL)
    data = re.sub(r'[#地から1字上げ[\s\S]*$', '', data)
    data = re.sub("\n\u3000", "", data)
    data = re.sub("《[^》]+》", "", data)

dataにテキストが入っているとして進める。

Chatインスタンスの初期化

from claudette import Chat, mk_msg

system_prompt = """\
あなたは文学作品の分析を任務とするAIアシスタントです。
テーマ、キャラクター、文体に関する洞察に満ちた解説を提供することがあなたの目標です。\
"""

chat = Chat(model, sp=system_prompt)

テキストを含めたメッセージをキャッシュ対象にして送信。

msg = f"""\
<LITERALY_WORK>
{data}
</LITERALY_WORK>

「走れメロス」の主なテーマを分析して。\
"""

r = chat(mk_msg(msg, cache=True))
r

走れメロス」の主なテーマは以下のように分析できます:

人間の信頼と誠実さ: メロスは友人セリヌンティウスに対する信頼と誠実さを貫き通し、最後まで約束を守ろうとする。この人間関係の美しさが物語の核心をなしている。

暴君への抵抗: メロスは暴君ディオニスの残虐な行為に立ち向かい、正義のために命を賭す。この勇気と正義感が強調されている。

人間の弱さと強さ: メロスは途中で疲労に倒れそうになるが、最後まで走り抜く。この人間の弱さと強さの両面が描かれている。

愛と友情: メロスの妹への愛情と、セリヌンティウスとの友情が物語の重要なテーマとなっている。

人間性の尊厳: 最終的に暴君ディオニスも、メロスとセリヌンティウスの誠実さに感化され、人間性の尊厳を認めるようになる。

以上のように、「走れメロス」は人間の信頼、正義感、愛情、友情といった普遍的なテーマを深く掘り下げた作品といえる。

で、詳細を見ると、キャッシュ作成トークンが使用されているのがわかる。

レスポンスからもトークン使用状況は確認できる。

print(r.usage)
Usage(input_tokens=431, output_tokens=450, cache_creation_input_tokens=0, cache_read_input_tokens=10485)

続けてチャットしてみる。

r = chat("登場人物をリストアップして")
r

「走れメロス」の主要な登場人物は以下の通りです:

  1. メロス
  • 主人公。村の牧人で、妹と二人暮らし。セリヌンティウスとの友情が深い。
  • 正義感と誠実さを持った人物。暴君ディオニスに立ち向かい、友人を救うために命を賭す。
  1. セリヌンティウス
  • メロスの親友。シラクスの市で石工をしている。
  • メロスを信じ続け、最後まで待ち続ける。
  1. 暴君ディオニス
  • 残虐な行為を繰り返す王。人々を疑い、殺戮を繰り返す。
  • しかし最終的にメロスとセリヌンティウスの誠実さに感化される。
  1. メロスの妹
  • メロスの大切な家族。近々結婚式を挙げる。
  • メロスの正義感と誠実さを理解している。
  1. 花婿の牧人
  • メロスの妹の婿となる人物。
  • メロスの急ぐ事情を理解し、結婚式を前倒しにする。
  1. フィロストラトス
  • セリヌンティウスの弟子。
  • メロスの到着が遅れていることを知らせる。

以上のように、メロスを中心に、友人、家族、そして敵対者であるディオニスが登場し、物語を展開させている。

トークン使用状況を見ると、キャッシュから読み込まれているのがわかる。

print(r.usage)
Usage(input_tokens=431, output_tokens=450, cache_creation_input_tokens=0, cache_read_input_tokens=10485)

で、これどうせならシステムプロンプトで定義したいなと思ったのだけども、mk_msg()で指定できるロールはuserassistantだけで、systemは指定できない模様。

あと、Claudeのキャッシュの定義は4つまで、という制約があるのだけど

r = chat(mk_msg("◯◯◯◯", cache=True))
r

みたいな感じで延々とやると以下のようにエラーになる。

A maximum of 4 blocks with cache_control may be provided. Found 5.'

ソースも追いかけてみたけど、この辺はあまり細かい制御ができなさそうに思える。

kun432kun432

Tool Use

Tool Useにも対応している。

まず、ツールで使う関数を用意する。今回は四則演算の関数を用意した。

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

なお、Claudetteは内部でfastcoreという、Pythonを使いやすくするためのライブラリ?(雑)のDocumentクラスというものを使っているらしく、こういった型定義や各コメントがおそらくツール定義に反映されるっぽい。つまり指定必須。

では、Chatインスタンスを定義して、チャットしてみる。

sp="""あなたは親切で計算が得意な日本語のアシスタントです。どんなツールを使っているかについては、決して話さないでください。"""

chat = Chat(
    model,
    sp=sp,
    tools=[sums],
    tool_choice='sums'
)

r = chat("604542 たす 6458932 は?")
r

ツールが使用されていることがわかる。

604542 と 6458932 を足し算します
ToolUseBlock(id='toolu_0169xoRPWLw4ssDKKZJ5WzAK', input={'a': 604542, 'b': 6458932}, name='sums', type='tool_use')

ただ回答が得られていない。ここでもう一度Chatインスタンスを呼び出してみる。

chat()
604542 と 6458932 を足すと、7063474 になります。

Tool Useは複数回のターンになるのが普通なので、ある意味ステップ実行されているようなイメージになる。

では複数のツールを使って、これをシングルステップで実行させてみる。シングルステップで実行するにはChat.toolloopを使う。

sp="""あなたは親切で計算が得意な日本語のアシスタントです。どんなツールを使っているかについては、決して話さないでください。"""

chat = Chat(
    model,
    sp=sp,
    tools=[sums,mults],
)

r = chat.toolloop('604542 + 6458932 * 2 を計算して。', trace_func=print)
r
6458932 と 2 を掛け算します
Message(id='msg_01SofZQ58GB6fuZ1ZgRV1J5V', content=[TextBlock(text='はい、わかりました。では、その計算をしてみましょう。', type='text'), ToolUseBlock(id='toolu_018WisGVewf8RWeEHSx7hk9y', input={'a': 6458932, 'b': 2}, name='mults', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 885; Out: 93; Total: 978)
604542 と 12917864 を足し算します
Message(id='msg_01QfxKd3HmhmKTqAivcQZRZR', content=[TextBlock(text='次に、その結果と604542を足し算します。', type='text'), ToolUseBlock(id='toolu_01CyiCyWPhgVVbcFKtfw2Zbv', input={'a': 604542, 'b': 12917864}, name='sums', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 992; Out: 89; Total: 1081)
Message(id='msg_0174oDENT2EvDf9xLbrSwTo9', content=[TextBlock(text='以上のように計算しますと、604542 + 6458932 * 2 = 13522406 となります。', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 1095; Out: 36; Total: 1131)
以上のように計算しますと、604542 + 6458932 * 2 = 13522406 となります。

kun432kun432

画像

画像をチャットに含める。claudetteのレポジトリにある以下の画像を使用する。

https://raw.githubusercontent.com/AnswerDotAI/claudette/main/samples/puppy.jpg

画像ファイルをダウンロードしてバイトで読み込む

import requests
from io import BytesIO
from PIL import Image

response = requests.get('https://raw.githubusercontent.com/AnswerDotAI/claudette/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

画像の中に咲いている花は、ダリアだと思われます。ダリアは菊科の多年草で、球形の花弁が特徴的です。この画像では、紫色の大きな花が咲いており、犬の周りに優雅に咲いています。ダリアは色彩が豊かで、秋の代表的な花の1つです。犬と花が調和のとれた素敵な構図になっています。

kun432kun432

まとめ

個人的には、プロンプトキャッシュが少し使いにくいなぁというところもあるし、商用で使うのはどうかなとは思うんだけど、かなりライトに少ない量で書けるので、プロンプトキャッシュはTool Useもカバーされているので、ノートブックやプロトタイプとかなら積極的に使っていきたいところ。ノートブックならデバッグ的に情報が出るってのは特に使いやすいよね。

あと、良いなと思うのは、ソースが全部ノートブックで確認できる形で提供されているんだよね。

https://claudette.answer.ai/core.html

https://claudette.answer.ai/toolloop.html

https://claudette.answer.ai/async.html

自分でラッパー書いたりする場合にも参考になると思う。特に自分の場合、元々はインフラ専門で、プログラマーではないので、こういうのはとても参考になる。

このスクラップは2ヶ月前にクローズされました