ツールの可用性の制御

Note

プログレッシブ ツール公開 API (FunctionInvocationContext.add_tools / remove_tools) は現在Python専用です。

このページでは、ワークフローを必要とせずに、モデルが呼び出すことができるツールと、すべての 1 つのエージェント実行内の順序を制御するための 3 つの補完的な手法について説明します。

  • プログレッシブ ツールの公開 — 実行時にツールまたは関数ミドルウェア内からツールを追加または削除するため、モデルには使用できるツールのみが表示されます。
  • ミドルウェアゲーティング — 関数ミドルウェアを使用して、基になる関数を実行せずに呼び出し引数を検証し、修正フィードバックを返します。
  • 強制最初の呼び出しtool_choice を使用して、他の前に特定のツールを呼び出すモデルを要求します。

Note

"常にget_record前にupdate_recordを呼び出す" などのペアワイズ順序制約では、ワークフローは必要ありません。 このページの手法は、1 回の実行内でそのパターンを処理します。 ワークフローは、複数回の実行や並列ブランチにまたがる、真に複数ステップのオーケストレーションを行うためのものです。

ツールの段階的な表示

プログレッシブ ツール露出を使用すると、小さなツール セットで実行を開始し、以前のツールの結果に応じてツールを追加または削除できます。すべて同じ実行内です。 モデルでは、関数呼び出しループの 次のイテレーション でのみ更新されたセットが表示されます。実行中のバッチで既に要求されているツール呼び出しは、変更が有効になるまで引き続き実行されます。

API は試験的であり、 FunctionInvocationContextに存在します。

メンバー 説明
ctx.tools 現在の実行におけるツールの動的で変更可能な listNone 関数が関数呼び出しループの外部で呼び出されたとき。
ctx.add_tools(tools) 1 つ以上のツールを追加します。 呼び出し可能オブジェクトは FunctionTool でラップされます。 同じオブジェクトを再追加するのは no-opです。重複する名前を持つ別のオブジェクトが ValueErrorを発生させます。 全か無か: バッチ内のいずれかのツールでエラーが発生する場合、どのツールも追加されません。
ctx.remove_tools(tools) 名前、ツールオブジェクト、または呼び出し可能オブジェクトを指定して削除します。 リストに存在しない名前は、暗黙的に無視されます。

どちらのヘルパーも、プロセス内で最初に呼び出されたときに ExperimentalWarning を出力します(機能 ID PROGRESSIVE_TOOLS)。 関数呼び出しループの外部でいずれかのヘルパーを呼び出すと、 RuntimeErrorが発生します。

重要

ツール リストは、新しい agent.run() 呼び出しごとに元のセットにリセットされるため、ターンごとにすべてのゲートが自動的に再アームされます。

Note

プログレッシブ ツールの公開は、標準の関数呼び出しループにのみ適用されます。 CodeAct プロバイダー (agent-framework-montyagent-framework-hyperlight) では使用できません。モデルでは、個々のツール スキーマではなく、単一のコード実行サーフェスが表示されます。 CodeAct サンドボックス内から add_tools または remove_tools を呼び出すと、 RuntimeErrorが発生します。 CodeAct エージェントのツール セットを変更するには、実行の間にプロバイダー独自の add_tools / remove_tool / clear_tools メソッドを使用します。

ローダーツールパターン

少数の "ローダー" ツールセットを前もって登録し、モデルが必要に応じて追加のツールをプルできるようにします。 これにより、初期スキーマが小さく保たれ、ツールの選択精度が向上し、コストが削減されます。

import asyncio
import warnings
from typing import Annotated

from agent_framework import Agent, FunctionInvocationContext, tool
from agent_framework.openai import OpenAIChatClient
from pydantic import Field

warnings.filterwarnings("ignore", category=FutureWarning)  # suppress ExperimentalWarning for brevity


@tool(approval_mode="never_require")
def factorial(n: Annotated[int, Field(description="A non-negative integer.")]) -> str:
    """Compute the factorial of n."""
    if n < 0:
        return "Error: n must be a non-negative integer."
    result = 1
    for value in range(2, n + 1):
        result *= value
    return f"{n}! = {result}"


@tool(approval_mode="never_require")
def fibonacci(n: Annotated[int, Field(description="The 0-based index in the Fibonacci sequence.")]) -> str:
    """Compute the n-th Fibonacci number."""
    if n < 0:
        return "Error: n must be a non-negative integer."
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return f"fib({n}) = {a}"


# The ctx parameter is injected by the framework and is NOT visible to the model.
@tool(approval_mode="never_require")
def load_math_tools(ctx: FunctionInvocationContext) -> str:
    """Load additional math tools (factorial, fibonacci) so they can be used."""
    ctx.add_tools([factorial, fibonacci])
    return "Loaded math tools: factorial, fibonacci. You can now call them."


async def main() -> None:
    agent = Agent(
        client=OpenAIChatClient(),
        name="MathAgent",
        instructions=(
            "You are a math assistant. "
            "If you need math capabilities that are not yet available, call load_math_tools first."
        ),
        tools=[load_math_tools],  # agent starts with only the loader
    )
    print(await agent.run("What is 5 factorial?"))


asyncio.run(main())

