上級 20 分API

ツール呼び出し(Tool Calling)を使いこなす:Ollamaと Python

ツール呼び出し(tool calling)は、テキストを生成するだけのモデルを、実際のコードの実行を指示できるエージェントに変えます。天気APIの呼び出し、データベースへの問い合わせ、計算の実行などが可能になります。本ガイドでは、PythonでOllamaのツール呼び出しを最初から最後まで使いこなす方法を解説します。ツールのJSON形式、実行ループ、ツール呼び出しのストリーミング(0.17系)、そしてデコード時に直接適用されるJSON Schemaによって制約された構造化出力を取り上げます。すべてが http://localhost:11434 でローカルに動作し、APIキーもデータ漏洩もありません。

著者 Mohamed Meguedmi·更新 2026-08-27·Windows・macOS・Linuxでテスト済み

#なぜツール呼び出し(tool calling)を使うのか?

LLM単体では、学習後の現実世界の状況はわかりません。今日の天気も、口座の残高も、ご自身のデータベースの内容も知りません。ツール呼び出しは、この不足を補います。利用可能な関数の一覧をモデルに提示すると、モデルはどの関数をどの引数で呼び出すかを決めます。ご自身のコードがそれらの関数を実行し、結果をモデルに返すことで、モデルはその情報に基づいた回答を作成します。

理解しておくべき重要な点は、モデル自身は何も実行しないということです。モデルは「ville='Lyon' を指定して get_meteo を呼び出してください」という構造化されたリクエストを生成するだけです。関数を実行し、すべての制御を保持するのは、あなたのPythonプログラムです。この役割の分離によって、ツール呼び出しは安全で予測可能になります。

最新のデータ
モデルは、学習時の記憶をもとに推測するのではなく、リアルタイムでAPIに問い合わせます。
具体的なアクション
チケットの作成、メールの送信、データベースへの書き込み。LLMが処理を統括し、実際の操作はユーザーのコードが行います。
信頼性
計算や厳密な検索は決定論的なコードに任せ、モデルがハルシネーションで結果を作り出すことを避けます。
100 % ローカル
Ollamaを使えば、処理の全工程が手元のマシン内で完結します。APIキーは不要で、外部へのリクエストも、トークンごとの課金もありません。

#Ollamaのツール呼び出しの仕組み

ローカルコパイロットキット

このガイドでモデルの導入まで、キットでエディタ内でコードを書くコパイロットの導入まで進められます。

  • 永久に利用できるオンラインスペース
  • PDF + ファイル
  • 生涯アップデート

完全なサイクルは5つの段階で構成されます。これを明確に視覚化することは、最も頻繁な混乱、すなわち「1回の呼び出しで十分だ」という誤解を避けるために重要です。少なくとも2回の呼び出しが必要です。1回目はツールの要求を取得するため、2回目は最終的な回答を取得するためです。

  1. 01
    質問とツールを送信します
    チャットリクエストにはユーザーのメッセージと利用可能なツールのリスト(tools パラメータ)が含まれます。
  2. 02
    モデルがツール呼び出しの要求を返します
    テキストで回答する代わりに、関数名と引数を含む tool_calls を1つ以上返します。
  3. 03
    自分のコードが関数を実行する
    nameとargumentsを取得し、対応する実際のPython関数を呼び出して、結果を取得します。
  4. 04
    結果を返します
    結果は「tool」ロールのメッセージとして履歴に追加され、その後、chatを再度実行します。
  5. 05
    モデルが最終的な回答を生成する
    結果に基づき、今回はユーザー向けに自然言語で応答を生成します。
i
最低 2 回の呼び出し
完全なツール呼び出しサイクル = 最低でもモデルへの二回のパス。モデルが複数のツールを連続して実行する場合、応答に tool_calls がなくなるまでループします。

#前提条件

必要なのは三つです。起動している Ollama デーモン、実際にツール呼び出しに対応したモデル、そして公式 Python ライブラリです。二つ目には注意してください。すべてのモデルがツール呼び出しに対応しているわけではありません。この機能向けに設計された最近のモデルファミリーを選んでください。

Ollamaが最新版である
ツール呼び出しのストリーミングを利用するには、0.17系以降のバージョンが必要です。デーモンはhttp://localhost:11434で待ち受けます。
ツールに対応したモデル
Qwen 3.5, Granite 4.2, Mistral Small, Devstral, gpt-oss。ollama.com/library に「tools」マークがついているモデル。
十分なVRAM
テストには小規模なモデル(Qwen 3.5 4B、約3.4 GB)で十分です。Granite 4.2 8B(約5.3 GB)や Qwen 3.5 9B(約6.6 GB)のほうが、複数のツールを使う指示により忠実に従います。入門用の構成には RTX 3060 12 GB が適しています。
ollamaのライブラリ
pip install -U ollama。型付きのPython関数から、ツールのスキーマを直接構築できます。
環境の準備
# Le daemon Ollama doit tourner (souvent déjà lancé en service)
ollama serve

