Guides

How to Optimize Tool Calling for AI Agents

Optimize AI agent tool calling with better tool design, simpler schemas, atomic tools, dynamic filtering, and evals, not just a bigger model.

Garrett Scott
,
Head of Marketing

How to Optimize Tool Calling for AI Agents

Many teams discover the gap between having tools and using them reliably only after a successful demo. The agent works in controlled examples, then real users ask messier questions, combine multiple requests, omit context, or use phrasing the system was not tested against. The challenge is reliability: there is a wide gap between an agent that has access to tools and an agent that selects the right tool, supplies the right inputs, completes the task, and does so at a cost suitable for production.

To optimize tool calling, improve the full path from user request to tool selection, input generation, and task completion. Model choice has the largest measured effect in Paragon's own evals, but it is not the first lever most teams can pull: tool descriptions, schemas, atomic tool design, dynamic filtering, and ongoing evals are what a team controls once a model is chosen, and they determine how much of the gap a smaller, cheaper model can close.

What It Means to Optimize Tool Calling

Tool calling — also called function calling in some model providers' documentation — should be measured across four separate dimensions.

Tool-Calling Metric

What It Measures

Why It Matters

Tool Correctness

Whether the agent selected the right tool for the request

Prevents the agent from using the wrong action, such as creating a record when it should search for one

Input Accuracy

Whether the agent passed the right arguments into the tool

Reduces failures caused by incorrect names, dates, IDs, filters, or query strings

Task Completion

Whether the full user request was completed successfully

Shows whether the agent actually solved the user's problem, not just whether it made a tool call

Task Efficiency

How many tokens, calls, retries, and dollars were required

Helps teams control cost, latency, and production scalability

Tool correctness and task completion often diverge in practice: an agent can select the right tool and still fail the request if a downstream step breaks, or it can complete a task despite an early misstep it recovers from. Paragon's own evals showed this directly — the model that picked the right tool most often was not the model that finished the most tasks. Input accuracy failures compound the same way: a correctly selected tool called with the wrong customer ID or date range still produces a wrong result. Task efficiency is the cost lens on all three — a system that eventually gets the right answer by retrying, over-fetching, or loading unnecessary tools can still be too slow or expensive for production.

These metrics should be tracked separately. If you only measure whether the task worked, you will not know whether failures come from poor tool selection, bad argument extraction, weak tool design, missing permissions, or excessive context.

What the Benchmark Data Shows

LLM choice had the largest measured effect on tool-calling performance in Paragon's evals across six SaaS providers. Prompt wording, description depth, and routing moved the numbers too, but mostly on multi-tool tasks — on single-tool tasks their effect was close to flat. Tool-calling reliability is one layer of the larger problem of connecting agents to third-party systems; see the agent integration infrastructure overview for how it fits with authentication, sync, and orchestration.

We ran these benchmarks with DeepEval's tool-calling and task-completion metrics across 50 test cases spanning six third-party providers: Salesforce, HubSpot, Slack, Gmail, Google Drive, and Notion. Thirty-six cases used a single tool; fourteen chained multiple tools. Each case scored 0 to 1 on Tool Correctness (right tool picked) and Task Completion (task finished). The run was April 2025, against gpt-4o, o3-mini, Claude 3.5-Sonnet, and an o3-2025-04-16 snapshot.

One limit is worth stating plainly, and it is why the findings below are reported as directions rather than as scores. The prompt, description, and routing results rest on the fourteen multi-tool cases in that set, and each configuration was a single run with no confidence intervals or repeated trials. The models were also April 2025 vintage. Absolute numbers from a run that old would tell you more about those specific model snapshots than about the choices in front of you, so what follows is the shape of the result — which lever moved which metric, and in which direction.

Which lever moved which metric

Model choice moved the numbers most. Swapping the underlying LLM produced larger and more consistent swings than any prompt, schema, or routing change tested. It was also the clearest case of the two metrics pulling apart: the model that picked the right tool most often was not the model that completed the most tasks.

