8.3.4 関数呼び出し 入門

- 自然言語の出力だけでは、なぜ安定してツールを呼び出しにくいのかを理解する
- 関数 スキーマ、パラメータ、呼び出し結果という 3 つの核心概念を理解する
- 最小限の関数呼び出しの一連の流れを読み取れるようになる
- 関数呼び出し がどのような場面に最も向いているかを知る
初心者はまず押さえる / 上級者はあとで理解する
Section titled “初心者はまず押さえる / 上級者はあとで理解する”もしあなたが初心者なら、この節ではまず 1 つのことを押さえてください。Function Calling は、モデルに本当にコードを実行させるものではありません。まず構造化された「呼び出し意図」を出力させ、その後でプログラムがそれを検査・実行・結果返却します。
すでに LLM アプリを作ったことがあるなら、次の点をさらに意識するとよいでしょう。ツール schema は十分に明確か、パラメータ検証は完全か、ツール失敗時にどう再試行またはフォールバックするか、呼び出しログはデバッグや評価に十分使えるか。
一、なぜ純テキスト出力だけでは足りないのか?
Section titled “一、なぜ純テキスト出力だけでは足りないのか?”よくある不安定なやり方
Section titled “よくある不安定なやり方”たとえば、ユーザーがこう聞いたとします。
「北京の今日の気温は?」
モデルに 1 文で返させます。
「
get_weather(city='Beijing')を呼び出すのがおすすめです」
一見使えそうに見えますが、実はかなり脆いです。
- フォーマットが安定しない
- パラメータ名を間違える可能性がある
- 都市名が「北京」「Beijing」「北京市」のように揺れる
- さらに説明文を大量に付け足すこともある
本当の問題は何か?
Section titled “本当の問題は何か?”問題は、モデルがタスクを理解できないことではありません。むしろ、
自然言語は自由すぎて、安定したプログラムインターフェースには向かない。
という点にあります。
プログラムが好むのは次のようなものです。
- 固定されたフィールド
- 明確なパラメータ
- 検証可能な構造
ここに Function Calling の価値があります。
二、関数呼び出し とは何か?
Section titled “二、関数呼び出し とは何か?”一言で理解する
Section titled “一言で理解する”関数呼び出し = モデルに自由文ではなく、構造化されたツール呼び出しを出力させること。
通常、含まれるのは次の 2 つです。
- どのツールを呼ぶか
- どんなパラメータを渡すか
たとえば、こんな形です。
{ "name": "get_weather", "arguments": { "city": "Beijing" }}自由文より何が優れているのか?
Section titled “自由文より何が優れているのか?”プログラムインターフェースに近いからです。雑談文ではありません。
プログラムはこの構造を受け取ったら、次のようなことができます。
- フィールドを検証する
- 自動で実行する
- 失敗時に再試行する
- ログを残す
つまり、Function Calling はモデルとプログラムの間に橋を架けるものです。
三、まず最小の一連の流れを見てみよう
Section titled “三、まず最小の一連の流れを見てみよう”2 つのツールを定義する
Section titled “2 つのツールを定義する”import astimport operator
OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv,}
def safe_calculate(expression): def visit(node): if isinstance(node, ast.Expression): return visit(node.body) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in OPS: return OPS[type(node.op)](visit(node.left), visit(node.right)) if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): return -visit(node.operand) raise ValueError("unsupported_expression")
return visit(ast.parse(expression, mode="eval"))
def get_weather(city): data = { "Beijing": {"temperature": 22, "condition": "sunny"}, "Shanghai": {"temperature": 25, "condition": "cloudy"} } return data.get(city, {"error": "city_not_found"})
def calculate(expression): return {"result": safe_calculate(expression)}「モデルの出力」を表す呼び出し構造を定義する
Section titled “「モデルの出力」を表す呼び出し構造を定義する”tool_call = { "name": "get_weather", "arguments": { "city": "Beijing" }}
print(tool_call)想定出力:
{'name': 'get_weather', 'arguments': {'city': 'Beijing'}}この呼び出しを実際に実行する
Section titled “この呼び出しを実際に実行する”def dispatch(call): if call["name"] == "get_weather": return get_weather(**call["arguments"]) if call["name"] == "calculate": return calculate(**call["arguments"]) return {"error": "unknown_tool"}
tool_call = { "name": "get_weather", "arguments": {"city": "Beijing"}}
result = dispatch(tool_call)print(result)想定出力:
{'temperature': 22, 'condition': 'sunny'}これが関数呼び出しの最小閉ループです。
- タスクを識別する
- 構造化された呼び出しを出力する
- プログラムが実行する
- 結果を受け取る
四、スキーマ とは何か?
Section titled “四、スキーマ とは何か?”スキーマ は「ツールの説明書」と考えればよい
Section titled “スキーマ は「ツールの説明書」と考えればよい”モデルが正しくツールを呼び出すには、次の情報が必要です。
- ツール名
- 各パラメータ名
- パラメータの型
- 必須かどうか
これが schema の役割です。
シンプルな スキーマ の例
Section titled “シンプルな スキーマ の例”weather_schema = { "name": "get_weather", "description": "指定した都市の天気を取得する", "parameters": { "city": { "type": "string", "description": "都市の英語名。例: Beijing" } }, "required": ["city"]}
print(weather_schema)想定出力:
{'name': 'get_weather', 'description': '指定した都市の天気を取得する', 'parameters': {'city': {'type': 'string', 'description': '都市の英語名。例: Beijing'}}, 'required': ['city']}schema は「見た目を整えるための文」ではありません。モデルとプログラムにこう伝えています。
このツールは、このように呼び出せる。
五、なぜパラメータ検証が重要なのか?
Section titled “五、なぜパラメータ検証が重要なのか?”モデルがいつも正しいパラメータを返すとは限らない
Section titled “モデルがいつも正しいパラメータを返すとは限らない”モデルがツールの選択を正しくできても、次のようなミスは起こりえます。
- フィールドが抜ける
- 型が違う
- パラメータ値が無効
たとえば、
bad_call = { "name": "get_weather", "arguments": {"city_name": "Beijing"}}もしプログラムが検証しなければ、実行時にそのまま落ちてしまいます。
最小の検証例
Section titled “最小の検証例”def validate_weather_call(call): if call.get("name") != "get_weather": return False, "wrong_tool"
args = call.get("arguments", {}) if "city" not in args: return False, "missing_city" if not isinstance(args["city"], str): return False, "city_must_be_string"
return True, "ok"
good_call = {"name": "get_weather", "arguments": {"city": "Beijing"}}bad_call = {"name": "get_weather", "arguments": {"city_name": "Beijing"}}
print(validate_weather_call(good_call))print(validate_weather_call(bad_call))想定出力:
(True, 'ok')(False, 'missing_city')六、より完全な例:天気と計算機
Section titled “六、より完全な例:天気と計算機”まず「モデルがどのツールを呼ぶか」をまねる
Section titled “まず「モデルがどのツールを呼ぶか」をまねる”ここでは本物の大規模モデルは使いません。まずは学習用のルール関数を書き、「ツール呼び出しの構造」を見やすくします。
def mock_llm_tool_selector(user_query): if "天気" in user_query: city = "Beijing" if "北京" in user_query else "Shanghai" return { "name": "get_weather", "arguments": {"city": city} }
if "計算" in user_query: expression = user_query.replace("計算", "").strip() return { "name": "calculate", "arguments": {"expression": expression} }
return None次に実行器をつなぐ
Section titled “次に実行器をつなぐ”import astimport operator
OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv,}
def safe_calculate(expression): def visit(node): if isinstance(node, ast.Expression): return visit(node.body) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in OPS: return OPS[type(node.op)](visit(node.left), visit(node.right)) if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): return -visit(node.operand) raise ValueError("unsupported_expression")
return visit(ast.parse(expression, mode="eval"))
def get_weather(city): data = { "Beijing": {"temperature": 22, "condition": "sunny"}, "Shanghai": {"temperature": 25, "condition": "cloudy"} } return data.get(city, {"error": "city_not_found"})
def calculate(expression): return {"result": safe_calculate(expression)}
def dispatch(call): if call["name"] == "get_weather": return get_weather(**call["arguments"]) if call["name"] == "calculate": return calculate(**call["arguments"]) return {"error": "unknown_tool"}
queries = [ "北京今天天気はどうですか", "計算 3 * (4 + 5)"]
for q in queries: call = mock_llm_tool_selector(q) result = dispatch(call) print("ユーザーの質問:", q) print("ツール呼び出し:", call) print("実行結果:", result) print("-" * 40)想定出力:
ユーザーの質問: 北京今天天気はどうですかツール呼び出し: {'name': 'get_weather', 'arguments': {'city': 'Beijing'}}実行結果: {'temperature': 22, 'condition': 'sunny'}----------------------------------------ユーザーの質問: 計算 3 * (4 + 5)ツール呼び出し: {'name': 'calculate', 'arguments': {'expression': '3 * (4 + 5)'}}実行結果: {'result': 27}----------------------------------------
この例は、実際のシステムの骨組みにかなり近いです。
七、関数呼び出し はどんなタスクに向いているのか?
Section titled “七、関数呼び出し はどんなタスクに向いているのか?”特に向いているもの
Section titled “特に向いているもの”- 天気の取得
- ナレッジベース検索
- データベース検索
- 数学計算
- 検索 API の呼び出し
- チケット起票
つまり、
モデルは「何をするか」を決め、プログラムが実際に実行する。
あまり向いていないもの
Section titled “あまり向いていないもの”タスクの本質が次のような場合です。
- 文章を書く
- 自由な生成を行う
- ただの雑談をする
このような場合は、必ずしも Function Calling が必要とは限りません。
八、知識ベース駆動の SOP 文書アシスタントを作るなら、最小ツールセットはどうあるべきか?
Section titled “八、知識ベース駆動の SOP 文書アシスタントを作るなら、最小ツールセットはどうあるべきか?”この種のプロジェクトを初めて作るとき、最初から何十個ものツールを用意する必要はありません。
より安定した最小ツールセットは、通常たった 4 つで十分です。
-
retrieve_internal_docs(topic)
社内 SOP、ポリシー、ケース文書を検索する -
check_policy_case_coverage(materials)ドラフトに必要なポリシー根拠とケース根拠が足りているか確認する -
build_sop_draft_schema(materials)資料を policy、case、checklist、citations の欄に整理する -
export_word(schema)
SOP テンプレートを当てて Word に出力する
まずはこう考えるとよいです。
- モデルが直接 Word を書くわけではない
- モデルは「次にどの工程を呼ぶか」を決めている
小さなツール定義の例は、次のように書けます。
tools = [ { "name": "retrieve_internal_docs", "description": "テーマに基づいて社内 SOP とポリシー文書を検索する", "parameters": {"topic": {"type": "string"}}, }, { "name": "export_word", "description": "構造化された SOP ドラフトを Word 文書として出力する", "parameters": {"title": {"type": "string"}, "sections": {"type": "array"}}, },]
print(tools)想定出力:
[{'name': 'retrieve_internal_docs', 'description': 'テーマに基づいて社内 SOP とポリシー文書を検索する', 'parameters': {'topic': {'type': 'string'}}}, {'name': 'export_word', 'description': '構造化された SOP ドラフトを Word 文書として出力する', 'parameters': {'title': {'type': 'string'}, 'sections': {'type': 'array'}}}]九、最もよくある実装上の問題
Section titled “九、最もよくある実装上の問題”ツールの選択を間違える
Section titled “ツールの選択を間違える”本当はナレッジベースを検索すべきなのに、計算機を呼んでしまう例です。
パラメータが安定しない
Section titled “パラメータが安定しない”たとえば次のように揺れます。
citycity_namelocation
モデルは混ぜて使ってしまうことがあります。
ツール実行の失敗
Section titled “ツール実行の失敗”ツール呼び出しの構造が正しくても、次のような失敗は起こります。
- API タイムアウト
- パラメータが不正
- 都市が存在しない
つまり、
関数呼び出し は「モデルがツールを呼べるようになれば終わり」ではありません。その後ろに、必ず実装上の安全網が必要です。
十、初心者がよくやってしまうミス
Section titled “十、初心者がよくやってしまうミス”関数呼び出し を「モデルが直接コードを実行するもの」と思う
Section titled “関数呼び出し を「モデルが直接コードを実行するもの」と思う”違います。
モデルは構造化された呼び出し意図を出力するだけで、実際に実行するのはあなたのプログラムです。
ツール スキーマ があいまいすぎる
Section titled “ツール スキーマ があいまいすぎる”ツール説明が不十分で、パラメータ定義も不明確だと、モデルは誤った呼び出しをしやすくなります。
パラメータ検証をしない
Section titled “パラメータ検証をしない”本番環境に入ると、これは非常に危険な習慣です。
このページを終えたら、この証拠カードを残します。
- 要求
- 入力、状態、tools/context、期待される出力の契約
- 検証済み出力
- パーサー/スキーマ、または業務ルール確認の結果
- 追跡記録
- モデル呼び出し、ツール/関数呼び出し、文書解析、または対話状態
- 失敗確認
- フォーマット不正、必須フィールド不足、古い状態、または誤ったツール
- 次の行動
- prompt、schema、state、API、または parsing の改善
まとめを見る前に:関数呼び出し の工程ループ
Section titled “まとめを見る前に:関数呼び出し の工程ループ”flowchart LR A["ユーザーの質問"] --> B["モデルがツールが必要か判断する"] B --> C["構造化された tool call を出力する"] C --> D["パラメータ検証"] D --> E{"検証に通るか"} E -- はい --> F["プログラムがツールを実行する"] E -- いいえ --> G["エラーを返す / パラメータを再生成する"] F --> H["ツールの結果"] H --> I["モデルが最終回答をまとめる"]
style C fill:#e3f2fd,stroke:#1565c0,color:#333 style D fill:#fff3e0,stroke:#e65100,color:#333 style F fill:#e8f5e9,stroke:#2e7d32,color:#333このループはとても重要です。なぜなら、Function Calling の難しさは「モデルが関数名を言えるかどうか」ではなく、モデル、schema、検証、実行器、エラー処理がそろって安定したシステムになるかどうかにあるからです。

