控制工具可用性

注释

渐进式工具开放 API(FunctionInvocationContext.add_tools / remove_tools)目前仅支持 Python。

本页介绍三种补充技术,用于控制模型可以调用哪些工具以及按何种顺序在单个代理运行中运行,而无需工作流:

  • 渐进式工具公开 - 在运行时从工具或函数中间件中添加或删除工具,因此模型只看到它已准备好使用的工具。
  • 中间件检查 - 使用函数中间件验证调用参数并返回纠正反馈,而无需执行基础函数。
  • 强制首次调用 - 用于 tool_choice 要求模型在任何其他工具之前调用特定工具。

注释

成对顺序约束(例如“始终在调用 get_record 之前调用 update_record”)无需工作流。 本页介绍的技术可在单次运行中处理该模式。 工作流适用于跨多次运行或并行分支的真正多步骤编排。

渐进式工具曝光

工具逐步开放使您能够先使用一小组工具开始一次运行,并根据先前的工具结果添加或移除工具,而这一切都在同一次运行中完成。 该模型仅在函数调用循环的 下一次迭代 中看到更新集;在正在进行的批处理中请求的工具调用在更改生效之前仍在执行。

API 是实验性的,并位于以下位置 FunctionInvocationContext

成员 说明
ctx.tools 当前运行的实时可变 list 工具。 None 当函数在函数调用循环外被调用时。
ctx.add_tools(tools) 添加一个或多个工具。 可调用对象会被包装为 FunctionTool。 重新添加同一对象不会执行任何操作;名称重复的不同对象会引发 ValueError。 全有或全无:如果批处理中任何一个工具会引发错误,则不会添加其中任何工具。
ctx.remove_tools(tools) 按名称、工具对象或可调用对象删除。 列表中不存在的名称将被无提示忽略。

这两个辅助函数在进程中首次被调用时会发出 ExperimentalWarning(功能 ID 为 PROGRESSIVE_TOOLS)。 在函数调用循环之外调用任一辅助函数都会引发 RuntimeError

Important

工具列表会在每次发起新的 agent.run() 调用时重置为初始集合,因此每一轮都会自动重新启用所有门。

注释

渐进式工具公开仅适用于标准函数调用循环。 它不适用于 CodeAct 提供程序(agent-framework-montyagent-framework-hyperlight),因为在这种情况下,模型看到的是单一的代码执行界面,而不是各个工具的模式。 在 CodeAct 沙盒内调用 add_toolsremove_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_choiceNone 该模型,以便在后续迭代时释放模型。

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 字典,或简写字符串 "auto""required""none"

from agent_framework import ToolMode

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

语义和注意事项

Behavior 详情
下一次迭代效果 add_tools / remove_tools 这些变更会在下一次循环迭代中对模型可见。 当前批次中已分派的工具调用仍会完成。
进行中的批次 如果模型在一批中请求多个工具,则会在发送回更新的工具列表之前执行所有工具。
重复的名称 重新添加完全相同的对象不会产生任何效果。 添加名称与现有工具匹配的另一个对象会引发ValueError。 在添加任何内容之前,都会先验证整个批次,因此如果列表中途出现重复项,当前生效的列表将保持不变。
外部循环错误 add_tools时,调用remove_toolsctx.tools is None会引发RuntimeError。 当直接(例如通过 FunctionTool.invoke)而不是通过代理循环调用函数时,将发生这种情况。
实验状态 这两个帮助程序都会在每个进程首次调用时发出 ExperimentalWarning 。 如果需要,可使用 warnings.filterwarnings("ignore", category=FutureWarning) 进行抑制。
每次运行范围 实时工具列表是在每次normalize_tools调用开始时根据agent.run()创建的新副本。 调用方的原始 tools 容器永远不会发生改变。
CodeAct 排除 不适用于 agent-framework-montyagent-framework-hyperlight CodeAct 提供程序。

后续步骤