# Un modèle qui supporte les outils
ollama pull qwen3.5:4b

# La librairie Python officielle
pip install -U ollama
!
ツールサポートのないモデル
toolsパラメータを、それに対応していないモデルに渡しても、必ずしも明確なエラーが出るとは限りません。モデルがツールを無視してテキストで応答したり、応答内容にJSONらしきものを返したりすることがあります。コードを書く前に、必ずモデルの「Tools」ラベルを確認してください。

#ツールのJSONフォーマット

ツールは、OpenAIのスキーマに厳密に合わせたJSONスキーマで記述します。type: "function"のオブジェクトに、名前、説明、JSON Schema形式のparametersオブジェクトを含めます。説明は非常に重要です。モデルはその説明を読んで、いつ、どのようにツールを呼び出すかを判断します。具体的かつ明確に記述してください。

OpenAI形式でのツールの定義
{
  "type": "function",
  "function": {
    "name": "get_meteo",
    "description": "Renvoie la météo actuelle pour une ville donnée",
    "parameters": {
      "type": "object",
      "properties": {
        "ville": {
          "type": "string",
          "description": "Nom de la ville, ex : Lyon"
        },
        "unite": {
          "type": "string",
          "enum": ["celsius", "fahrenheit"],
          "description": "Unité de température souhaitée"
        }
      },
      "required": ["ville"]
    }
  }
}
→
スキーマの作成をライブラリに任せる
Pythonでは、このJSONを手作業で書く必要はありません。型注釈とdocstringを備えた関数を直接渡せば、ollamaライブラリがスキーマ(名前、型、説明)を自動的に導き出します。これは、JSON内のタイプミスを避ける最も確実な方法です。

#Pythonでの最初のツール呼び出し

まずは最も簡単なケースから始めましょう。関数を1つ、質問を1つ用意して、モデルがどう判断するかを観察します。型アノテーションとdocstringは、モデルに送るスキーマを生成するために使われます。

最初のtool call
import ollama

def get_meteo(ville: str, unite: str = "celsius") -> str:
    """Renvoie la météo actuelle pour une ville donnée.

    Args:
        ville: Nom de la ville (ex : Lyon).
        unite: Unité de température, celsius ou fahrenheit.
    """
    # Ici, un vrai appel à une API météo. On simule le retour.
    return f"21 degrés, ciel dégagé à {ville} ({unite})."

reponse = ollama.chat(
    model="qwen3.5:4b",
    messages=[{"role": "user", "content": "Quel temps fait-il à Lyon ?"}],
    tools=[get_meteo],  # la lib introspecte signature + docstring
)

# Le modèle n'a pas répondu en texte : il demande un outil
for appel in reponse.message.tool_calls or []:
    print(appel.function.name)       # -> get_meteo
    print(appel.function.arguments)  # -> {'ville': 'Lyon'}

この段階では、message.contentは通常空です。モデルは呼び出し要求をmessage.tool_callsに格納して返しています。各tool_callには、function.name(文字列)とfunction.arguments(ライブラリによってすでにPythonの辞書にデシリアライズ済み)が含まれています。あとはツールを実行して結果を返すだけです。

#完全な実行ループ

以下は、ツール呼び出しを行うエージェントの再利用可能な基本構成です。各ツール名と対応する関数を辞書で結び付け、要求された呼び出しを実行し、結果を履歴に追加してから、最終回答のために2回目の呼び出しを行います。モデルが複数のツールを続けて呼び出す場合に対応できるよう、全体をループで囲みます。

エージェントループの全体
import ollama

def get_meteo(ville: str, unite: str = "celsius") -> str:
    """Météo actuelle d'une ville."""
    return f"21 degrés, ciel dégagé à {ville}."

# Registre nom -> fonction réelle
OUTILS = {"get_meteo": get_meteo}

messages = [{"role": "user", "content": "Météo à Lyon puis à Marseille ?"}]