完全に実行可能なサンプルは、 python/samples/02-agents/tools/dynamic_tool_exposure.pyにあります。

ゲーティングパターン

最初に読み取りツールのみを登録します。 読み取りツールは、フェッチが成功した後に書き込みツールを追加するため、読み取りツールが実行される前にモデルで書き込みツールを呼び出すことはできません。

from agent_framework import Agent, FunctionInvocationContext, tool
from agent_framework.openai import OpenAIChatClient

_last_fetched_id: str | None = None


@tool(approval_mode="never_require")
def get_record(record_id: str, ctx: FunctionInvocationContext) -> str:
    """Fetch a record. Unlocks update_record for the same record."""
    global _last_fetched_id
    _last_fetched_id = record_id
    ctx.add_tools(update_record)  # gate: expose the write tool now
    return f"Record {record_id}: title='Example record', status='open'"


@tool(approval_mode="never_require")
def update_record(record_id: str, status: str) -> str:
    """Update the status of a record."""
    return f"Updated record {record_id} to status '{status}'."


agent = Agent(
    client=OpenAIChatClient(),
    name="RecordAgent",
    instructions="You help manage records. Fetch a record before updating it.",
    tools=[get_record],  # update_record is hidden until get_record runs
)

ctx.toolsは実行の開始時に[get_record]にリセットされるため、ゲートは会話ターンごとに自動的に再アームします。

ミドルウェア制御

関数ミドルウェアは、保留中のツール呼び出しの引数を検査し、基になる関数が実行される前にそれを拒否するには、context.resultを呼び出さずにcall_next()を設定します。 context.resultに割り当てられた文字列は、関数の結果としてモデルに返され、修正フィードバックが提供されます。

これは、スキーマ定義時に使用できない情報を必要とする引数レベルのチェックに役立ちます。たとえば、更新プログラムが、実行の前にフェッチされたものと同じ項目を対象としていることを確認します。

from collections.abc import Awaitable, Callable

from agent_framework import FunctionInvocationContext

_last_fetched_id: str | None = None


async def enforce_read_before_write(
    context: FunctionInvocationContext,
    call_next: Callable[[], Awaitable[None]],
) -> None:
    """Reject update_record calls that target a different record than the one fetched."""
    if context.function.name == "update_record":
        requested_id = context.arguments.get("record_id") if hasattr(context.arguments, "get") else None
        if requested_id != _last_fetched_id:
            # Set result without calling call_next — the function never executes.
            context.result = (
                f"Error: you must fetch record '{requested_id}' before updating it. "
                f"Last fetched record was '{_last_fetched_id}'."
            )
            return
    await call_next()

ミドルウェアをエージェントに追加します。

agent = Agent(
    client=OpenAIChatClient(),
    name="RecordAgent",
    instructions="Fetch a record before updating it.",
    tools=[get_record, update_record],
    middleware=[enforce_read_before_write],
)

関数ミドルウェアの詳細については、「ミドルウェアと結果のオーバーライドの定義」を参照してください。

を使用してツール呼び出しを強制する tool_choice

モデルが最初のアクションとして特定のツールを呼び出すように要求するには、モード tool_choice"required"required_function_nameを渡します。 フレームワークは、最初のイテレーションの後に tool_choice を自動的に None にリセットし、後続のイテレーションでモデルが解放されるようにします。

result = await agent.run(
    "Update record REC-42 to status 'in-progress'.",
    options={"tool_choice": {"mode": "required", "required_function_name": "get_record"}},
)

tool_choice フィールドは、ToolMode dict、または短縮形文字列の"auto""required"、または"none"を受け取ります。

from agent_framework import ToolMode

tool_choice: ToolMode = {"mode": "required", "required_function_name": "get_record"}

セマンティクスと注意事項

Behavior Detail
次の反復効果 add_tools / remove_tools 変更は、次のループイテレーションでモデルに表示されます。 現在のバッチですでに送信されているツール呼び出しは、そのまま完了します。
処理中のバッチ モデルが 1 つのバッチで複数のツールを要求した場合は、更新されたツール リストが返送される前にすべてのツールが実行されます。
重複する名前 まったく同じオブジェクトを再追加するのは no-opです。 既存のツールと一致する名前の別のオブジェクトを追加すると、 ValueErrorが発生します。 バッチ全体が追加される前に検証されるため、リストの途中で重複すると、ライブ リストは変更されません。
外部ループ エラー add_toolsremove_toolsを発生させるときにctx.tools is NoneまたはRuntimeErrorを呼び出します。 これは、エージェント ループではなく、関数が直接 ( FunctionTool.invoke 経由で) 呼び出された場合に発生します。
試験段階の状態 どちらのヘルパーも、プロセスごとの最初の呼び出しで ExperimentalWarning を出力します。 必要に応じて、 warnings.filterwarnings("ignore", category=FutureWarning) で抑制します。
実行ごとのスコープ ライブ ツールリストは、各normalize_tools呼び出しの開始時にagent.run()から作成された新しいコピーです。 呼び出し元の元の tools コンテナーは変更されません。
CodeAct の除外 agent-framework-montyまたは agent-framework-hyperlight CodeAct プロバイダーでは使用できません。

次のステップ