CLAUDE LABEN
WWDC — WWDC 2026でSiriはGoogle Geminiベースと確定。ChatGPT等への外部ハンドオフは廃止され、サードパーティAI選択はEU(DMA)で当面非対応にBILLING — 6/15の課金変更まで残り6日。Agent SDK・headless Claude Code・GitHub Actions・他社エージェントがAPIレート準拠の月次クレジットへ移行OUTAGE — claude.ai・Claude Code・Coworkで障害が報告(6月)。スケジュール実行はfallbackModelとリトライ前提の設計が安全ですDYNAMIC-WORKFLOWS — Max・TeamプランとAPIでdynamic workflowsがデフォルトON。コードベース横断のバグ探索や独立検証に活用ULTRACODE — Claude Codeの新設定ultracodeがeffortメニューに追加。xhigh固定でワークフロー判断はClaudeに委ねますOPUS4.8 — Claude Opus 4.8が主要プランのデフォルトとして定着。コーディング・エージェント・推論を強化WWDC — WWDC 2026でSiriはGoogle Geminiベースと確定。ChatGPT等への外部ハンドオフは廃止され、サードパーティAI選択はEU(DMA)で当面非対応にBILLING — 6/15の課金変更まで残り6日。Agent SDK・headless Claude Code・GitHub Actions・他社エージェントがAPIレート準拠の月次クレジットへ移行OUTAGE — claude.ai・Claude Code・Coworkで障害が報告(6月)。スケジュール実行はfallbackModelとリトライ前提の設計が安全ですDYNAMIC-WORKFLOWS — Max・TeamプランとAPIでdynamic workflowsがデフォルトON。コードベース横断のバグ探索や独立検証に活用ULTRACODE — Claude Codeの新設定ultracodeがeffortメニューに追加。xhigh固定でワークフロー判断はClaudeに委ねますOPUS4.8 — Claude Opus 4.8が主要プランのデフォルトとして定着。コーディング・エージェント・推論を強化
記事一覧/API & SDK
API & SDK/2026-05-06中級

Claude API × Python 実践:ツール呼び出しとストリーミングを組み合わせてAIアシスタントを作る

Claude API の Tool Use とStreamingを同時に使うPython実装を解説。ツールを定義してリアルタイム応答するAIアシスタントの完成コードと、組み合わせ時に詰まりやすいポイントを丁寧に解説します。

python32tool-use25streaming17api58claude-api71tutorial5

ツール呼び出し(Tool Use)とストリーミング、どちらか片方は動いたのに、両方を組み合わせようとした途端に止まってしまった。そんな経験はないでしょうか。

Claude API を触り始めて最初にぶつかる壁の1つが、この「Tool Use × Streaming」の組み合わせです。どちらも理解しているつもりでも、ストリームのイベント処理が複雑で、思わぬ挙動をすることがあります。

Tool Use × Streaming の仕組みを整理する

なぜ組み合わせが難しいのかを理解するところから始めましょう。

通常のストリーミングでは、Claude が生成するテキストをチャンク単位で受け取ります。しかしツール呼び出しが発生すると、Claude は「テキスト生成を一時中断してツールを要求する」という特殊なレスポンスを返します。

ストリームで届くイベントの種別は主に以下の通りです。

  • content_block_start — テキストまたはツール呼び出しブロックの開始
  • content_block_delta — テキストの差分、またはツール入力 JSON の断片
  • content_block_stop — ブロック終了
  • message_delta — stop_reason が含まれ、ツール呼び出し要求なら tool_use

この flow を正確に処理できるかどうかが、実装の成否を分けます。

ツール呼び出しの基本については Claude API Tool Use ガイド、ストリーミングと Tool Use の詳細な挙動については Streaming × Tool Use の実装詳解 も参考になります。

Python 環境のセットアップ

Python 3.10 以上と Anthropic SDK があれば始められます。

pip install anthropic>=0.40.0

今回は2つのシンプルなツールを定義します。

  • get_current_time — 現在時刻を ISO 8601 形式で返す(引数なし)
  • calculate — 数式を評価して数値を返す(expression 引数あり)

シンプルなツールにすることで、ツール処理のロジックそのものに集中できます。

基本実装:ツール定義とストリーミング処理

まずはツールの定義と実行関数から見ていきます。

