Skip to content

Commit 107ef04

Browse files
thoothein
andauthored
Add conditional tool enabling feature to agent as tool. (#1193)
### Summary Adds `is_enabled` parameter to `Agent.as_tool()` method for conditionally enabling/disabling agent tools at runtime. Supports boolean values and callable functions for dynamic tool filtering in multi-agent orchestration. ### Test plan - Added unit tests in `tests/test_agent_as_tool.py` - Added example in `examples/agent_patterns/agents_as_tools_conditional.py` - Updated documentation in `docs/tools.md` - All tests pass ### Issue number Closes #1097 ### Checks - [x] I've added new tests (if relevant) - [x] I've added/updated the relevant documentation - [x] I've run `make lint` and `make format` - [x] I've made sure tests pass --------- Co-authored-by: thein <thein@MacBook-Pro.local>
1 parent 16f0f58 commit 107ef04

File tree

4 files changed

+394
-0
lines changed

4 files changed

+394
-0
lines changed

docs/tools.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,74 @@ json_tool = data_agent.as_tool(
311311
)
312312
```
313313

314+
### Conditional tool enabling
315+
316+
You can conditionally enable or disable agent tools at runtime using the `is_enabled` parameter. This allows you to dynamically filter which tools are available to the LLM based on context, user preferences, or runtime conditions.
317+
318+
```python
319+
import asyncio
320+
from agents import Agent, AgentBase, Runner, RunContextWrapper
321+
from pydantic import BaseModel
322+
323+
class LanguageContext(BaseModel):
324+
language_preference: str = "french_spanish"
325+
326+
def french_enabled(ctx: RunContextWrapper[LanguageContext], agent: AgentBase) -> bool:
327+
"""Enable French for French+Spanish preference."""
328+
return ctx.context.language_preference == "french_spanish"
329+
330+
# Create specialized agents
331+
spanish_agent = Agent(
332+
name="spanish_agent",
333+
instructions="You respond in Spanish. Always reply to the user's question in Spanish.",
334+
)
335+
336+
french_agent = Agent(
337+
name="french_agent",
338+
instructions="You respond in French. Always reply to the user's question in French.",
339+
)
340+
341+
# Create orchestrator with conditional tools
342+
orchestrator = Agent(
343+
name="orchestrator",
344+
instructions=(
345+
"You are a multilingual assistant. You use the tools given to you to respond to users. "
346+
"You must call ALL available tools to provide responses in different languages. "
347+
"You never respond in languages yourself, you always use the provided tools."
348+
),
349+
tools=[
350+
spanish_agent.as_tool(
351+
tool_name="respond_spanish",
352+
tool_description="Respond to the user's question in Spanish",
353+
is_enabled=True, # Always enabled
354+
),
355+
french_agent.as_tool(
356+
tool_name="respond_french",
357+
tool_description="Respond to the user's question in French",
358+
is_enabled=french_enabled,
359+
),
360+
],
361+
)
362+
363+
async def main():
364+
context = RunContextWrapper(LanguageContext(language_preference="french_spanish"))
365+
result = await Runner.run(orchestrator, "How are you?", context=context.context)
366+
print(result.final_output)
367+
368+
asyncio.run(main())
369+
```
370+
371+
The `is_enabled` parameter accepts:
372+
- **Boolean values**: `True` (always enabled) or `False` (always disabled)
373+
- **Callable functions**: Functions that take `(context, agent)` and return a boolean
374+
- **Async functions**: Async functions for complex conditional logic
375+
376+
Disabled tools are completely hidden from the LLM at runtime, making this useful for:
377+
- Feature gating based on user permissions
378+
- Environment-specific tool availability (dev vs prod)
379+
- A/B testing different tool configurations
380+
- Dynamic tool filtering based on runtime state
381+
314382
## Handling errors in function tools
315383

316384
When you create a function tool via `@function_tool`, you can pass a `failure_error_function`. This is a function that provides an error response to the LLM in case the tool call crashes.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import asyncio
2+
3+
from pydantic import BaseModel
4+
5+
from agents import Agent, AgentBase, RunContextWrapper, Runner, trace
6+
7+
"""
8+
This example demonstrates the agents-as-tools pattern with conditional tool enabling.
9+
Agent tools are dynamically enabled/disabled based on user access levels using the
10+
is_enabled parameter.
11+
"""
12+
13+
14+
class AppContext(BaseModel):
15+
language_preference: str = "spanish_only" # "spanish_only", "french_spanish", "european"
16+
17+
18+
def french_spanish_enabled(ctx: RunContextWrapper[AppContext], agent: AgentBase) -> bool:
19+
"""Enable for French+Spanish and European preferences."""
20+
return ctx.context.language_preference in ["french_spanish", "european"]
21+
22+
23+
def european_enabled(ctx: RunContextWrapper[AppContext], agent: AgentBase) -> bool:
24+
"""Only enable for European preference."""
25+
return ctx.context.language_preference == "european"
26+
27+
28+
# Create specialized agents
29+
spanish_agent = Agent(
30+
name="spanish_agent",
31+
instructions="You respond in Spanish. Always reply to the user's question in Spanish.",
32+
)
33+
34+
french_agent = Agent(
35+
name="french_agent",
36+
instructions="You respond in French. Always reply to the user's question in French.",
37+
)
38+
39+
italian_agent = Agent(
40+
name="italian_agent",
41+
instructions="You respond in Italian. Always reply to the user's question in Italian.",
42+
)
43+
44+
# Create orchestrator with conditional tools
45+
orchestrator = Agent(
46+
name="orchestrator",
47+
instructions=(
48+
"You are a multilingual assistant. You use the tools given to you to respond to users. "
49+
"You must call ALL available tools to provide responses in different languages. "
50+
"You never respond in languages yourself, you always use the provided tools."
51+
),
52+
tools=[
53+
spanish_agent.as_tool(
54+
tool_name="respond_spanish",
55+
tool_description="Respond to the user's question in Spanish",
56+
is_enabled=True, # Always enabled
57+
),
58+
french_agent.as_tool(
59+
tool_name="respond_french",
60+
tool_description="Respond to the user's question in French",
61+
is_enabled=french_spanish_enabled,
62+
),
63+
italian_agent.as_tool(
64+
tool_name="respond_italian",
65+
tool_description="Respond to the user's question in Italian",
66+
is_enabled=european_enabled,
67+
),
68+
],
69+
)
70+
71+
72+
async def main():
73+
"""Interactive demo with LLM interaction."""
74+
print("Agents-as-Tools with Conditional Enabling\n")
75+
print(
76+
"This demonstrates how language response tools are dynamically enabled based on user preferences.\n"
77+
)
78+
79+
print("Choose language preference:")
80+
print("1. Spanish only (1 tool)")
81+
print("2. French and Spanish (2 tools)")
82+
print("3. European languages (3 tools)")
83+
84+
choice = input("\nSelect option (1-3): ").strip()
85+
preference_map = {"1": "spanish_only", "2": "french_spanish", "3": "european"}
86+
language_preference = preference_map.get(choice, "spanish_only")
87+
88+
# Create context and show available tools
89+
context = RunContextWrapper(AppContext(language_preference=language_preference))
90+
available_tools = await orchestrator.get_all_tools(context)
91+
tool_names = [tool.name for tool in available_tools]
92+
93+
print(f"\nLanguage preference: {language_preference}")
94+
print(f"Available tools: {', '.join(tool_names)}")
95+
print(f"The LLM will only see and can use these {len(available_tools)} tools\n")
96+
97+
# Get user request
98+
user_request = input("Ask a question and see responses in available languages:\n")
99+
100+
# Run with LLM interaction
101+
print("\nProcessing request...")
102+
with trace("Conditional tool access"):
103+
result = await Runner.run(
104+
starting_agent=orchestrator,
105+
input=user_request,
106+
context=context.context,
107+
)
108+
109+
print(f"\nResponse:\n{result.final_output}")
110+
111+
112+
if __name__ == "__main__":
113+
asyncio.run(main())

src/agents/agent.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,8 @@ def as_tool(
356356
tool_name: str | None,
357357
tool_description: str | None,
358358
custom_output_extractor: Callable[[RunResult], Awaitable[str]] | None = None,
359+
is_enabled: bool
360+
| Callable[[RunContextWrapper[Any], AgentBase[Any]], MaybeAwaitable[bool]] = True,
359361
) -> Tool:
360362
"""Transform this agent into a tool, callable by other agents.
361363
@@ -371,11 +373,15 @@ def as_tool(
371373
when to use it.
372374
custom_output_extractor: A function that extracts the output from the agent. If not
373375
provided, the last message from the agent will be used.
376+
is_enabled: Whether the tool is enabled. Can be a bool or a callable that takes the run
377+
context and agent and returns whether the tool is enabled. Disabled tools are hidden
378+
from the LLM at runtime.
374379
"""
375380

376381
@function_tool(
377382
name_override=tool_name or _transforms.transform_string_function_style(self.name),
378383
description_override=tool_description or "",
384+
is_enabled=is_enabled,
379385
)
380386
async def run_agent(context: RunContextWrapper, input: str) -> str:
381387
from .run import Runner

0 commit comments

Comments
 (0)