ClaudeのAPIを使う

[2025-02-25] Claude 3.7 Sonnet (thinking) 用のコードを追記しました。

Anthropic(アンスロピック)は2021年にOpenAIから離脱した人たちが作った会社です。Claude(クロード)という生成AIを開発しています。APIを使うには コンソール で登録してAPIキーをもらいます。

お値段などは LLM API比較 をご覧ください。

Pythonから使うには、発行されたAPIキーを

export ANTHROPIC_API_KEY='...'

のような形で環境変数として登録しておくか、あるいは python-dotenv を使うのが便利です。

pip install anthropic

でライブラリをインストールしておき、次のようにして client オブジェクトを作ります。

import anthropic

client = anthropic.Anthropic(
    # もし環境変数でAPIキーをセットできなければここで指定:
    # api_key="..."
)

最新のClaude 3.7 Sonnetを使ってみましょう。

message = client.messages.create(
    model="claude-3-7-sonnet-20250219",
    max_tokens=1000, # 出力上限
    temperature=0.0, # 0.0-1.0
    system="", # 必要ならシステムプロンプトを設定
    messages=[
        {
            "role": "user",
            "content": "まどマギでだれが好きですか?"
        }
    ]
)

print(message.content[0].text)

message.usage で入力、出力のトークン数がわかります。

client.messages.create()messages はリストで、

[
    { "role": "user", "content": "問1" },
    { "role": "assistant", "content": "答1" },
    { "role": "user", "content": "問2" },
]

のように増やしていきます。このあたりはOpenAIのものと同じです。

マルチメディアの場合は次のように指定します。

message = client.messages.create(
    ...,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/jpeg",  # jpeg, png, gif, webp
                        "data": "/9j/4AAQSkZJRg...",
                    }
                },
                {
                    "type": "text",
                    "text": "これは何?"
                }
            ]
        }
    ]
)

ストリーミングも簡単です:

with client.messages.stream(
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello"}],
        model="claude-3-5-sonnet-20240620",
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

OpenAIのAPIを使うで作ったような簡単なアプリを作ってみましょう。

import mimetypes
import base64
import anthropic

class Claude:
    def __init__(self, model="claude-3-5-sonnet-20240620",
                 messages=None, temperature=0, stream=True):
        self.client = anthropic.Anthropic()
        self.model = model
        self.messages = messages or []
        self.temperature = temperature
        self.stream = stream

    def chat(self, prompt, media=None, temperature=None, stream=None, max_tokens=4096):
        prompt = prompt.strip()
        if media is None:
            content = prompt
        else:
            mime_type, _ = mimetypes.guess_type(media)
            if mime_type and mime_type.startswith("image"):
                with open(media, "rb") as image_file:
                    image_content = image_file.read()
                    base64_content = base64.b64encode(image_content).decode("utf-8")
            else:
                print(media, "is not an image")
                return
            content = [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": mime_type,
                        "data": base64_content
                    }
                },
                {
                    "type": "text",
                    "text": prompt
                }
            ]
        if len(self.messages) > 0 and self.messages[-1]["role"] == "user":
            self.messages.pop()
        self.messages.append({"role": "user", "content": content})
        stream = stream if stream is not None else self.stream
        temperature = temperature if temperature is not None else self.temperature
        ans = ""
        if stream:
            try:
                with self.client.messages.stream(model=self.model,
                                                 max_tokens=max_tokens,
                                                 messages=self.messages,
                                                 temperature=temperature) as f:
                    for text in f.text_stream:
                        print(text, end="", flush=True)
                        ans += text
            except Exception as e:
                print("Error", e)
        else:
            try:
                message = self.client.messages.create(model=self.model,
                                                      max_tokens=max_tokens,
                                                      messages=self.messages,
                                                      temperature=temperature)
                ans = message.content[0].text
                print(ans)
                print(message.usage)
            except Exception as e:
                print("Error", e)
        if ans != "":
            self.messages.append({"role": "assistant", "content": ans})

    def get_messages(self):
        return self.messages

