Add background synchronization for post embeddings - #1041
theaminulai wants to merge 1 commit into
Conversation
Adds an Embedding Sync experiment that keeps stored post embeddings up to date as content changes, without embedding synchronously inside a save request and without needing to re-scan a whole site to find what changed. save_post queues a post's ID in a single wpai_embedding_sync_queue option; a recurring cron event (wpai_embedding_sync_process_queue, every 15 minutes by default) drains a bounded batch of that queue, skips any post whose content hash already matches what's stored (Embedding_Repository::get_content_hash()), and embeds the rest in one batched generate_embeddings() call per run. A provider failure re-queues the whole batch for the next run instead of losing it. before_delete_post removes a deleted post's queue entry and its stored embeddings. Like every other embedding-consuming code path in this plugin, this requires an explicit provider and model — configured through this experiment's Developer Options, the same mechanism abilities already use for developer model overrides. Nothing is embedded until one is configured. Part of WordPress#962.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1041 +/- ##
=============================================
- Coverage 80.23% 80.23% -0.01%
- Complexity 2998 3042 +44
=============================================
Files 124 126 +2
Lines 11978 12116 +138
=============================================
+ Hits 9611 9721 +110
- Misses 2367 2395 +28
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
What?
Part of #962
Adds an Embedding Sync experiment that keeps stored post embeddings up to date in the background as content changes, addressing the "Add background synchronization and large-scale processing" item tracked in #1036.
Why?
The embeddings storage/CRUD layer added in #976 gives the plugin a place to store vectors and a way to check whether a stored vector is stale (
Embedding_Repository::get_content_hash()), but nothing in the plugin actually keeps that store synchronized with post content on its own — today the only way to (re-)embed a post is thewp ai embeddings generateWP-CLI command, run by hand, one post (or one string) at a time. A real site needs this to happen automatically, and needs it to not block the editor: embedding a post synchronously insidesave_postwould mean every save waits on an external provider request, and re-scanning a whole site's content on every change does not scale.How?
includes/Embedding_Sync/Embedding_Sync_Manager.php(new module, parallel toincludes/Logging/):save_post→maybe_queue_post(), which records the post ID in a singlewpai_embedding_sync_queueoption (a set, not a copy of content) after excluding autosaves/revisions and filtering by configurable post type (defaultpost,page) and status (defaultpublish). Saving a post never waits on a provider request.wpai_embedding_synccron schedule (15 minutes by default, filterable, floored at 15 minutes per the platform's cron-interval guidance) and scheduleswpai_embedding_sync_process_queueon it.process_queue()resolves the provider and model from this experiment's Developer Options (get_feature_developer_model_config()— the same mechanismAbstract_Abilityalready uses for developer model overrides), takes a bounded batch off the queue (wpai_embedding_sync_batch_size, default 20), drops any post whose content hash already matches what's stored (no provider request for unchanged content), and embeds the rest in a single batchedgenerate_embeddings()call, storing results viaEmbedding_Repository::save_many(). A provider error re-queues the whole batch for the next run rather than losing it or storing anything partial.before_delete_post→handle_post_deleted(), which drops the post from the queue and deletes its stored embeddings across every provider/model viaEmbedding_Repository::delete_for_object().generate_batch()is a small, separately-overridable method wrappinggenerate_embeddings(), so tests can substitute a fake generator instead of making live provider requests.includes/Experiments/Embedding_Sync/Embedding_Sync.php: the thinAbstract_Featureglue class, following the exact shape ofExperiments/AI_Request_Logging/AI_Request_Logging.php. Registered inExperiments::EXPERIMENT_CLASSES.includes/Admin/Uninstall.php: clears the new cron hook on uninstall (the queue option itself is already covered by the existingwpai_option-prefix cleanup).docs/experiments/embedding-sync.md: new experiment doc following the established per-experiment template (Summary, Key Hooks, Architecture, Filter/Action Hooks, Testing, Notes).Not in scope for this PR: chunking (the CLI's
--chunkflag already covers ad hoc chunked embedding; background sync embeds each post as a single vector), and reacting to a post leaving the synced status set (e.g. unpublishing) — its existing embedding is left as-is rather than removed.Testing Instructions
composer installvendor/bin/phpunit --filter "Embedding_Sync_ManagerTest|UninstallTest"wp cron event run wpai_embedding_sync_process_queue) and confirm the post now has a stored embedding (e.g.wp ai embeddings compare <id> <id>reports cosine similarity1). Full steps indocs/experiments/embedding-sync.md#testing.I wasn't able to run the full WP-integrated PHPUnit suite cleanly in my local environment:
Embedding_SchemaTestandEmbedding_RepositoryTest(pre-existing, unmodified by this PR) also fail locally with "The embeddings table could not be created" when run insideWP_UnitTestCase's default transactional test isolation, which rewritesCREATE TABLE/DROP TABLEinto theirTEMPORARYequivalents —dbDelta()doesn't create the table successfully against that rewrite on this local MySQL 8.0.31/Windows setup, though creating it directly (outside that rewrite) works fine. I confirmed this by reproducing the identical failure on an unmodifieddevelopcheckout, so it isn't something this PR introduces. My newEmbedding_Sync_ManagerTesthits the same four table-dependent cases for the same reason; the other seven (queueing, filtering, deletion, and the no-provider-configured path, which don't need the table) pass. I also temporarily worked around the table-creation issue locally (removing theWP_UnitTestCasetemp-table filters, matching the techniqueUninstallTest.phpalready uses for the same reason) to confirm all 11 tests pass end to end, including the float-vector round trip throughVector_Codec; that workaround is not part of this diff sinceEmbedding_RepositoryTest.phpdoesn't use it either and I didn't want to touch an unrelated test file's isolation strategy.phpcs --standard=phpcs.xml.distandphpstan analyseboth pass clean on every changed/new source file.Changelog Entry