Caching

Caching AI Responses

⏱ 16 min

Course 8’s performance lesson cached an ability’s execute_callback, the query behind a “list orders” call. That’s caching a WordPress read. This lesson caches something different: the AI-generated output itself, the actual text or JSON a model returned for a given prompt. The two look similar on the surface, both are wp_cache_set() calls with a key and a TTL, but an AI response has a cost dimension a database query doesn’t. Every uncached call to generate_text() is a billed request to a model provider, so a cache hit here isn’t just a faster response, it’s a call you didn’t pay for. The harder problem isn’t writing the cache, it’s knowing which AI responses are actually safe to reuse and which ones defeat the entire point of the feature the moment they go stale.

What you'll learn in this lesson
Keying an AI response cache correctly
A hash of the full prompt and its parameters, not just a post ID or a guess.
Transients vs the object cache for this job
When a simple transient is enough and when you actually need wp_cache_*.
Safe-to-cache: deterministic summarization of unchanging content
The clearest case where caching an AI response is a pure win.
Risky-to-cache: anything meant to feel fresh or conversational
Where a cache hit quietly breaks the feature it is supposed to speed up.
A reusable caching wrapper around wp_ai_client_prompt()
One function every AI feature in your plugin can call through.
Prerequisites

Comfortable calling wp_ai_client_prompt()->generate_text() (Course 3), and the basic idea of set_transient() / get_transient() and wp_cache_get() / wp_cache_set() from Course 8’s performance lesson. This lesson assumes you know those functions exist and goes straight into using them for AI output specifically.

Step 1: Key the cache on the actual input, not a proxy for it

The most common mistake in caching an AI response is keying it on something adjacent to the prompt, a post ID, a feature name, instead of the prompt itself. If two different inputs produce the same key, the second request silently gets the first request’s output. Build the key from a hash of everything that affects the result: the prompt text, the system instruction, and any parameter (temperature, model preference) that changes what comes back.

// File: my-plugin/class-ai-cache.php
function my_plugin_ai_cache_key( string $prompt, string $system_instruction, string $model_hint = '' ): string {
	$fingerprint = wp_json_encode( array(
		'prompt'     => $prompt,
		'system'     => $system_instruction,
		'model_hint' => $model_hint,
	) );

	return 'my_plugin_ai_' . md5( $fingerprint );
}
A post ID is not a stable proxy for its content

Keying on post_123 instead of a hash of the actual prompt text feels simpler, but it silently breaks the moment the post is edited: the cache key stays the same while the content behind it changes, and the AI response you serve no longer matches what you generated it from. Hash the real input. If you also want a post-scoped key for easy invalidation (covered next lesson), store it as a second, separate mapping rather than replacing the content hash with it.

Step 2: Choose transients or the object cache, deliberately

Both work for this. The difference is what they’re good at:

Transients
Always available, even without a persistent object cache backend. Stored in the options table (or a real cache if one exists behind the transients API). Good default for most AI response caching.
wp_cache_get() / wp_cache_set()
On a site running a real persistent object cache (Redis or Memcached, configured via a drop-in or hosting-provided integration), this is shared cleanly across requests and workers, and supports cache groups you can flush together. Without a persistent backend, it still works but resets every request, which is far less useful for output that's expensive to regenerate.

If you don’t know whether the target site has a persistent object cache configured, transients are the safer default, they behave consistently either way. If you control the hosting environment and know a persistent backend is present, the object cache gives you cache groups, which matters a great deal for the invalidation strategy in the next lesson.

// File: my-plugin/class-ai-cache.php
function my_plugin_get_cached_ai_response( string $key ) {
	$cached = get_transient( $key );
	return false === $cached ? null : $cached;
}

function my_plugin_set_cached_ai_response( string $key, string $value, int $ttl_seconds ): void {
	set_transient( $key, $value, $ttl_seconds );
}

Step 3: Wrap the call, don’t scatter caching logic across every feature

A caching wrapper that every AI feature calls through means the caching decision lives in one place, not copy-pasted into every function that calls generate_text():

// File: my-plugin/class-ai-cache.php
function my_plugin_generate_text_cached( string $prompt, string $system_instruction, int $ttl_seconds ) {
	$key    = my_plugin_ai_cache_key( $prompt, $system_instruction );
	$cached = my_plugin_get_cached_ai_response( $key );

	if ( null !== $cached ) {
		return $cached;
	}

	$result = wp_ai_client_prompt( $prompt )
		->using_system_instruction( $system_instruction )
		->generate_text();

	if ( is_wp_error( $result ) ) {
		return $result;
	}

	my_plugin_set_cached_ai_response( $key, $result, $ttl_seconds );
	return $result;
}

Notice errors are never cached, only a successful string result is stored. Caching a WP_Error would mean a transient provider outage gets replayed as a failure for every request until the TTL expires, which is worse than the outage itself.

Step 4: Decide whether the feature can tolerate a cache at all

This is the actual judgment call, and it matters more than the mechanics above. Caching an AI response is safe when the task is deterministic-in-intent and the underlying input doesn’t change on its own:

Safe versus risky to cache, by task shape
TaskCache?Why
Summarizing a published blog post’s bodyYesThe source text is fixed once published; the same input should produce a stable summary, and regenerating it on every page view wastes a call on unchanged content.
Generating alt text for an uploaded imageYesThe image doesn’t change after upload; one generation, reused indefinitely (or until the image itself is replaced), is correct.
A conversational support-chat replyNoThe whole point is a response to what the user just said, in context. Caching by prompt hash would occasionally serve one user’s reply to another user who happened to type the same words.
”What’s trending right now” style copyNoExplicitly time-sensitive; a cached answer is a stale, wrong answer by design, not just an old one.
An AI-drafted reply a human reviews before sendingDependsSafe to cache the draft while the human hasn’t acted on it yet; once sent, the cached value has served its purpose and should not resurface for a different, later situation.

The dividing line isn’t “is this text generation,” it’s “does this specific call need to reflect something that changes independently of the input I’m hashing.” A summarization task’s only real input is the source text. A conversational reply’s real input includes the moment it was asked in, something no prompt hash can capture.

Don't cache your way into a stale-feeling product

Caching every AI feature by default because caching is cheap and calls are expensive is a real temptation once cost becomes visible (next module). Resist it for anything a user experiences as a live response. A support widget that occasionally repeats a stranger’s cached answer erodes trust faster than a slightly slower response would.

Test it

Call the wrapper twice with an identical prompt and confirm the model is only actually invoked once:

wp-env run cli wp eval '
$prompt = "Summarize in one sentence: This plugin adds AI-powered alt text generation.";
$system = "You are a concise technical summarizer.";

$first  = my_plugin_generate_text_cached( $prompt, $system, 300 );
echo "First call: " . $first . "\n";

$second = my_plugin_generate_text_cached( $prompt, $system, 300 );
echo "Second call (should be instant, identical): " . $second . "\n";
'

Both calls should return identical text, and the second should return noticeably faster since it never reaches the model provider.

Recap

Caching an AI response is not the same problem as caching a database query: the key must be a hash of the actual prompt and its parameters, never a proxy like a post ID, and errors should never be cached. Transients are the safer default; the object cache is worth using deliberately when a persistent backend (Redis or Memcached) is actually configured, since it enables cache groups for the invalidation work in the next lesson. Above all, caching is a decision about the task’s shape, not a default: deterministic summarization of unchanging content is the clean case, anything meant to feel live or conversational is not.

Resources & further reading

Cache Invalidation Strategies for AI-Generated Content →