Skip to content

Add conditional tool enabling feature to agent as tool. #1193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,74 @@ json_tool = data_agent.as_tool(
)
```

### Conditional tool enabling

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.

```python
import asyncio
from agents import Agent, AgentBase, Runner, RunContextWrapper
from pydantic import BaseModel

class LanguageContext(BaseModel):
language_preference: str = "french_spanish"

def french_enabled(ctx: RunContextWrapper[LanguageContext], agent: AgentBase) -> bool:
"""Enable French for French+Spanish preference."""
return ctx.context.language_preference == "french_spanish"

# Create specialized agents
spanish_agent = Agent(
name="spanish_agent",
instructions="You respond in Spanish. Always reply to the user's question in Spanish.",
)

french_agent = Agent(
name="french_agent",
instructions="You respond in French. Always reply to the user's question in French.",
)

# Create orchestrator with conditional tools
orchestrator = Agent(
name="orchestrator",
instructions=(
"You are a multilingual assistant. You use the tools given to you to respond to users. "
"You must call ALL available tools to provide responses in different languages. "
"You never respond in languages yourself, you always use the provided tools."
),
tools=[
spanish_agent.as_tool(
tool_name="respond_spanish",
tool_description="Respond to the user's question in Spanish",
is_enabled=True, # Always enabled
),
french_agent.as_tool(
tool_name="respond_french",
tool_description="Respond to the user's question in French",
is_enabled=french_enabled,
),
],
)

async def main():
context = RunContextWrapper(LanguageContext(language_preference="french_spanish"))
result = await Runner.run(orchestrator, "How are you?", context=context.context)
print(result.final_output)

asyncio.run(main())
```

The `is_enabled` parameter accepts:
- **Boolean values**: `True` (always enabled) or `False` (always disabled)
- **Callable functions**: Functions that take `(context, agent)` and return a boolean
- **Async functions**: Async functions for complex conditional logic

Disabled tools are completely hidden from the LLM at runtime, making this useful for:
- Feature gating based on user permissions
- Environment-specific tool availability (dev vs prod)
- A/B testing different tool configurations
- Dynamic tool filtering based on runtime state

## Handling errors in function tools

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.
Expand Down
113 changes: 113 additions & 0 deletions examples/agent_patterns/agents_as_tools_conditional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import asyncio

from pydantic import BaseModel

from agents import Agent, AgentBase, RunContextWrapper, Runner, trace

"""
This example demonstrates the agents-as-tools pattern with conditional tool enabling.
Agent tools are dynamically enabled/disabled based on user access levels using the
is_enabled parameter.
"""


class AppContext(BaseModel):
language_preference: str = "spanish_only" # "spanish_only", "french_spanish", "european"


def french_spanish_enabled(ctx: RunContextWrapper[AppContext], agent: AgentBase) -> bool:
"""Enable for French+Spanish and European preferences."""
return ctx.context.language_preference in ["french_spanish", "european"]


def european_enabled(ctx: RunContextWrapper[AppContext], agent: AgentBase) -> bool:
"""Only enable for European preference."""
return ctx.context.language_preference == "european"


# Create specialized agents
spanish_agent = Agent(
name="spanish_agent",
instructions="You respond in Spanish. Always reply to the user's question in Spanish.",
)

french_agent = Agent(
name="french_agent",
instructions="You respond in French. Always reply to the user's question in French.",
)

italian_agent = Agent(
name="italian_agent",
instructions="You respond in Italian. Always reply to the user's question in Italian.",
)

# Create orchestrator with conditional tools
orchestrator = Agent(
name="orchestrator",
instructions=(
"You are a multilingual assistant. You use the tools given to you to respond to users. "
"You must call ALL available tools to provide responses in different languages. "
"You never respond in languages yourself, you always use the provided tools."
),
tools=[
spanish_agent.as_tool(
tool_name="respond_spanish",
tool_description="Respond to the user's question in Spanish",
is_enabled=True, # Always enabled
),
french_agent.as_tool(
tool_name="respond_french",
tool_description="Respond to the user's question in French",
is_enabled=french_spanish_enabled,
),
italian_agent.as_tool(
tool_name="respond_italian",
tool_description="Respond to the user's question in Italian",
is_enabled=european_enabled,
),
],
)


async def main():
"""Interactive demo with LLM interaction."""
print("Agents-as-Tools with Conditional Enabling\n")
print(
"This demonstrates how language response tools are dynamically enabled based on user preferences.\n"
)

print("Choose language preference:")
print("1. Spanish only (1 tool)")
print("2. French and Spanish (2 tools)")
print("3. European languages (3 tools)")

choice = input("\nSelect option (1-3): ").strip()
preference_map = {"1": "spanish_only", "2": "french_spanish", "3": "european"}
language_preference = preference_map.get(choice, "spanish_only")

# Create context and show available tools
context = RunContextWrapper(AppContext(language_preference=language_preference))
available_tools = await orchestrator.get_all_tools(context)
tool_names = [tool.name for tool in available_tools]

print(f"\nLanguage preference: {language_preference}")
print(f"Available tools: {', '.join(tool_names)}")
print(f"The LLM will only see and can use these {len(available_tools)} tools\n")

# Get user request
user_request = input("Ask a question and see responses in available languages:\n")

# Run with LLM interaction
print("\nProcessing request...")
with trace("Conditional tool access"):
result = await Runner.run(
starting_agent=orchestrator,
input=user_request,
context=context.context,
)

print(f"\nResponse:\n{result.final_output}")


if __name__ == "__main__":
asyncio.run(main())
6 changes: 6 additions & 0 deletions src/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ def as_tool(
tool_name: str | None,
tool_description: str | None,
custom_output_extractor: Callable[[RunResult], Awaitable[str]] | None = None,
is_enabled: bool
| Callable[[RunContextWrapper[Any], AgentBase[Any]], MaybeAwaitable[bool]] = True,
) -> Tool:
"""Transform this agent into a tool, callable by other agents.

Expand All @@ -249,11 +251,15 @@ def as_tool(
when to use it.
custom_output_extractor: A function that extracts the output from the agent. If not
provided, the last message from the agent will be used.
is_enabled: Whether the tool is enabled. Can be a bool or a callable that takes the run
context and agent and returns whether the tool is enabled. Disabled tools are hidden
from the LLM at runtime.
"""

@function_tool(
name_override=tool_name or _transforms.transform_string_function_style(self.name),
description_override=tool_description or "",
is_enabled=is_enabled,
)
async def run_agent(context: RunContextWrapper, input: str) -> str:
from .run import Runner
Expand Down
Loading