Tool search
When an application has hundreds of tools, sending every definition on every request wastes tokens and makes the model choose worse. With tool search, you mark most tools as defer_loading: true. The model starts with only a search tool and the few tools you always want loaded; when it needs something else, it searches and Syntic loads the matching definitions into the conversation.
When to use it
- More than about 30 tools, or more than 10,000 tokens of tool definitions.
- Large MCP servers that expose many tools.
- Catalogs that grow over time, such as one tool per integration.
With a handful of tools, list them normally.
Endpoint and auth
POST https://app.syntic.ai/v1/chat/completions (and Messages), Authorization: Bearer sk_live_....
Request fields
| Field | Where | Details |
|---|---|---|
defer_loading | each function tool, or each mcp_servers[] entry | true keeps the definition out of the prompt until a search finds it. |
{"type": "syntic_tool_search"} | tools | Adds the search tool. Required when any tool is deferred. |
mode | on the search tool | keyword (default, matches names and descriptions) or semantic (matches by meaning). |
max_results | on the search tool | Tools loaded per search, 1 to 10. Default 5. |
At least one tool must stay loaded (not deferred), or the search tool itself counts as that tool.
How it works
- The model sees the search tool, your non-deferred tools and a one-line hint that more tools exist.
- It calls
syntic_tool_searchwith a query, for example “refund an order”. - Syntic finds matches among the deferred tools and adds their full definitions to the conversation.
- The model calls the tool it needs, which comes back to you as a normal
tool_callsentry.
Loaded definitions stay loaded for the rest of the request. To keep them in later requests, return the assistant message unchanged; it carries a tool_search record that Syntic replays.
Response
{
"message": {
"role": "assistant",
"content": "",
"server_tool_calls": [
{ "id": "srv_05", "type": "syntic_tool_search", "query": "refund an order", "loaded": ["create_refund", "get_order"], "status": "ok" }
],
"tool_calls": [
{ "id": "call_r1", "type": "function", "function": { "name": "create_refund", "arguments": "{\"order_number\":\"582131\",\"amount_pence\":1250}" } }
]
},
"finish_reason": "tool_calls"
}Examples
curl https://app.syntic.ai/v1/chat/completions \
-H "Authorization: Bearer $SYNTIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "Amara S1.2",
"stream": false,
"tools": [
{ "type": "syntic_tool_search", "mode": "semantic" },
{ "type": "function", "function": { "name": "get_order", "description": "Look up an order.", "parameters": { "type": "object", "properties": { "order_number": { "type": "string" } }, "required": ["order_number"] } } },
{ "type": "function", "defer_loading": true, "function": { "name": "create_refund", "description": "Refund part or all of an order.", "parameters": { "type": "object", "properties": { "order_number": { "type": "string" }, "amount_pence": { "type": "integer" } }, "required": ["order_number", "amount_pence"] } } }
],
"messages": [{ "role": "user", "content": "Refund £12.50 on order 582131." }]
}'Errors
| Status | code | Cause |
|---|---|---|
| 400 | tool_search_required | Some tools are deferred but syntic_tool_search is missing. |
| 400 | all_tools_deferred | Nothing would be loaded (search tool aside), and tool_choice names a deferred tool. |
| 400 | too_many_tools | More than 10,000 tools in total. |
Cost and limits
- Deferred definitions are not billed until loaded. A search adds the loaded definitions as input tokens.
semanticmode embeds your tool descriptions once per distinct catalog (cached by hash); there is no extra fee.- Up to 10,000 tools per request, 128 loaded at once.
Build notes for the platform team
- Index deferred tools per request by hash of the definitions; reuse the index across requests.
- Semantic mode uses the embeddings backend.
- Pairs with the MCP connector; build after it.