Управление доступностью инструментов

Замечание

API прогрессивного предоставления доступа к инструментам (FunctionInvocationContext.add_tools / remove_tools) в настоящее время доступен только для Python.

На этой странице рассматриваются три взаимодополняющих метода управления тем, какие инструменты может вызывать модель и в каком порядке, — всё это в рамках одного запуска агента, без необходимости создавать рабочий процесс:

  • Постепенное предоставление инструментов — добавляйте или удаляйте инструменты во время выполнения изнутри инструмента или промежуточного слоя функций, чтобы модель видела только те инструменты, которые она готова использовать.
  • Контроль через middleware — используйте middleware для функции, чтобы проверять аргументы вызова и возвращать корректирующую обратную связь, не выполняя саму функцию.
  • Принудительный первый вызов — используйте tool_choice, чтобы потребовать, чтобы модель вызвала определённый инструмент раньше любых других.

Замечание

Ограничения последовательного упорядочения, такие как "всегда вызывать get_record раньше update_record", не требуют рабочего процесса. Методы на этой странице обрабатывают этот шаблон внутри одного запуска. Рабочие процессы используются для настоящей многошаговой оркестрации между несколькими запусками или параллельными ветвями.

Прогрессивное воздействие инструментов

Постепенное предоставление инструментов позволяет начать запуск с небольшим набором инструментов и добавлять или удалять инструменты на основе результатов предыдущих вызовов инструментов — и всё это в рамках одного и того же запуска. Модель видит обновлённый набор только на следующей итерации цикла вызовов функций; вызовы инструментов, уже запрошенные в пакете, который ещё выполняется, всё равно будут выполнены до того, как изменение вступит в силу.

API является экспериментальным и доступен по адресу FunctionInvocationContext:

Член Описание
ctx.tools Актуальный, изменяемый list набор инструментов для текущего запуска. None при вызове функции вне цикла вызова функции.
ctx.add_tools(tools) Добавьте один или несколько инструментов. Вызываемые объекты оборачиваются в FunctionTool. Повторное добавление того же объекта ничего не делает; другой объект с дублирующимся именем вызывает ValueError. Все или ничего: если какой-либо инструмент в пакете вызывается, ни один из них не добавляется.
ctx.remove_tools(tools) Удалите по имени, объекту инструмента или вызываемому объекту. Имена, не присутствующих в списке, игнорируются автоматически.

Обе вспомогательные функции возвращают ExperimentalWarning при первом вызове в процессе (идентификатор функции: PROGRESSIVE_TOOLS). Вызов любого из вспомогательных методов вне цикла вызова функций приводит к возникновению RuntimeError.

Important

Список инструментов сбрасывается до исходного набора при каждом новом вызове agent.run(), поэтому все гейты автоматически снова активируются на каждом шаге.

Замечание

Поэтапное предоставление инструментов применяется только к стандартному циклу вызова функций. Она недоступна для поставщиков CodeAct (agent-framework-monty, agent-framework-hyperlightгде модель видит одну область выполнения кода, а не отдельные схемы инструментов. Вызов add_tools или remove_tools из песочницы CodeAct приводит к ошибке 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=UserWarning)  # 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'.",
    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 Detail
Эффект следующей итерации add_tools / remove_tools мутации становятся видны модели в следующей итерации цикла. Вызовы инструментов, уже отправленные в текущем пакете, выполняются независимо от того,
Пакет в обработке Если модель запрашивает несколько инструментов в одном пакете, все выполняются до отправки обновленного списка инструментов.
Повторяющиеся имена Повторное добавление точно такого же объекта — операция, не приводящая ни к каким изменениям. Добавление другого объекта, имя которого совпадает с именем существующего инструмента, вызывает ValueError. Весь пакет проверяется перед любым добавлением, поэтому дубликат посередине списка оставляет динамический список неизменным.
Ошибка внешнего цикла Вызов add_tools или remove_tools, когда ctx.tools is None вызывает RuntimeError. Это происходит, когда функция вызывается напрямую (например, через FunctionTool.invoke) а не через цикл агента.
Экспериментальное состояние Обе вспомогательные функции выводят ExperimentalWarning при первом вызове в каждом процессе. Подавить с помощью warnings.filterwarnings("ignore", category=UserWarning) при желании.
Область выполнения Актуальный список инструментов — это новая копия, созданная на основе normalize_tools в начале каждого вызова agent.run(). Исходный контейнер tools, переданный вызывающей стороной, никогда не изменяется.
Исключение CodeAct Недоступно для agent-framework-monty или agent-framework-hyperlight поставщиков CodeAct.

Дальнейшие действия