Remarque
L’accès à cette page requiert une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page requiert une autorisation. Vous pouvez essayer de modifier des répertoires.
Note
L’API d’exposition progressive des outils (FunctionInvocationContext.add_tools / remove_tools) est actuellement Python uniquement.
Cette page présente trois techniques complémentaires pour contrôler les outils qu’un modèle peut appeler et dans quel ordre, le tout au sein d’une seule exécution d’agent, sans nécessiter de flux de travail :
- Exposition progressive des outils : ajoutez ou supprimez des outils au moment de l’exécution à l’intérieur d’un intergiciel de fonction ou d’outil. Le modèle voit uniquement les outils qu’il est prêt à utiliser.
- Middleware de filtrage — utilisez un middleware de fonction pour valider les arguments d’appel et renvoyer un retour correctif sans exécuter la fonction sous-jacente.
-
Premier appel forcé : utilisez
tool_choicepour exiger que le modèle appelle un outil spécifique avant d’autres.
Note
Les contraintes d’ordre par paire telles que « toujours appeler get_record avant update_record» ne nécessitent pas de flux de travail. Les techniques décrites sur cette page traitent ce schéma au cours d’une seule exécution. Les flux de travail sont destinés à une orchestration multi-étapes authentique entre les exécutions ou les branches parallèles.
Exposition progressive des outils
L’exposition progressive des outils vous permet de démarrer une exécution avec un petit ensemble d’outils et d’ajouter ou de supprimer des outils en réponse aux résultats de l’outil précédents, tous au sein de la même exécution. Le modèle ne voit l’ensemble mis à jour qu’à la itération suivante de la boucle d’appel de fonction ; les appels d’outil déjà demandés dans le lot en cours s’exécutent toujours avant que la modification ne prenne effet.
L’API est expérimentale et vit sur FunctionInvocationContext:
| Membre | Description |
|---|---|
ctx.tools |
Les outils dynamiques et mutables list pour l’exécution actuelle.
None lorsque la fonction est appelée en dehors d’une boucle d’appel de fonction. |
ctx.add_tools(tools) |
Ajoutez un ou plusieurs outils. Les callables sont encapsulés dans FunctionTool. Réajouter le même objet n’a aucun effet ; un autre objet portant un nom dupliqué provoque ValueError. Tout ou rien : si un outil du lot déclencherait, aucun n’est ajouté. |
ctx.remove_tools(tools) |
Supprimez par nom, objet d’outil ou objet appelable. Les noms non présents dans la liste sont ignorés en mode silencieux. |
Ces deux fonctions utilitaires émettent ExperimentalWarning lors de leur premier appel dans un processus (identifiant de fonctionnalité PROGRESSIVE_TOOLS). L’appel de l’assistance en dehors d’une boucle d’appel de fonction déclenche RuntimeError.
Important
La liste des outils est réinitialisée à l’ensemble initial à chaque nouvel appel agent.run(), de sorte que tous les garde-fous se réarment automatiquement à chaque tour.
Note
L’exposition progressive des outils s’applique uniquement à la boucle d’appel de fonction standard. Il n’est pas disponible pour les fournisseurs CodeAct (agent-framework-monty, agent-framework-hyperlight), où le modèle voit une surface d’exécution de code unique plutôt que des schémas d’outils individuels. L’appel à add_tools ou à remove_tools depuis un bac à sable CodeAct génère RuntimeError. Pour modifier l’ensemble d’outils d’un agent CodeAct, utilisez les propres add_tools / remove_tool / clear_tools méthodes du fournisseur entre les exécutions.
Modèle chargeur-outil
Enregistrez dès le départ un petit ensemble d’outils « loader » et laissez le modèle charger des outils supplémentaires à la demande. Cela permet de réduire le schéma initial, ce qui améliore la précision de la sélection des outils et réduit les coûts.
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())
L’exemple exécutable complet est à python/samples/02-agents/tools/dynamic_tool_exposure.py.
Schéma de contrôle
Inscrivez uniquement l’outil de lecture initialement. L’outil de lecture ajoute l’outil d’écriture après une extraction réussie, de sorte que le modèle ne peut pas appeler l’outil d’écriture avant l’exécution de l’outil de lecture.
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
)
Étant donné que ctx.tools se réinitialise à [get_record] au début de chaque exécution, le mécanisme se réarme automatiquement à chaque tour de conversation.
Contrôle d’accès de l’intergiciel
L’intergiciel de fonction peut inspecter les arguments d’un appel d’outil en attente et le rejeter avant l’exécution de la fonction sous-jacente en définissant context.result sans appeler call_next(). La chaîne affectée à context.result est retournée au modèle en tant que résultat de la fonction, en lui donnant des commentaires correctifs.
Cela est utile pour les vérifications au niveau de l’argument qui nécessitent des informations non disponibles au moment de la définition du schéma, par exemple en vérifiant qu’une mise à jour cible le même élément récupéré précédemment dans l’exécution.
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()
Ajoutez le middleware à l’agent :
agent = Agent(
client=OpenAIChatClient(),
name="RecordAgent",
instructions="Fetch a record before updating it.",
tools=[get_record, update_record],
middleware=[enforce_read_before_write],
)
Pour en savoir plus sur le middleware de fonction, consultez Définition du middleware et Remplacements de résultat.
Forcer un appel d’outil avec tool_choice
Pour exiger que le modèle appelle un outil spécifique comme première action, passez tool_choice avec le mode "required" et un required_function_name. Le framework réinitialise automatiquement tool_choice à None après la première itération, de sorte que le modèle soit libre lors des itérations suivantes.
result = await agent.run(
"Update record REC-42 to status 'in-progress'.",
tool_choice={"mode": "required", "required_function_name": "get_record"},
)
Le tool_choice champ accepte une ToolMode dictée, ou les chaînes abrégées "auto", "required"ou "none":
from agent_framework import ToolMode
tool_choice: ToolMode = {"mode": "required", "required_function_name": "get_record"}
Sémantiques et mises en garde
| Comportement | Détails |
|---|---|
| Effet de l’itération suivante |
add_tools
/
remove_tools les mutations sont visibles pour le modèle lors de l’itération suivante de la boucle. Les appels d’outil déjà envoyés dans le lot actuel se terminent quand même. |
| Lot en cours | Si le modèle demande plusieurs outils dans un lot, tous s’exécutent avant que la liste d’outils mise à jour soit renvoyée. |
| Noms dupliqués | Rajouter exactement le même objet n’a aucun effet. L’ajout d’un autre objet dont le nom correspond à un outil existant déclenche ValueError. L’ensemble du lot est validé avant tout ajout, de sorte qu’un doublon au milieu d’une liste laisse la liste active inchangée. |
| Erreur en dehors de la boucle | L’appel à add_tools ou à remove_tools lorsque ctx.tools is None lève RuntimeError. Cela se produit lorsque la fonction est appelée directement (par exemple via FunctionTool.invoke) plutôt que via la boucle de l’agent. |
| État expérimental | Les deux fonctions utilitaires émettent ExperimentalWarning lors de leur premier appel dans chaque processus. Supprimez avec warnings.filterwarnings("ignore", category=UserWarning) si vous le souhaitez. |
| Étendue par exécution | La liste active des outils est une nouvelle copie créée à partir de normalize_tools au début de chaque appel à agent.run(). Le conteneur d’origine tools de l’appelant n’est jamais modifié. |
| Exclusion de CodeAct | Non disponible pour les fournisseurs CodeAct agent-framework-monty ou agent-framework-hyperlight. |