Store Projects

Project: AI Shopping Assistant

⏱ 16 min

This project is customer-facing, which changes the rules. Everything else in this course so far has assumed an admin or staff caller. A shopping assistant talks to anonymous site visitors, so it has to be strictly read-only and careful about exactly which fields it surfaces, no cost prices, no supplier data, no exact stock counts that give competitors useful numbers.

What you'll learn in this lesson
Reusing woocommerce/products-query for customer search
Calling an official ability from your own code with wp_get_ability().
A customer-safe stock-check ability
In stock or out of stock only, never exact quantities, for public callers.
Turning ability output into a conversational answer
Using wp_ai_client_prompt() to phrase results, not to decide facts.
Permission scoping for a public, read-only surface
What "safe to expose broadly" actually requires here.
Prerequisites

WooCommerce 10.9+ for woocommerce/products-query. WordPress 7.0+ with an AI provider configured for the conversational layer. If you’re below 10.9, substitute your own wc_get_products()-based search ability, the pattern is identical.

Step 1: Calling the official search ability from your own code

Rather than reimplementing product search, call WooCommerce’s own ability directly:

// File: my-plugin.php
function my_plugin_search_products( $search_term ) {
	$ability = wp_get_ability( 'woocommerce/products-query' );
	if ( ! $ability ) {
		return array();
	}
	return $ability->execute( array( 'search' => $search_term, 'per_page' => 5 ) );
}

This is the same “call an ability from your own PHP” pattern from Course 1, applied to an official WooCommerce ability instead of your own.

Step 2: A customer-safe stock-check ability

The official abilities return whatever fields WooCommerce decides to include, which may be more detail than you want a public assistant repeating verbatim. This ability deliberately narrows the output to a boolean-ish status:

// File: my-plugin.php
add_action( 'wp_abilities_api_init', function () {
	wp_register_ability(
		'my-plugin/check-product-availability',
		array(
			'label'         => __( 'Check product availability', 'my-plugin' ),
			'description'   => __( 'Returns whether a product is currently in stock. Does not return exact stock quantities.', 'my-plugin' ),
			'category'      => 'store-inventory',
			'input_schema'  => array(
				'type'       => 'object',
				'properties' => array(
					'product_id' => array( 'type' => 'integer' ),
				),
				'required'   => array( 'product_id' ),
			),
			'output_schema' => array(
				'type'       => 'object',
				'properties' => array(
					'available' => array( 'type' => 'boolean' ),
					'name'      => array( 'type' => 'string' ),
				),
			),
			'permission_callback' => '__return_true',
			'execute_callback' => function ( $input ) {
				$product = wc_get_product( $input['product_id'] );
				if ( ! $product ) {
					return array( 'available' => false, 'name' => '' );
				}
				return array(
					'available' => $product->is_in_stock(),
					'name'      => $product->get_name(),
				);
			},
			'meta' => array(
				'annotations' => array( 'readonly' => true ),
				'mcp'         => array( 'public' => true ),
			),
		)
	);
} );

permission_callback returning __return_true is a deliberate, reviewed choice here, not an oversight, this ability only ever returns public catalog information already visible on the product page to any visitor. That’s a meaningfully different decision than the write abilities from Lesson 2, which require manage_woocommerce.

Public read-only still means reviewed, not unreviewed

“Safe to expose publicly” isn’t the same as “no thought required.” Before setting permission_callback to always-true on any ability, confirm every field in its output is something an anonymous visitor could already see through the normal storefront, not internal-only data that happens to live on the same object.

Step 3: Wiring the conversational layer

The AI model’s job here is phrasing, not fact-finding, it should only talk about what the ability calls actually returned:

// File: my-plugin.php
function my_plugin_shopping_assistant_reply( $customer_message ) {
	$matches = my_plugin_search_products( $customer_message );

	$prompt = sprintf(
		"You're a helpful shop assistant. A customer asked: \"%s\".\n" .
		"Here are matching products from our catalog (JSON, use only this data): %s\n" .
		"Reply in two sentences, mentioning at most three products by name. " .
		"If nothing matches, say so plainly, don't invent products.",
		$customer_message,
		wp_json_encode( $matches )
	);

	return wp_ai_client_prompt( $prompt )->generate_text();
}

Test it

wp-env run cli wp eval '
echo my_plugin_shopping_assistant_reply( "do you have any waterproof hiking boots" );
'

Then confirm the stock-check ability directly:

wp-env run cli wp eval '
$ability = wp_get_ability( "my-plugin/check-product-availability" );
var_dump( $ability->execute( array( "product_id" => 42 ) ) );
'
Letting the model answer questions the ability data can't support

If a customer asks something the search results don’t cover (“is this true leather or synthetic?”), the model may still attempt an answer using general knowledge rather than your actual product data, this is exactly the hallucination risk from Lesson 3, applied to a live customer conversation instead of an internal draft. Instruct the model explicitly to say “I don’t have that detail” when the supplied data doesn’t answer the question, and treat that as a normal, acceptable response rather than a failure.

Recap

A customer-facing assistant reuses woocommerce/products-query for search, adds a narrow, deliberately-limited custom ability for stock status, and uses wp_ai_client_prompt() only to phrase results the ability calls already produced. Every field returned to an anonymous caller needs a specific “a visitor could already see this on the storefront” justification, that’s the actual bar for permission_callback returning true on a public ability.

Resources & further reading

← Project: Product Description Generator Project: Order-Management Automation →