Add support for filtered semantic search - #22
Conversation
- Introduced 'filters' parameter in Query and REST classes to allow for custom filtering. - Implemented canonicalization of filters in the Search class to ensure consistent handling. - Enhanced caching mechanism to account for filters, ensuring unique cache keys for different filter sets. - Added unit tests to validate the behavior of the new filter functionality and its impact on SQL queries.
… options. Default demo on Admin but supported natively in the Search module. - Added advanced search facets in the embeddings view, allowing users to filter by post type, category, tag, author, and date range. - Implemented responsive design for search controls to improve usability on narrow screens. - Introduced a strategy parameter in the search class to determine filtering approach (pre-filter or post-filter) based on the presence of filters and candidate counts. - Updated the search logic to handle filtering strategies effectively, ensuring optimal performance. - Added unit tests to validate the new filtering strategies and their integration with existing search functionality.
|
@rbcorrales you have a better understanding of how this could play out with the other plugins like Search Blocks and Smart Search. I think it's useful to have the filter options at the core WPVDB level, so that's what I opened this PR, but let me know your thoughts. The main use case I see is for blogs where there are many Post Categories and they want to (for instance) implement a Chatbot that feeds from only 1 of the existing Categories. For example, a Travel site might have Posts about places, tours and restaurants, but also posts about Gift Guides and Style, and they might want to filter out the latter for a "Travel Agent" bot (this is a real life example). I noticed there' a With this changes, the centralized SQL query now receives the WP taxonomies and other metadata as optional arguments. |
There was a problem hiding this comment.
Pull request overview
This PR extends WPVDB’s semantic search to support WordPress-style filtering (post type/status, author constraints, tax/date queries, and doc_type), and integrates the filter set into cache keys to prevent cross-contamination between filtered and unfiltered cached results. It also introduces a prefilter/postfilter strategy mechanism (with an “auto” heuristic) and adds admin UI facets plus a query-plan panel on the Embeddings screen to tune and observe filtered searches.
Changes:
- Add a filter layer to
Search::query()/Search::build_clauses()plus canonicalization + hashing for stable cache keys. - Add strategy selection (prefilter/postfilter/auto) including a postfilter top-up loop and candidate counting.
- Add Embeddings admin search facets (GET-based) and query-plan display, with supporting CSS.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/SearchTest.php | Adds unit coverage for filter canonicalization, cache seed stability, clause building behavior, and strategy normalization. |
| includes/class-wpvdb-search.php | Implements filters, candidate counting, strategy heuristic, postfilter execution, and cache seeding utilities. |
| includes/class-wpvdb-rest.php | Incorporates filters into REST query execution and into the cache key override to avoid incorrect cache hits. |
| includes/class-wpvdb-query.php | Allows WP_Query integration to pass vdb_filters into the vector search layer. |
| assets/css/admin.css | Adds responsive layout styles for the new Embeddings facet controls. |
| admin/views/embeddings.php | Adds Embeddings search facets, strategy override control, and query-plan reporting UI. |
| .gitignore | Ignores a new local cache directory under docker setup. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Updated the embeddings view to use a default strategy for filtering if none is provided. - Modified SQL queries in the Search class to count distinct IDs and group results, enhancing performance and accuracy. - Improved sorting logic to ensure consistent cache behavior for scalar lists.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (3)
includes/class-wpvdb-search.php:755
- Using
GROUP BY e.idwhile selecting multiple non-aggregated columns ({$columns}plus computeddistance) can fail on MySQL/MariaDB whenONLY_FULL_GROUP_BYis enabled (SQL error about non-grouped columns). To de-duplicate rows introduced by taxonomy joins without relying on relaxed SQL modes, preferSELECT DISTINCTand drop theGROUP BY.
GROUP BY e.id
includes/class-wpvdb-search.php:819
- Same
GROUP BY e.idissue as the native path: selecting many non-aggregated columns withGROUP BYcan break underONLY_FULL_GROUP_BY. If the goal is to de-duplicate rows caused by joins,SELECT DISTINCTis safer and portable.
GROUP BY e.id
includes/class-wpvdb-search.php:368
- When
tax_queryadds joins, this query can return duplicatee.idvalues. Since the result is only used as a set membership test, selecting distinct IDs avoids unnecessary duplicates and reduces work in PHP when building$allowed.
"SELECT e.id FROM {$table} e{$clauses['join']} WHERE {$where}",
Add support for filtered semantic search: filter layer, cache-key correctness, strategy heuristic, admin facets
Why
WPVDB\Searchreturns the nearest vectors for a query, with no way to constrain the result set. Integrators cannot ask for "posts in the XYZ category, published this year, by this author".This is useful for folks that vectorize the full collection of categorized Posts, and want to implement a more granular search.
What changed
1. Filter layer (
class-wpvdb-search.php)Search::query()accepts afiltersargument:Supported keys:
post_type,post_status,author/author__in/author__not_in,post__in/post__not_in,date_query,tax_query,doc_type.The filters apply in
build_clauses(), which feeds both the native and the PHP fallback paths. The two engines therefore stay in agreement.tax_queryanddate_querydelegate toWP_Tax_Query::get_sql( 'e', 'doc_id' )andWP_Date_Query. Core generates correct, escaped SQL against a non-wp_postsprimary table, so this PR ships no query DSL of its own. Verified on WP 7.0.2.No schema change. All facets derive from
wp_postsand the term tables throughdoc_id.2. Cache-key correctness
Cachepreviously keyed query results on version, text, model, and limit. A request filtered to one category would return results cached from an unfiltered request with the same text. That failure is silent and hard to reproduce, so it ships in the same PR as the filters.Search::filters_cache_seed()canonicalizes the filter array and hashes it. REST folds the hash into the cache key. An empty filter set returns an empty seed, so existing cache keys stay byte-identical.3. Strategy heuristic: pre/post filter.
A filtered vector search can run two ways. Pre-filtering scans the filtered subset exactly. Post-filtering uses the vector index across the entire dataset and discards non-matching rows. Neither wins in all cases.
Search::query()acceptsstrategy:auto,prefilter, orpostfilter.candidates == 0candidates <= 5000candidates / total >= 0.5Post-filtering widens its over-fetch window and retries while results come up short, bounded by
wpvdb_search_max_topup_rounds.Tuning filters:
wpvdb_search_exact_scan_threshold,wpvdb_search_broad_ratio,wpvdb_search_overfetch_multiplier,wpvdb_search_max_topup_rounds, andwpvdb_search_strategyas a hard override.The default is
prefilter, notauto. See the open item below.4. Admin facets and query plan
The Embeddings screen gains facet controls, built from core components (
wp_dropdown_categories(),wp_dropdown_users(),submit_button()) inside the standard.tablenavfilter row:Advanced searchdisclosure. It opens automatically when any advanced facet is active.Controls submit as
GET, so a tuned query is a shareable URL. Styles live inassets/css/admin.cssand reflow at WP's 782px breakpoint.The query-plan panel reports strategy and how it was chosen, candidates against total, engine and database type, top-up rounds, rows scanned, and timings. That panel is how the thresholds get calibrated against a real dataset.
It also populates the
plan['candidates']key, which was previously declared and never set.Verification
Against MariaDB 11.7 with a live index:
wp_postsand the term tables.cocktailreturns 3 of 3,beerreturns 0 of 0.prefilterandpostfilterreturn identical result sets for the same query.embed_ms=0with an empty API key and raises no error, which confirms it skips the paid round trip.tax_query+post_type+date_queryproduces valid SQL on both query shapes.11 new unit tests. The suite passes at 224 tests and 824 assertions. PHPCS reports no new violations.
Follow-ups
argsschema and WP-CLImetacolumn scaffolding