claude = Claude()

claude.chat("""
まどマギでだれが好きですか?
""")

claude.chat("...") で続きの会話ができます。claude.chat("...", media="filename.jpg") のようにして画像を指定できます(JPEG、PNG、GIF、WEBP)。会話をリセットするには claude = Claude() をもう一度行います。

混んでいると APIStatusError: {'type': 'error', 'error': {'type': 'overloaded_error', 'message': 'Overloaded'}} が返ることがあります(特にストリーミングのとき)。そのときは claude.chat("...", stream=False) も試してみてください。

Claude 3.7 Sonnet からは thinking が使えますが、thinking を使うなら temperature は 1 にする必要があります。上のコードに thinking を付けたものは次のようになりそうです(とりあえず動いているようですが十分テストできていません)。

import anthropic

class Claude:
    def __init__(self, model="claude-3-7-sonnet-20250219", messages=None):
        self.client = anthropic.Anthropic()
        self.model = model
        self.messages = messages or []

    def get_messages(self):
        return self.messages

    def chat(self, prompt, thinking=True,
             max_tokens=4000,    # <= 64000 (<= 128000)
             budget_tokens=2000, # >= 1024, < max_tokens, not used when thinking=False
             temperature=0):     # not used when thinking=True
        if len(self.messages) > 0 and self.messages[-1]["role"] == "user":
            self.messages.pop()
        self.messages.append({"role": "user", "content": prompt.strip()})
        content_thinking = ""
        content_text = ""
        content_signature = ""
        redacted_data = ""
        args = {
            "model": self.model,
            "max_tokens": max_tokens,
            "messages": self.messages
        }
        if thinking:
            args["thinking"] = {
                "type": "enabled",
                "budget_tokens": budget_tokens
            }
        else:
            args["temperature"] = temperature
        if max_tokens > 64000:
            args["extra_headers"] = {
                "anthropic-beta": "output-128k-2025-02-19"
            }
        try:
            # debugout = open("debugout", "a") ##### debug
            with self.client.messages.stream(**args) as stream:
                current_block_type = None
                for event in stream:
                    # print(event, file=debugout) ##### debug
                    if event.type == "content_block_start":
                        current_block_type = event.content_block.type
                        print("<" + current_block_type + ">")
                    elif event.type == "content_block_delta":
                        if event.delta.type == "thinking_delta":
                            print(event.delta.thinking, end="", flush=True)
                            content_thinking += event.delta.thinking
                        elif event.delta.type == "text_delta":
                            print(event.delta.text, end="", flush=True)
                            content_text += event.delta.text
                        elif event.delta.type == "signature_delta":
                            # print(event.delta.signature, end="", flush=True)
                            content_signature += event.delta.signature
                    elif event.type == "content_block_stop":
                        print("\n</" + current_block_type + ">")
                        if current_block_type == "redacted_thinking":
                            redacted_data += event.content_block.data
                        current_block_type = None
                    elif event.type == "message_stop":
                        print(event.message.usage)
            # debugout.close() ##### debug
        except Exception as e:
            print("Error", e)
        content = []
        if redacted_data != "":
            content.append(
                anthropic.types.redacted_thinking_block.RedactedThinkingBlock(
                    data=redacted_data,
                    type="redacted_thinking"
                )
            )
        if content_thinking != "":
            content.append(
                anthropic.types.thinking_block.ThinkingBlock(
                    signature=content_signature,
                    thinking=content_thinking,
                    type="thinking"
                )
            )
        if content_text != "":
            content.append(
                anthropic.types.text_block.TextBlock(
                    text=content_text,
                    type="text"
                )
            )
        self.messages.append({
            "role": "assistant",
            "content": content
        })

claude = Claude()
claude.chat("""
(ここに質問を書く)
""")

詳しくはドキュメントの Building with extended thinking をご覧ください。コード例は これ が参考になります。