Prompt wording and description depth only mattered on multi-tool work. Across the full fifty cases, rewriting the system prompt or adding detail to tool descriptions barely moved either metric. On the fourteen tasks that chained several tools, both helped task completion meaningfully. Single-tool calls were close to flat — a useful signal about when prompt and description work is worth the effort.

Routing to a narrower tool set was model-dependent, and cut both ways. Narrowing the exposed set from roughly twenty tools to around five had almost no effect on one model. On another it raised tool correctness while lowering task completion in the same run. That is why the filtering guidance below is framed per model rather than as a blanket rule: fewer tools is not automatically better, and the metric you optimize for decides the answer.

The practical read: model choice produced the biggest swings, but tool design is the lever a team actually controls once a model is chosen, and it decides whether a smaller, cheaper model can close the gap with a frontier one. Treat tool design as the most controllable lever here, not the biggest swing.

Optimizing Tool Calling for AI Agents

Start With Tool Design

When tool calling fails, many teams first consider upgrading to a larger model. This can help in some cases, but it is usually the wrong first lever. Tool design is the most controllable lever within a given model, because the model can only reason over the tools and schemas it receives.

A tool description is part of the prompt. If the description is too short, the model may not understand when to use the tool. If the description is too long, it adds token cost every time the tool is loaded and may distract the model with unnecessary detail.

The goal is a concise but informative description. It should explain what the tool does, when to use it, what it returns, and any important constraints. It should not include generic marketing language, irrelevant implementation detail, or lengthy examples unless they materially improve tool selection.

For example, "Search Notion" is too vague. "Searches connected Notion workspaces for pages matching a natural-language query and returns page titles, IDs, URLs, and matching snippets" gives the model enough context to choose the tool correctly.

Input design carries just as much weight. Engineers often expose tools in a way mirroring backend function arguments or raw API endpoints. This can work well for code, but it is harder for a language model. The model has to infer every argument from natural language, and each nested field adds another chance for failure.

Flatter schemas are usually better. A tool with one well-described query parameter can outperform a tool with deeply nested parameters, even if both eventually call the same backend API. The model's job should be to express the user's intent clearly. The server-side tool implementation can handle the API-specific details.

Wrap Multi-Step Workflows Into Atomic Tools

Many useful agent actions require more than one API call. Searching for a document may require one request to find matching files and another request to retrieve the file content. Creating a CRM follow-up task may require finding the contact, validating the account, and then creating the task.

If each API endpoint is exposed as a separate tool, the agent has to orchestrate the sequence. This creates more failure points. It may call the first tool correctly, misunderstand the result, skip the next step, or pass the wrong ID into the second call.

A better approach is to wrap common multi-step sequences into atomic tools. Instead of exposing "search page" and "get page contents" separately, expose a tool searching for a page and return its contents. Instead of exposing separate low-level CRM endpoints, expose a tool finding a contact and create a note or task in one operation.

Atomic tools reduce reasoning burden. They also make evaluation easier because the expected behavior is clearer. The agent does not need to know how the external API is structured. It only needs to understand the user's task and choose the tool designed for the task.

The tradeoff is flexibility. Atomic tools are more opinionated than raw endpoint tools. They should be designed around common product workflows. For production agents, the tradeoff is usually worth it: reliability outweighs exposing every backend primitive to the model.

Load Only the Tools the Task Needs

Every tool placed in the model's context has a cost. It consumes tokens, increases prompt size, and gives the model another possible option to consider. A large tool set can make an agent less accurate because the model must distinguish between many similar tools.

Tool filtering is one of the highest-impact optimizations for production agents. Instead of loading every available tool, load only the tools relevant to the current task, integration, user permissions, or conversation state.

If a user is working inside Salesforce, the agent may not need tools for Slack, Notion, Google Drive, and Zendesk. If the user asks to summarize documents, the agent may need retrieval tools but not write-action tools. If the user has not authenticated a specific integration, those tools should not be available at all.

This improves both accuracy and cost. The model sees fewer choices, so tool selection becomes easier. The prompt also becomes smaller, reducing input token usage. In agent systems with dozens or hundreds of possible actions, dynamic tool filtering is not optional. It is part of the reliability layer.