import anthropic
import json
import math
from datetime import datetime
from typing import Any
 
client = anthropic.Anthropic()
 
# ツール定義(JSON スキーマ形式)
TOOLS = [
    {
        "name": "get_current_time",
        "description": "現在の日時を ISO 8601 形式で返します",
        "input_schema": {
            "type": "object",
            "properties": {},
            "required": []
        }
    },
    {
        "name": "calculate",
        "description": "数式を評価して結果を返します。四則演算・冪乗・sqrt などに対応しています",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {
                    "type": "string",
                    "description": "評価する数式(例: '2 + 3 * 4', 'sqrt(16)')"
                }
            },
            "required": ["expression"]
        }
    }
]
 
def execute_tool(name: str, inputs: dict[str, Any]) -> str:
    """ツールを実際に実行して結果文字列を返す"""
    if name == "get_current_time":
        return datetime.now().isoformat()
 
    elif name == "calculate":
        expr = inputs.get("expression", "")
        try:
            # 安全のため、使用できる関数を明示的に制限する
            allowed_names = {
                "sqrt": math.sqrt, "sin": math.sin, "cos": math.cos,
                "tan": math.tan, "log": math.log, "abs": abs, "pi": math.pi
            }
            result = eval(expr, {"__builtins__": {}}, allowed_names)
            return str(result)
        except Exception as e:
            return f"計算エラー: {str(e)}"
 
    return f"未知のツール: {name}"

次がメインのストリーミング処理です。ここが実装の核心です。

def chat_with_tools(messages: list, model: str = "claude-sonnet-4-6", depth: int = 0) -> str:
    """
    ストリーミング + Tool Use でチャットを実行する。
    ツール呼び出しが発生した場合は深さを増やして再帰的に処理する。
    """
    if depth > 5:
        return "(ツール呼び出しの上限に達しました)"
 
    collected_text = []
    tool_calls = []
    current_tool = None
    current_tool_input_raw = ""
 
    with client.messages.stream(
        model=model,
        max_tokens=2048,
        tools=TOOLS,
        messages=messages
    ) as stream:
        for event in stream:
            event_type = event.type
 
            if event_type == "content_block_start":
                block = event.content_block
                if block.type == "tool_use":
                    # ツール呼び出しブロックが始まった
                    current_tool = {"id": block.id, "name": block.name}
                    current_tool_input_raw = ""
 
            elif event_type == "content_block_delta":
                delta = event.delta
                if delta.type == "text_delta":
                    # テキストをリアルタイムで表示しながら蓄積
                    print(delta.text, end="", flush=True)
                    collected_text.append(delta.text)
                elif delta.type == "input_json_delta":
                    # ツール入力 JSON の断片を蓄積(完成してから parse する)
                    current_tool_input_raw += delta.partial_json
 
            elif event_type == "content_block_stop":
                if current_tool is not None:
                    # JSON が完成したのでパースしてツール呼び出しリストに追加
                    tool_input = json.loads(current_tool_input_raw) if current_tool_input_raw else {}
                    tool_calls.append({**current_tool, "input": tool_input})
                    current_tool = None
                    current_tool_input_raw = ""
 
    # ツール呼び出しがなければテキストをそのまま返す
    if not tool_calls:
        return "".join(collected_text)
 
    # ツール実行 → 結果を次のリクエストに渡す
    print("\n[ツールを実行中...]")
 
    # アシスタントの発言(テキスト + ツール呼び出し)を履歴に追加
    assistant_content = []
    if collected_text:
        assistant_content.append({"type": "text", "text": "".join(collected_text)})
    for tc in tool_calls:
        assistant_content.append({
            "type": "tool_use",
            "id": tc["id"],
            "name": tc["name"],
            "input": tc["input"]
        })
    messages.append({"role": "assistant", "content": assistant_content})
 
    # 各ツールを実行し、結果を tool_result として追加
    tool_results = []
    for tc in tool_calls:
        result = execute_tool(tc["name"], tc["input"])
        print(f"  {tc['name']}({tc['input']}) → {result}")
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": tc["id"],
            "content": result
        })
    messages.append({"role": "user", "content": tool_results})
 
    # ツール結果を踏まえた最終回答を再帰的に取得
    return chat_with_tools(messages, model, depth + 1)

最後に、会話ループを作ります。

