Tool Search: Dynamic Tools
On this page
Lesson updated November 2, 2026 based on OpenAI documentation: the Code Interpreter tool now requires a
containerparameter (added to all examples), input text parts are typed asinput_text, and web search uses theweb_searchtool (web_search_previewis now a legacy tool without domain filters). Generated files are retrieved using thefichiers_generesandtelechargerfunctions from Lesson 10. Sources: Code Interpreter · Web search.
Tool Search: Dynamic Tools
When you have dozens or hundreds of functions, declaring them all in every call becomes counterproductive: the model loses precision and costs explode. Tool Search solves this problem by letting the model look up the right tool in an indexed catalog.
The Problem with Large Tool Catalogs
The cost of a large tool catalog is invisible in two ways. First, financially: every function definition is sent with every request, including the ninety-eight that won’t be used. At scale, this eventually exceeds the cost of the responses themselves. Second, and more seriously, qualitatively: beyond a few dozen tools with overlapping scopes, the model makes worse choices. Too many options degrade decision-making, here as elsewhere.
# 5 functions = ~500 context tokens
# 50 functions = ~5000 context tokens
# 200 functions = impossible to manage efficiently
# The model loses precision when there are too many functions
# It sometimes picks the wrong function or hallucinates parameters
Enabling Tool Search
Tool Search flips the logic: instead of presenting everything to the model, you index the catalog and show it only what matches the current request. The mechanism relies on semantic search over descriptions—which is why the next section isn’t just style advice; it’s a requirement for the system to work.
from openai import OpenAI
client = OpenAI()
# Declare a large number of functions
all_functions = [
{
"type": "function",
"name": "create_invoice",
"description": "Create a new invoice for a customer",
"parameters": {"type": "object", "properties": {"client_id": {"type": "string"}}, "required": ["client_id"]}
},
{
"type": "function",
"name": "cancel_invoice",
"description": "Cancel an existing invoice",
"parameters": {"type": "object", "properties": {"invoice_id": {"type": "string"}}, "required": ["invoice_id"]}
},
# ... dozens of other functions
]
# Use Tool Search to let the model find the right function
response = client.responses.create(
model="gpt-5.6-terra",
input="Cancel invoice FAC-2026-0042",
tools=[{
"type": "tool_search",
"tool_search": {
"tools": all_functions
}
}]
)
The model receives only the functions relevant to the request in its context, not the entire catalog.
Organizing the Function Catalog
Two principles are enough to structure a catalog that remains navigable. Prefix names by domain (crm_, billing_, inventory_): execution trace reading becomes immediate, and name collisions disappear. And give each function a clear scope—two functions whose descriptions overlap will produce unstable choices, whether a human or a vector engine is making the call.
crm_catalog = [
{
"type": "function",
"name": "crm_search_customer",
"description": "Search for a customer in the CRM by name, email, or ID. "
"Use to find information about an existing customer.",
"parameters": {
"type": "object",
"properties": {
"term": {"type": "string", "description": "Search term"},
"field": {"type": "string", "enum": ["name", "email", "id"]}
},
"required": ["term"],
"additionalProperties": False
},
"strict": True
},
{
"type": "function",
"name": "crm_create_customer",
"description": "Create a new customer in the CRM. "
"Use when a customer does not yet exist.",
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"company": {"type": "string"}
},
"required": ["name", "email", "company"],
"additionalProperties": False
},
"strict": True
}
]
billing_catalog = [
{
"type": "function",
"name": "bill_create_invoice",
"description": "Create an invoice. Requires an existing customer.",
"parameters": {
"type": "object",
"properties": {
"client_id": {"type": "string"},
"lines": {"type": "array", "items": {"type": "object", "properties": {
"description": {"type": "string"},
"amount": {"type": "number"}
}}}
},
"required": ["client_id", "lines"],
"additionalProperties": False
},
"strict": True
}
]
# Merge all catalogs
full_catalog = crm_catalog + billing_catalog
Best Practices for Descriptions
Write descriptions for search, not for documentation. “Retrieve customer data” will never be found by a query about “order history” or “billing contact details”: the words your users actually type must appear in the indexed text. A useful description names use cases and business vocabulary, not implementation details.
# Bad: too generic
{"name": "get_data", "description": "Retrieve data"}
# Good: specific with usage context
{
"name": "hr_check_leave_balance",
"description": "Check an employee's leave balance (vacation, personal time, sick leave). "
"Returns remaining days by leave type. "
"Use when an employee asks how many days they have left."
}
# Good: with synonyms to improve search
{
"name": "acct_reconcile_bank",
"description": "Bank reconciliation: compare accounting entries "
"with bank transactions. Account reconciliation. "
"Use to verify consistency between accounting records "
"and bank statements."
}
Combining Tool Search with Fixed Tools
Mixing is the standard mode of operation in production. A few cross-cutting tools—time, current user, document search—remain declared permanently because they serve almost every request, while the business catalog stays dynamic. This keeps the context light without making the model search for what it needs every time.
response = client.responses.create(
model="gpt-5.6-terra",
input="Search the web for this month's sales and create the corresponding invoice",
tools=[
# Always-available tools
{"type": "web_search"},
{"type": "code_interpreter", "container": {"type": "auto"}},
# Dynamic catalog
{
"type": "tool_search",
"tool_search": {
"tools": full_catalog # 200+ functions
}
}
]
)
Pattern: Multi-Domain System
class MultiDomainSystem:
def __init__(self):
self.client = OpenAI()
self.catalogs = {}
def register_domain(self, name: str, functions: list[dict]):
"""Register a function catalog for a domain."""
self.catalogs[name] = functions
def full_catalog(self) -> list[dict]:
"""Merge all catalogs."""
result = []
for functions in self.catalogs.values():
result.extend(functions)
return result
def execute(self, query: str) -> str:
"""Execute a query with Tool Search across all domains."""
response = self.client.responses.create(
model="gpt-5.6-terra",
input=query,
tools=[{
"type": "tool_search",
"tool_search": {
"tools": self.full_catalog()
}
}]
)
# Process function calls
for item in response.output:
if item.type == "function_call":
result = self.dispatcher(item.name, item.arguments)
# ... function calling loop
return response.output_text
def dispatcher(self, name: str, arguments: str):
"""Route the call to the correct service."""
# Your routing logic
pass
# Usage
system = MultiDomainSystem()
system.register_domain("crm", crm_catalog)
system.register_domain("billing", billing_catalog)
system.register_domain("hr", hr_catalog)
system.register_domain("support", support_catalog)
# The model automatically finds the right function
# among hundreds of possibilities
result = system.execute("How many vacation days does Marie Dupont have left?")
Key Takeaways
- Tool Search solves the problem of large function catalogs
- The model receives only relevant functions in its context
- Rich, specific descriptions are essential for precision
- Prefix function names by domain for readability
- Combine Tool Search with always-available built-in tools