while True:
    reponse = ollama.chat(model="qwen3.5:4b", messages=messages, tools=[get_meteo])
    messages.append(reponse.message)  # on garde la demande dans l'historique

    if not reponse.message.tool_calls:
        # Plus d'outil demandé : c'est la réponse finale
        print(reponse.message.content)
        break

    for appel in reponse.message.tool_calls:
        fonction = OUTILS.get(appel.function.name)
        if fonction is None:
            resultat = f"Erreur : outil inconnu '{appel.function.name}'"
        else:
            resultat = fonction(**appel.function.arguments)
        messages.append({
            "role": "tool",
            "tool_name": appel.function.name,
            "content": str(resultat),
        })
!
引数を決して信用しない
引数はモデルが生成するため、不完全だったり、型が不適切だったり、許容範囲を外れていたりする可能性があります。関数を実行する前に引数を検証してください。特に、ファイルシステム、データベース、シェルコマンドを扱う関数では重要です。検証を行わないツール呼び出しは、インジェクション攻撃の入り口になります。

結果メッセージには tool ロールと、どの呼び出しへの応答なのかを示す tool_name フィールドが含まれます。content は文字列でなければなりません。オブジェクトは返す前に json.dumps でシリアライズしてください。モデルはこの内容を、外界を観察して得られた情報であるかのように読み直します。

#OpenAIと同等の使い方:openaiクライアントで同じコードを使う

Ollama は /v1 に OpenAI 互換のエンドポイントを提供しています。コードですでに openai クライアントを使用している場合、変更はほとんど不要です。base_url を Ollama に向け、ダミーの API キーを設定してください。ツールと tool_calls の形式は同一です。この「OpenAI との同等性」によって、移行が非常に簡単になります。

OpenAIクライアント経由でのツール呼び出し
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

reponse = client.chat.completions.create(
    model="qwen3.5:4b",
    messages=[{"role": "user", "content": "Météo à Lyon ?"}],
    tools=[{
        "type": "function",
        "function": {
            "name": "get_meteo",
            "description": "Météo actuelle d'une ville",
            "parameters": {
                "type": "object",
                "properties": {"ville": {"type": "string"}},
                "required": ["ville"],
            },
        },
    }],
)

print(reponse.choices[0].message.tool_calls)
i
覚えておくべき違い
openaiクライアントでは、function.argumentsはJSON文字列として返されます(json.loadsでパースする必要があります)。一方、ollamaのネイティブライブラリでは、すでに辞書形式で返されます。クライアントを切り替える際は、この違いに注意してください。

#ツール呼び出しのストリーミング(0.17系)

以前は、ストリーミングを有効にするとツール呼び出しが無効になるため、どちらかを選ぶ必要がありました。0.17シリーズ以降、Ollamaは生成の進行に合わせてツール呼び出しをストリーミングできるようになりました。具体的には、ストリームのチャンク内でtool_callsを受け取り、テキストがある場合はそれも同時に受け取ります。これにより、滑らかに応答を表示しながらツールを実行できます。

ツール呼び出しのストリーミング
import ollama

flux = ollama.chat(
    model="qwen3.5:4b",
    messages=[{"role": "user", "content": "Météo à Nice ?"}],
    tools=[get_meteo],
    stream=True,
)

for morceau in flux:
    # Le texte arrive token par token
    if morceau.message.content:
        print(morceau.message.content, end="", flush=True)
    # Les appels d'outils arrivent aussi dans le flux
    for appel in morceau.message.tool_calls or []:
        print("\n[outil]", appel.function.name, appel.function.arguments)
→
ストリーミングのタイミング
ストリーミングは、ユーザーが回答の生成過程を見られる会話型インターフェースで特に効果を発揮します。バッチ処理やデータ抽出では、非ストリーミングモードを使ってください。そのほうが扱いやすく、回答全体を一度に取得できます。

#構造化出力:JSON Schemaに準拠した出力を強制する

ツール呼び出しは操作を実行するためのもので、構造化出力は応答の形式を保証するためのものです。formatパラメータにJSON Schemaを渡すと、Ollamaがデコード時にそのスキーマを適用します。モデルにはトークンごとに制約がかかり、スキーマに適合する有効な出力だけを生成するようになります。不完全なJSONを不安定な方法でパースする必要はなくなり、仕組みそのものによって構造が保証されます。

Pythonで最も実用的な方法は、Pydanticモデルで構造を定義し、model_json_schema()でスキーマを取得することです。その後、型付きかつ検証されたオブジェクトを取得できます。

Pydantic による構造化出力の検証
from pydantic import BaseModel
import ollama

class Facture(BaseModel):
    numero: str
    montant_ttc: float
    devise: str
    lignes: list[str]