この節の学習ループ
Section titled “この節の学習ループ”| レベル | できるようになること |
|---|---|
| 直感 | 自由文がなぜそのままプログラムインターフェースに向かないか説明できる |
| コード | 最小の tool call、dispatch、パラメータ検証関数を書ける |
| 工学 | スキーマ、検証、エラー処理、ログがそれぞれ何を担当するか説明できる |
| 次のステップとのつながり | 関数呼び出し が Agent のツール呼び出しの前提になる理由を理解できる |
この節でいちばん大事なのは、name と arguments の 2 つのフィールドを覚えることではありません。本質は次の一点です。
関数呼び出し は、モデルの自然言語理解能力を、プログラムの構造化された実行能力につなぐ仕組みである。
この点を理解できると、次に Agent、ツール戦略、複数ツールの協調を学ぶときに、かなりスムーズになります。
- この節の例に
search_docs(keyword)という新しいツールを追加してみましょう。 calculateのパラメータ検証関数を書いて、危険な式を防いでみましょう。- もしモデルが「北京の天気」を何度も
calculateに誤ルーティングするなら、まず prompt、スキーマ、実行器のどれを直しますか? - 自分の言葉で説明してみましょう。関数呼び出し は、なぜ「モデルにコマンド文を直接返させる」より安定しているのでしょうか?
操作例と確認ポイント
search_docs(keyword)には入力 schema、検証ルール、実行器の戻り値、失敗時の挙動を定義します。calculateの検証では、数字、安全な演算子、括弧だけを許可するか、AST whitelist を使います。任意文字列をeval()してはいけません。- まず tool schema と説明文を直し、必要なら prompt 例を追加します。実行器は不正引数を拒否できますが、routing を単独で教えることはできません。
- Function Calling は構造化引数、型付き schema、検証点、監査可能な tool call を持つため、自由形式のコマンド文字列より安定します。