def main():
    print("Claude AIアシスタント(ツール対応・ストリーミング版)")
    print("終了: 'quit' または 'exit'\n")
 
    messages = []
 
    while True:
        user_input = input("You: ").strip()
        if not user_input or user_input.lower() in ("quit", "exit"):
            break
 
        messages.append({"role": "user", "content": user_input})
        print("Assistant: ", end="", flush=True)
        response = chat_with_tools(messages)
        print()
 
        # 最終応答を履歴に追加
        messages.append({"role": "assistant", "content": response})
 
if __name__ == "__main__":
    main()

これを実行して「今何時ですか?」「3の15乗はいくつ?」などと聞いてみてください。Claude がツールを呼び出して正確に答えてくれます。

実装で詰まりやすいポイント

実際に書いてみると、いくつかの落とし穴があります。私が最初に詰まった箇所をまとめます。

ツール入力 JSON は断片で届く

input_json_delta イベントでは JSON が細切れで届きます。{"expression": "2** のような途中の状態です。必ず content_block_stop を待ってから json.loads() してください。

上記の実装では current_tool_input_raw に文字列として蓄積し、ブロック終了時にまとめてパースしています。これが最もシンプルなアプローチです。

テキストとツール呼び出しが混在することがある

Claude が「計算してみますね。」とテキストを少し出力してからツールを呼び出すことがあります。collected_texttool_calls を別々に管理しているのはこのためです。アシスタントの発言として両方を履歴に含める必要があります。

eval() のセキュリティに注意

今回の calculate ツールでは eval() を使っています。allowed_names で利用可能な関数を明示的に制限しているので、単純な数式の評価であれば安全ですが、本番環境で外部ユーザーが入力する場合はさらに厳格な入力バリデーションを追加してください。

実用的なツールの追加例

基本の仕組みが理解できたら、ツールを増やしてみましょう。実際のアプリでよく組み込むのは以下のようなものです。

# 例:ファイル内容を読み込むツール
def read_file_tool(file_path: str) -> str:
    try:
        with open(file_path, "r", encoding="utf-8") as f:
            content = f.read()
        # 長すぎる場合はトリミング
        return content[:5000] if len(content) > 5000 else content
    except FileNotFoundError:
        return f"ファイルが見つかりません: {file_path}"
    except Exception as e:
        return f"読み込みエラー: {str(e)}"

このパターンで、データベース検索・外部 API 呼び出し・コード実行など、どんな処理もツールとして組み込めます。ツール定義の description を丁寧に書くことで、Claude が適切な場面でそのツールを呼び出すようになります。

Python SDK のより詳しい使い方については Python SDK 基本チュートリアル も参照してみてください。

まずはこのコードをそのまま動かしてみてください

仕組みを理解するには、手を動かすのが一番の近道です。まずは今回のコードをそのままコピーして動かしてみてください。

ツール定義の description を変えると Claude の挙動が変わること、ツール結果を受け取ってから Claude が最終回答を組み立てていること、その flow がストリームで可視化されていること。実際に動かすと、ドキュメントを読むだけでは掴みきれない感覚が身についてきます。

自分のユースケースに合ったツールが決まったら、それを追加するだけで一気に実用的なアシスタントに育ちます。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API & SDK2026-05-16
Claude API の Tool Use でスキーマ定義ミスが出たとき:実際に遭遇した3つのパターンと診断手順
Claude API の Tool Use でよく起きるスキーマ定義ミス・invalid_tool_use・Claude がツールを呼ばない問題を、実際のアプリ開発で遭遇したケースをもとに診断手順と修正パターンで解説します。
API & SDK2026-05-05
Claude API のツール実行エラーを Claude 自身に診断させる — 自己修正ループの設計と本番実装
Tool Use実装で避けられないツール実行エラーの対処法を解説。is_errorフラグを活用してエラー情報をClaudeに返し、自己診断・修正ループを実装する実践的なPythonコードと本番でのアンチパターンも紹介します。
API & SDK2026-05-05
「考えながら調べる」AIエージェントの作り方 — Claude API 拡張思考 × Tool Use 本番実装ガイド
Claude APIの拡張思考(Extended Thinking)とTool Useを組み合わせる実装パターンを詳解。頻出エラーの根本原因、リサーチエージェントの完全実装コード、本番でのコスト・タイムアウト設計まで体系的に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →