Controlando a disponibilidade da ferramenta

Note

A API de exposição progressiva de ferramentas (FunctionInvocationContext.add_tools / remove_tools) está disponível atualmente apenas em Python.

Esta página aborda três técnicas complementares para controlar quais ferramentas um modelo pode chamar e em que ordem, tudo dentro de uma única execução de agente, sem a necessidade de um fluxo de trabalho:

  • Exposição progressiva de ferramentas — adicione ou remova ferramentas em tempo de execução de dentro de uma ferramenta ou de um middleware de função, para que o modelo veja apenas as ferramentas que está pronto para usar.
  • Middleware gating – use middleware de função para validar argumentos de chamada e retornar comentários corretivos sem executar a função subjacente.
  • Primeira chamada forçada – use tool_choice para exigir que o modelo chame uma ferramenta específica antes de qualquer outra.

Note

Restrições de ordenação emparelhada, como "sempre chamar get_record antes update_record", não exigem um fluxo de trabalho. As técnicas nesta página manipulam esse padrão dentro de uma única execução. Os fluxos de trabalho servem para uma orquestração real de múltiplas etapas ao longo de execuções ou de ramificações paralelas.

Exposição progressiva de ferramentas

A exposição progressiva de ferramentas permite que você inicie uma execução com um pequeno conjunto de ferramentas e adicione ou remova ferramentas em resposta aos resultados anteriores da ferramenta, tudo dentro da mesma execução. O modelo vê apenas o conjunto atualizado na próxima iteração do loop de chamada de função; as chamadas de ferramenta já solicitadas no lote de pré-lançamento ainda são executadas antes que a alteração entre em vigor.

A API é experimental e está disponível em FunctionInvocationContext:

Membro Description
ctx.tools As ferramentas ativas e mutáveis list para a execução atual. None quando a função é invocada fora de um loop de chamada de função.
ctx.add_tools(tools) Adicione uma ou mais ferramentas. Os callables são encapsulados como FunctionTool. Adicionar novamente o mesmo objeto não tem efeito; um objeto diferente com nome duplicado gera ValueError. Tudo ou nada: se qualquer ferramenta no lote gerar um erro, nenhuma será adicionada.
ctx.remove_tools(tools) Remova por nome, objeto de ferramenta ou callable. Os nomes não presentes na lista são ignorados silenciosamente.

Ambas as funções auxiliares emitem ExperimentalWarning na primeira vez que são chamadas em um processo (ID do recurso PROGRESSIVE_TOOLS). Chamar um auxiliar fora de um loop de chamada de função gera RuntimeError.

Importante

A lista de ferramentas é redefinida para o conjunto original em cada nova agent.run() chamada, para que todos os portões sejam re armados automaticamente para cada turno.

Note

A exposição progressiva da ferramenta aplica-se apenas ao loop de chamada de função padrão. Ele não está disponível para provedores CodeAct (agent-framework-monty, agent-framework-hyperlight), em que o modelo vê uma única superfície de execução de código em vez de esquemas de ferramentas individuais. Chamar add_tools ou remove_tools de dentro de uma sandbox do CodeAct gera RuntimeError. Para alterar o conjunto de ferramentas de um agente CodeAct, use os métodos do próprio provedor add_tools / remove_tool / clear_tools entre execuções.

Padrão de ferramenta de carregamento

Registre logo de início um pequeno conjunto de ferramentas de carregamento e permita que o modelo carregue ferramentas adicionais sob demanda. Isso mantém o esquema inicial pequeno, o que melhora a precisão da seleção de ferramentas e reduz o custo.

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())

O exemplo completo executável está em python/samples/02-agents/tools/dynamic_tool_exposure.py.

Padrão de controle

Registre inicialmente apenas a ferramenta de leitura. A ferramenta de leitura adiciona a ferramenta de gravação após uma busca bem-sucedida, portanto, o modelo não pode chamar a ferramenta de gravação antes que a ferramenta de leitura seja executada.

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
)

Como ctx.tools é redefinido para [get_record] no início de cada execução, o mecanismo é rearmado automaticamente a cada turno da conversa.

Controle de middleware

O middleware de função pode inspecionar os argumentos de uma chamada de ferramenta pendente e rejeitá-la antes que a função subjacente seja executada configurando context.result sem chamar call_next(). A cadeia de caracteres atribuída context.result é retornada ao modelo como resultado da função, fornecendo-lhe comentários corretivos.

Isso é útil para verificações no nível do argumento que precisam de informações não disponíveis no momento da definição do esquema, por exemplo, verificando se uma atualização tem como destino o mesmo item que foi buscado anteriormente na execução.

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()

Adicione o middleware ao agente:

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

Para saber mais sobre middleware para funções, consulte Definindo middleware e Substituições de resultado.

Forçando uma chamada de ferramenta com tool_choice

Para exigir que o modelo chame uma ferramenta específica como primeira ação, passe tool_choice com o modo "required" e um required_function_name. O framework redefine automaticamente tool_choice para None após a primeira iteração, de modo que o modelo fique livre nas iterações subsequentes.

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

O campo tool_choice aceita um ToolMode dict ou as strings abreviadas "auto", "required" ou "none":

from agent_framework import ToolMode

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

Semântica e advertências

Behavior Detalhes
Efeito de próxima iteração add_tools / remove_tools mutações são visíveis para o modelo na próxima iteração de loop. As chamadas de ferramenta já disparadas no lote atual são concluídas de qualquer forma.
Lote de pré-lançamento Se o modelo solicitar várias ferramentas em um lote, todas serão executadas antes que a lista de ferramentas atualizada seja enviada de volta.
Nomes duplicados Adicionar novamente exatamente o mesmo objeto é um no-op. Adicionar um objeto diferente cujo nome corresponde a uma ferramenta existente gera ValueError. O lote inteiro é validado antes de qualquer adição, portanto, uma duplicata no meio de uma lista deixa a lista dinâmica inalterada.
Erro de loop externo Chamar add_tools ou remove_tools quando ctx.tools is None lança RuntimeError. Isso acontece quando a função é invocada diretamente (por exemplo, via FunctionTool.invoke) em vez de por meio do loop do agente.
Status experimental Ambos os auxiliares emitem ExperimentalWarning na primeira chamada por processo. Suprima com warnings.filterwarnings("ignore", category=FutureWarning) se desejar.
Escopo por execução A lista ativa de ferramentas é uma nova cópia criada a partir de normalize_tools no início de cada chamada de agent.run(). O contêiner original tools do chamador nunca é modificado.
Exclusão de CodeAct Não disponível para os provedores CodeAct agent-framework-monty ou agent-framework-hyperlight.

Próximas Etapas