Structured Outputs

Designing Tool-Calling Prompts for Abilities

⏱ 15 min

Course 1’s ability lesson established that description is what an AI model actually reads to decide whether and how to call an ability. This lesson is that idea taken all the way to its practical conclusion: writing descriptions and input schemas well enough that a model picks the right ability reliably, even when your site has several abilities that could plausibly apply to the same request. This is prompt engineering aimed at a model’s tool-selection step rather than at its text generation, and it fails in its own specific ways.

What you'll learn in this lesson
What a model actually reads before calling a tool
The label, description, and every property description in input_schema.
Vague descriptions
Why "does something with posts" gets an ability skipped or misused.
Overlapping abilities
The specific failure mode of two abilities a model cannot reliably tell apart.
A before/after rewrite
Two real, disambiguated ability registrations for get vs search.
Prerequisites

Course 1’s Abilities API overview, particularly the lesson explaining wp_register_ability()’s fields. If you haven’t registered a real ability yet, Course 2 covers that hands-on, this lesson assumes you already understand the shape and is about writing better content for it, not a different mechanism.

Step 1: What a model reads before deciding to call an ability

When an AI client is deciding which ability to invoke for a user’s request, it isn’t reading your PHP, it’s reading the ability’s label, its description, and the description on every property inside input_schema. All of that text is the model’s only window into what an ability does and what it needs, which means every one of those fields is prompt content, written for a model to make a decision from, not documentation written for a human skimming a list.

Step 2: The vague-description failure mode

A description like this is common, and quietly unreliable:

// File: my-plugin.php (weak version)
add_action( 'wp_abilities_api_init', function () {
	wp_register_ability(
		'my-plugin/posts',
		array(
			'label'        => __( 'Posts', 'my-plugin' ),
			'description'  => __( 'Does things with posts.', 'my-plugin' ),
			'input_schema' => array(
				'type'       => 'object',
				'properties' => array(
					'params' => array( 'type' => 'string' ),
				),
			),
			// permission_callback / execute_callback omitted for brevity
		)
	);
} );

Two problems here compound each other. “Does things with posts” gives a model no signal for when to reach for this ability versus any other one that also touches posts, and a single generic params string property gives it no signal for what to actually pass in. A model faced with this will either avoid the ability entirely, or call it with guessed, malformed input.

Step 3: The overlapping-abilities failure mode, and a real fix

The sharper version of the same problem shows up once a site has two abilities that both plausibly apply to the same kind of request, one that fetches a single known post, and one that searches across many. Registered with similarly generic descriptions, a model frequently can’t tell them apart and either always picks one, ignoring the other entirely, or picks the wrong one for a given request. Here’s both, rewritten to be genuinely distinct:

// File: my-plugin.php (disambiguated version)
add_action( 'wp_abilities_api_init', function () {
	wp_register_ability(
		'my-plugin/get-post-by-id',
		array(
			'label'       => __( 'Get a single post by ID', 'my-plugin' ),
			'description' => __(
				'Fetches one specific post when you already know its numeric ID. ' .
				'Use this only when an exact post ID is known. If you only have a ' .
				'title, a topic, or a partial description, use ' .
				'my-plugin/search-posts instead.',
				'my-plugin'
			),
			'input_schema' => array(
				'type'       => 'object',
				'properties' => array(
					'post_id' => array(
						'type'        => 'integer',
						'description' => 'The numeric WordPress post ID to fetch.',
					),
				),
				'required' => array( 'post_id' ),
			),
			// ...permission_callback, execute_callback
		)
	);

	wp_register_ability(
		'my-plugin/search-posts',
		array(
			'label'       => __( 'Search posts by keyword', 'my-plugin' ),
			'description' => __(
				'Searches published posts by keyword or topic when the exact post ' .
				'is not already known by ID. Returns a list of matching posts with ' .
				'their IDs and titles. Use my-plugin/get-post-by-id afterward if a ' .
				'single specific post from the results needs to be fetched in full.',
				'my-plugin'
			),
			'input_schema' => array(
				'type'       => 'object',
				'properties' => array(
					'query' => array(
						'type'        => 'string',
						'description' => 'Keyword or phrase to search for in post titles and content.',
					),
				),
				'required' => array( 'query' ),
			),
			// ...permission_callback, execute_callback
		)
	);
} );

Each description now names the other ability explicitly and states exactly when to prefer one over the other, and each input_schema property has its own description rather than a bare type. Both changes matter, a model choosing between tools weighs the top-level description most heavily, but a property with no description still forces it to guess what value belongs there.

Name the alternative ability inside the description

Explicitly telling a model “use the other ability instead when X” inside a description is one of the most effective ways to resolve overlap between two abilities that genuinely both need to exist. It costs nothing at runtime and removes an entire class of misrouted calls.

Step 4: Scoping input schemas as tightly as the task allows

Beyond disambiguating descriptions, a schema with specific, narrowly typed properties gives a model far less room to pass something malformed than one generic string or object property does. An integer typed post_id with no other option can’t be handed a title string by mistake, whereas a params string property invites exactly that kind of confusion, as seen in Step 2.

Test it: verify a model picks the right ability

Register both abilities from Step 3, connect an MCP client from Course 2 or Course 3, and try two distinct requests in conversation:

"Get me post number 482."
"Find posts about staging site setup."

The first should trigger a my-plugin/get-post-by-id call with post_id: 482, the second a my-plugin/search-posts call with query: "staging site setup". Check your site’s logs or a temporary error_log() call inside each execute_callback to confirm which ability actually ran for each request.

A long, generic description is not the same as a good one

Padding a description with more words doesn’t fix vagueness, “Does things with posts, including fetching, searching, updating, creating, and deleting them, in a flexible and powerful way” is still functionally as unhelpful as “does things with posts.” A model needs specific, decision-relevant information: when to use this ability, what it needs, and what distinguishes it from the alternative, not more adjectives.

Recap

The description field and every property description inside input_schema are prompt content a model reads to decide whether and how to call an ability, not internal documentation. Vague descriptions get abilities skipped or misused, and overlapping abilities with similar descriptions get confused for each other. Naming the alternative ability explicitly inside each description, and scoping input_schema properties as narrowly and specifically as the task allows, are the two most reliable fixes for both problems.

Resources & further reading

← Getting Reliable JSON Back From the Model Guardrails: Constraining Model Behavior →