That said, filtering is a correctness lever more than a guaranteed win. In Paragon's evals, routing to a narrower tool set raised tool correctness on one model while lowering its task completion in the same run, and made almost no difference on another. Test it against your own model before applying it uniformly.

Match the Model to the Harness

Model choice is one input among several; it should be evaluated together with the harness, tool schemas, descriptions, and task set. A frontier model is not automatically the best tool-calling model for every agent.

Model size and tool-calling reliability do not move in lockstep. A smaller, cost-efficient model paired with well-designed tools can often complete a task about as reliably as a frontier model while consuming meaningfully fewer input tokens, particularly on the single-tool tasks common in production agents. In some cases, that is accurate enough for production at a much lower cost. In other cases, a more capable model may be justified because the task requires complex planning, ambiguity resolution, or multi-step reasoning.

The key is to test combinations. A model can perform well with one provider's tools but may perform differently with another provider's schema style. A tool set with simple atomic actions may work well with a smaller model, while a raw API-style tool set may require more reasoning capacity.

Teams should evaluate model and harness combinations using their own tasks. Public benchmarks are useful for orientation, but production agents fail on the details of a specific workflow, user base, and integration set.

Build an Evaluation Loop

Tool-calling quality should be tested the way software behavior is tested. Without an evaluation suite, teams end up relying on demos, intuition, and manual spot checks. This is not enough for production agents.

A useful evaluation suite does not need to be large at first. Start with 15 to 30 representative tasks. Include straightforward prompts, vague prompts, edge cases, and prompts resembling real user behavior. Real users often omit context, use shorthand, combine requests, or ask for something the agent cannot safely complete.

Each test should define the expected tool call or tool sequence. This allows you to score tool correctness automatically. For input accuracy and task completion, an LLM-as-judge can help evaluate whether the agent passed the right arguments and completed the request.

The evaluation suite should run whenever you change tool descriptions, add new tools, modify schemas, update prompts, change routing logic, or switch models. Tool-calling performance can drift even when the product change seems small. A new tool may introduce confusion with an existing tool. A longer description may increase cost without improving accuracy. A model upgrade may change argument extraction behavior.

The goal is to turn tool-calling quality into a number you can move. Track pass rate, tool correctness, input accuracy, completion rate, token usage, latency, and cost per successful task.

Why agents struggle with tools at scale

Tools are still tokens: LLMs decide when to call a tool based on the tool name, description, input names, and input descriptions. The code behind a tool call runs in the agent's own backend, not on the model provider's infrastructure, and the result is returned to the LLM as another message.


Diagram of the tool-calling round trip: the LLM requests a tool call, the backend executes it, and the result is returned to the LLM

This has two practical consequences:

  • Tool descriptions consume tokens in the context window on every request, whether or not the tool ends up being used.

  • Most tool-calling flows involve at least two round trips — one for the model's tool call intent, another to return the tool's result — though the exact number varies by API, streaming mode, cached tool results, and harness design.

Adding more tools compounds both costs: a larger context window from tool descriptions, and more opportunities for the model to select the wrong tool.


Claude Desktop warning that 219 tools are enabled across MCP servers, cautioning that too many tools can degrade performance and that some models may not respect more than 80 tools

The clients themselves say so: Claude Desktop warns when a workspace exceeds its recommended tool count, noting that too many tools can degrade performance and that some models may not respect more than 80 tools. Few products need that many, and the pattern holds well below that number. Filtering and tool selection have measurable performance and cost effects, which is why they matter for any agent handling a growing set of tools.

How to implement tools at scale

If tool descriptions consume the context window and too many options degrade accuracy, the fix is to load only the tools relevant to the current task. A few approaches work in practice:

Let users decide

Users often don't know which integrations or actions an agent can reach until they're told. Letting users choose which tools to enable limits the number of tools loaded and makes the available capabilities explicit.

The example below shows an agent using ActionKit to expose tools scoped to the integration providers a user has selected:


Interface showing a user selecting which integration providers, such as Salesforce or Slack, to enable as agent tools