reponse = ollama.chat(
    model="qwen3.5:4b",
    messages=[{
        "role": "user",
        "content": "Extrais numéro, montant TTC, devise et lignes de : "
                   "Facture F-2026-0042, total 149,90 EUR, "
                   "prestations : audit, rédaction.",
    }],
    # Le schéma est appliqué au décodage : sortie garantie conforme
    format=Facture.model_json_schema(),
)

facture = Facture.model_validate_json(reponse.message.content)
print(facture.montant_ttc)  # -> 149.9
i
format="json" と完全なスキーマの比較
format="json"は有効なJSONの出力だけを強制し、構造までは指定しません。完全なJSON Schemaを渡すと、さらに踏み込んで、デコード時にフィールド、型、許可される値を制約できます。期待する形式が分かっている場合は、常に明示的なスキーマを使ってください。
→
最適な組み合わせ
ツール呼び出しでデータを取得し、構造化出力で整った形で返します。APIを呼び出した後に、検証済みのPydanticオブジェクトを返すエージェントは、「JSONで答えてください」とモデルに頼んで、うまくいくことを期待するだけの場合よりも、はるかに堅牢です。

#エラー処理とよくある落とし穴

ツール呼び出しの失敗が目立つ形で現れることはまれです。多くの場合、モデルは気づかれないまま「脱線」します。よくある不具合と、その対処方法を紹介します。

tool_callが返されません
ツールを使う必要があったのに、モデルはテキストで回答しました。ツールの説明を改善するか、モデルを変更してください。小型モデルは、ツールを呼び出すべきかどうかの判断をよく誤ります。
欠落または誤った引数
function.argumentsでは、requiredで指定されたフィールドが欠けていたり、型が誤っていたりすることがあります。実際の関数を呼び出す前にPydanticまたはtry/exceptで検証し、エラーをツールの結果としてモデルに返してください。
モデルが捏造したツール
モデルが存在しない関数名を作り出します。そのため、OUTILS.get(name)を使い、処理が異常終了する代わりにエラーメッセージを返すようにしています。これにより、モデルは次のターンで自分の誤りを修正できます。
ツール呼び出しの無限ループ
モデルは同じツールを無限に繰り返し要求する可能性があります。最大反復回数カウンター(例:5)を追加することで、ループを切断し、無駄な処理を回避できます。
コンテキストの切り詰め
Ollamaはデフォルトでコンテキストを2048トークンに制限することがあり、長いセッションではツールの履歴が切り捨てられてしまいます。モデルのオプションでnum_ctxを増やしてください。
シリアライズされていない結果
Pythonオブジェクトをそのままcontentとして返すと、リクエストが正常に処理されなくなります。メッセージに追加する前に、必ずjson.dumpsまたはstrで文字列に変換してください。
ツールの防御的実行
import json

def executer_outil(appel, registre, garde_fou=5):
    nom = appel.function.name
    fonction = registre.get(nom)
    if fonction is None:
        return f"Erreur : outil inconnu '{nom}'."
    try:
        resultat = fonction(**appel.function.arguments)
    except TypeError as e:
        return f"Erreur d'arguments pour {nom} : {e}"
    except Exception as e:
        return f"Échec de {nom} : {e}"
    return json.dumps(resultat, ensure_ascii=False, default=str)

キーポイントは、ツールのエラーがエージェントをクラッシュさせないことです。エラーメッセージをモデルにそのまま返すようにし、そのメッセージを結果として処理させます。良いモデルは「認識できないツール」や「欠落した引数」といった情報を読み取り、次の呼び出しを自ら調整します。


#さらに詳しく

これで、PythonでOllamaのツール呼び出しを使う方法が身につきました。ツールのJSON形式、実行ループ、OpenAIと同等の機能、ストリーミング、スキーマで制約された構造化出力を扱えます。以下のガイドでは、このテーマをさらに掘り下げます。

Ollama の REST API
「REST APIを使ってOllamaをPythonアプリケーションに統合する」—エンドポイント:11434、ストリーミング、JSONモードの基礎を解説し、このガイド全体の土台となるガイドです。
LangChain と組み合わせたエージェント
「PythonでLangChainとOllamaを使ってローカルAIエージェントを作成する」— 基本的なツール呼び出しを土台に、複数のツールとメモリを連携させる。
量子化の選び方
「量子化の選択(Q4、Q5、Q8、FP16)」— ツールを操作するモデルのVRAM使用量と品質のバランスを取るために。
このガイドは役に立ちましたか?

ご意見、誤りのご指摘、補足はありますか?ぜひお知らせください。皆さんにとってより良いガイドにするために役立ちます。