Skip to main content
Lesson 15 of 20 · API Tools: Search, Files, and Code Interpreter

Tool Search: Dynamic Tools

Lesson 15 of 20 Updated November 2, 2026 Developer
On this page
  1. Tool Search: Dynamic Tools
  2. The Problem with Large Tool Catalogs
  3. Enabling Tool Search
  4. Organizing the Function Catalog
  5. Best Practices for Descriptions
  6. Combining Tool Search with Fixed Tools
  7. Pattern: Multi-Domain System
  8. Key Takeaways

Lesson updated November 2, 2026 based on OpenAI documentation: the Code Interpreter tool now requires a container parameter (added to all examples), input text parts are typed as input_text, and web search uses the web_search tool (web_search_preview is now a legacy tool without domain filters). Generated files are retrieved using the fichiers_generes and telecharger functions 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

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