Multi-agent pattern

When an agent should decide its own subset of tools for a given task, patterns like routing and orchestration — per Anthropic's research on building effective agents — can filter tools automatically.

In the planner-worker implementation, a "planner" agent creates a plan for what integrations are necessary for the task. For example, if a user asks for their email inbox, the planner agent will respond with ['gmail']. If a user asks about Salesforce and Gmail, the planner agent will respond with ['salesforce', 'gmail'].

export async function planWork(integrations: Array<string>, messages: Array<ModelMessage>) {
	const objectPrompt: ModelMessage = {
		role: "user",
		content: `Decide what integrations are needed to complete the task. 
			For generic requests, do not include any integrations.`
	}
	const objectMessages = [...messages, objectPrompt];
	const { object: integrationPlan } = await generateObject({
		model: openai('gpt-5-nano'),
		schema: z.object({
			integrations: integrations.length > 0 ? z.array(z.enum(integrations as [string, ...string[]])) : z.array(z.string()),
			integrationSpecificPrompt: z.array(z.string()),
		}),
		system: `You have access to these integrations: ${integrations.join()}

export async function planWork(integrations: Array<string>, messages: Array<ModelMessage>) {
	const objectPrompt: ModelMessage = {
		role: "user",
		content: `Decide what integrations are needed to complete the task. 
			For generic requests, do not include any integrations.`
	}
	const objectMessages = [...messages, objectPrompt];
	const { object: integrationPlan } = await generateObject({
		model: openai('gpt-5-nano'),
		schema: z.object({
			integrations: integrations.length > 0 ? z.array(z.enum(integrations as [string, ...string[]])) : z.array(z.string()),
			integrationSpecificPrompt: z.array(z.string()),
		}),
		system: `You have access to these integrations: ${integrations.join()}

export async function planWork(integrations: Array<string>, messages: Array<ModelMessage>) {
	const objectPrompt: ModelMessage = {
		role: "user",
		content: `Decide what integrations are needed to complete the task. 
			For generic requests, do not include any integrations.`
	}
	const objectMessages = [...messages, objectPrompt];
	const { object: integrationPlan } = await generateObject({
		model: openai('gpt-5-nano'),
		schema: z.object({
			integrations: integrations.length > 0 ? z.array(z.enum(integrations as [string, ...string[]])) : z.array(z.string()),
			integrationSpecificPrompt: z.array(z.string()),
		}),
		system: `You have access to these integrations: ${integrations.join()}

export async function planWork(integrations: Array<string>, messages: Array<ModelMessage>) {
	const objectPrompt: ModelMessage = {
		role: "user",
		content: `Decide what integrations are needed to complete the task. 
			For generic requests, do not include any integrations.`
	}
	const objectMessages = [...messages, objectPrompt];
	const { object: integrationPlan } = await generateObject({
		model: openai('gpt-5-nano'),
		schema: z.object({
			integrations: integrations.length > 0 ? z.array(z.enum(integrations as [string, ...string[]])) : z.array(z.string()),
			integrationSpecificPrompt: z.array(z.string()),
		}),
		system: `You have access to these integrations: ${integrations.join()}

Based on the plan's list of integrations, ActionKit builds tools dynamically, providing the right descriptions and input schema for different actions across an integration like Gmail.

tool({
	description: toolFunction.function.description,
	inputSchema: jsonSchema(toolFunction.function.parameters),
	execute: async (params: any) => {
			const response = await fetch(
				`https://actionkit.useparagon.com/projects/<project_id>/actions`,
				{
					method: "POST",
					body: JSON.stringify({
						action: toolFunction.function.name,
						parameters: params,
					}),
					headers: {
						Authorization: `Bearer ${paragonUserToken}`,
						"Content-Type": "application/json",
					},
				}
			);
			const output = await response.json();
			if (!response.ok) {
				throw new Error(JSON.stringify(output, null, 2));
			}
			return output;
})
tool({
	description: toolFunction.function.description,
	inputSchema: jsonSchema(toolFunction.function.parameters),
	execute: async (params: any) => {
			const response = await fetch(
				`https://actionkit.useparagon.com/projects/<project_id>/actions`,
				{
					method: "POST",
					body: JSON.stringify({
						action: toolFunction.function.name,
						parameters: params,
					}),
					headers: {
						Authorization: `Bearer ${paragonUserToken}`,
						"Content-Type": "application/json",
					},
				}
			);
			const output = await response.json();
			if (!response.ok) {
				throw new Error(JSON.stringify(output, null, 2));
			}
			return output;
})
tool({
	description: toolFunction.function.description,
	inputSchema: jsonSchema(toolFunction.function.parameters),
	execute: async (params: any) => {
			const response = await fetch(
				`https://actionkit.useparagon.com/projects/<project_id>/actions`,
				{
					method: "POST",
					body: JSON.stringify({
						action: toolFunction.function.name,
						parameters: params,
					}),
					headers: {
						Authorization: `Bearer ${paragonUserToken}`,
						"Content-Type": "application/json",
					},
				}
			);
			const output = await response.json();
			if (!response.ok) {
				throw new Error(JSON.stringify(output, null, 2));
			}
			return output;
})
tool({
	description: toolFunction.function.description,
	inputSchema: jsonSchema(toolFunction.function.parameters),
	execute: async (params: any) => {
			const response = await fetch(
				`https://actionkit.useparagon.com/projects/<project_id>/actions`,
				{
					method: "POST",
					body: JSON.stringify({
						action: toolFunction.function.name,
						parameters: params,
					}),
					headers: {
						Authorization: `Bearer ${paragonUserToken}`,
						"Content-Type": "application/json",
					},
				}
			);
			const output = await response.json();
			if (!response.ok) {
				throw new Error(JSON.stringify(output, null, 2));
			}
			return output;
})

A worker agent then uses only the integration-specific tools in its request to the OpenAI API.

const result = streamText({
	model: openai('gpt-5-nano'),
	system: `You MUST use the available tools to help with the user's request.
	Do not just describe what you would do - actually call the tools! Do NOT forget inputs.`,
	messages: revisedMessages,
	stopWhen: stepCountIs(5),
	tools: toolsForIntegration,
});
const result = streamText({
	model: openai('gpt-5-nano'),
	system: `You MUST use the available tools to help with the user's request.
	Do not just describe what you would do - actually call the tools! Do NOT forget inputs.`,
	messages: revisedMessages,
	stopWhen: stepCountIs(5),
	tools: toolsForIntegration,
});
const result = streamText({
	model: openai('gpt-5-nano'),
	system: `You MUST use the available tools to help with the user's request.
	Do not just describe what you would do - actually call the tools! Do NOT forget inputs.`,
	messages: revisedMessages,
	stopWhen: stepCountIs(5),
	tools: toolsForIntegration,
});
const result = streamText({
	model: openai('gpt-5-nano'),
	system: `You MUST use the available tools to help with the user's request.
	Do not just describe what you would do - actually call the tools! Do NOT forget inputs.`,
	messages: revisedMessages,
	stopWhen: stepCountIs(5),
	tools: toolsForIntegration,
});

End-to-end, the agent system loads only the tools relevant to the user's prompt.

MCP provided tools

MCP servers are similar to multi-agent patterns: rather than a planner agent living in the application backend, an MCP server can decide on tools and provide them dynamically. MCP servers do not inherently provide tool selection out of the box, though. MCP is a standard for how a server exposes prompts, resources, and tools to an agent; MCP servers can help select tools, but the standard does not require one to. A third-party MCP server without built-in tool selection still needs the consuming application to filter or route its exposed tools — either with a purpose-built MCP server or an agent pattern like planner-worker applied to the MCP-provided tools.

Paragon's ActionKit MCP server uses the same multi-agent pattern to plan and select tools, and additionally provides server-specific context — such as magic links that authenticate users directly in the chat.


Example of the ActionKit MCP server providing a magic link that authenticates a user directly in the chat

The ActionKit MCP server supports third-party integration tools directly in Cursor, Claude, or any other MCP client.

For the access-layer side of the problem, see how to give agents tool-calling access to SaaS apps at scale.

Scoping Tool Access By User And Tenant

Tool calling gives an agent the ability to act in external systems, so security cannot be treated as a later concern. The agent should only have access to tools the user is allowed to use and data the user is allowed to access.

For SaaS integrations, this usually means handling OAuth, scopes, token refresh, and user-level or tenant-level authorization. If an agent is acting inside a customer's Salesforce, Google Drive, Slack, or Zendesk account, the system must know which user authorized the connection and what users can do.

Read and write actions should be separated. A retrieval tool that searches documents should not require broad write permissions. A tool used to update CRM records should be loaded only when the user request requires it and the user has the correct authorization.

This is especially important for B2B SaaS products building agents for their own customers. Each customer may connect their own workspace, with different permissions and administrators. The agent architecture needs to prevent cross-tenant access and avoid exposing tools tied to disconnected or expired accounts.

Where Build Versus Buy Fits

Teams can build their own tool-calling layer. For one or two integrations, this can be reasonable. You can write the schemas, design the descriptions, wrap API sequences, manage authentication, handle errors, and evaluate performance yourself.

The cost changes as the number of integrations and actions grows. Each new SaaS app introduces a new authentication model, API structure, rate limit pattern, permission system, and set of edge cases. Each tool needs to be described in a way the model can use reliably. Each change needs to be tested. Over time, the integration layer can become a standing engineering commitment.

A tool provider helps by packaging third-party actions into agent-usable tools and handling parts of the authentication and integration layer. Paragon's ActionKit is designed for AI agents and product workflows that need to take actions across third-party SaaS apps. It exposes third-party actions as JSON Schema tools and supports adoption through API-based workflows or an MCP server, depending on the stack.

This does not remove the need to evaluate your own agent — tool-calling performance still depends on the model, prompts, task design, and product context regardless of which layer handles authentication and integration plumbing. Where a tool provider changes the calculus is the number of providers involved: a team calling into two or three SaaS APIs can reasonably build and maintain that layer itself, while a team calling into dozens faces a set of auth models, rate limits, and edge cases that keeps expanding with every provider it adds.

Frequently Asked Questions

What does it mean to optimize tool calling?
Optimizing tool calling means improving how reliably an AI agent selects the right tool, supplies the correct inputs, completes the user's task, and does so at an acceptable token and cost budget. These are related but separate dimensions.

Does a bigger model fix tool-calling problems?
Not usually as the first step, even though model choice produced the largest measured swing in Paragon's evals across six SaaS providers. Poor tool descriptions, complex schemas, too many tools in context, and weak evaluation practices still cause most failures, and fixing those first is what makes a model upgrade worth it.

Why does my agent pick the wrong tool?
Agents often pick the wrong tool because the available tools are too similar, the descriptions are vague, or too many irrelevant tools are loaded into context. Clear descriptions and dynamic tool filtering usually improve selection, though filtering is worth measuring rather than assuming: in Paragon's evals it raised tool-selection accuracy for one model while reducing that same model's end-to-end task completion.

Why does my agent pass the wrong inputs?
Input errors often come from schemas that are too nested, too abstract, or too closely modeled on backend API arguments. Flatter inputs and more task-oriented tools make argument extraction easier for the model.

How do I reduce token cost in tool calling?
Reduce the number of tools loaded into context and keep descriptions concise while still giving the model enough information to choose correctly. Avoid loading disconnected integrations, irrelevant actions, or long descriptions for tools unrelated to the task.

Related

TABLE OF CONTENTS
    Table of contents will appear here.
Ship native integrations 7x faster with Paragon

Ready to get started?

Join hundreds of SaaS companies that are scaling their integration roadmaps with Paragon

Ready to get started?

Join hundreds of SaaS companies that are scaling their integration roadmaps with Paragon

Ready to get started?

Join hundreds of SaaS companies that are scaling their integration roadmaps with Paragon

Ready to get started?

Join hundreds of SaaS companies that are scaling their integration roadmaps with Paragon