AI Semantic Search

AI Semantic Search adds meaning-aware (vector) search on top of keyword search, per index. Results blend both — a search for "keep my coffee hot on the summit" can surface a vacuum flask whose product copy never contains those words, because the embedding model understands they mean the same thing.

Under the hood, CelerSearch configures a Meilisearch embedder (an embedding model connection) on the index and sends hybrid search parameters with every query.

The AI Semantic Search panel in Index Settings
Enable AI Semantic Search per index — pick the embedding model, dimensions, and the document template the engine embeds.

Requirements

  • Meilisearch 1.13 or newer — embedders are GA, nothing to enable.
  • Meilisearch 1.3 – 1.12 — embedders exist behind the vectorStore experimental flag, which must be enabled on the instance.

When the bound engine doesn't qualify, the section renders a locked panel explaining the requirement, with the detected reason (e.g. pkgVersion 1.10.0 pre-dates stable embedders). The capability probe is cached for 5 minutes, so an engine upgrade is picked up shortly after.

Enabling It (Admin)

Open Index Settings and pick AI Semantic Search (under the AI Search category in the sidebar). Flip Enable AI semantic search, then configure:

FieldDefaultNotes
Modeltext-embedding-3-smallOr text-embedding-3-large. Switching auto-fills Dimensions.
Dimensions15363072 is auto-filled for -large. Editable (clamped to 1–3072).
Document templateIndex-aware (see below)Template that renders each document into the text that gets embedded. Pre-filled with the index type's own default.
Template max bytes3000Rendered template text is capped at this size (100–100,000).
OpenAI API Key—Stored server-side, shown masked (******** + last 4 chars). Leaving the masked value unchanged keeps the saved key.
Semantic ratio0.5Slider from 0 to 1: 0 is pure keyword search, 1 is pure semantic search.

v1 ships one provider: OpenAI. The Provider dropdown only appears once additional sources are registered via celersearch_embedder_sources.

At a semantic ratio of 0, no hybrid parameters are sent at all — searches behave exactly like plain keyword search (and no query embeddings are requested).

Document Templates

The document template is a liquid snippet the engine renders for every document to produce the text that gets embedded. Each index type pre-fills a default built from the fields its documents actually emit — the field names differ per type:

Index typeDocument fields used
Poststitle, excerpt, content
Productspost_title, short_description, content
Ordersnumber, status_name, customer, billing, items[]

For example, the products default:

LIQUID
{% if doc.post_title %}{{ doc.post_title }}
{% endif %}{% if doc.short_description %}{{ doc.short_description }}
{% endif %}{% if doc.content %}{{ doc.content }}{% endif %}

Guard every field

Rendering is strict: referencing a field a document doesn't have fails that document's entire batch — and since a rebuild clears the index first, a bad template leaves the index empty. Always wrap fields in {% if doc.field %}…{% endif %} guards, and only reference fields your index type emits.

Third-party index types can supply their own default via the celersearch_embedder_document_template filter (receives the template and the index).

Rebuilds and Embedding

Embedder settings ride the same settings push as everything else in Index Settings. Two consequences worth knowing:

  • Rebuilds re-apply the embedder automatically. A full rebuild deletes and recreates the remote index — the embedder config is re-pushed as part of that, before any documents flow. No manual re-configuration needed.
  • Rebuilds embed incrementally. Because the embedder is in place before documents arrive, each rebuild batch is embedded as it's indexed — there's no separate "embed everything" pass at the end.

New and updated documents are embedded automatically through real-time sync. Documents that were already indexed before you enabled the embedder have no vectors yet — run a full Rebuild once to embed them.

Where Hybrid Search Applies

Once enabled, hybrid search is injected centrally — every CelerSearch call path gets it automatically:

  • Main site search (WordPress query interception)
  • Autocomplete
  • WooCommerce shop filters

No per-area configuration. Code that passes its own hybrid parameter wins over the automatic injection (see celersearch_hybrid_search_params below).

Costs

Embedding calls go to OpenAI using your API key:

  • One embedding per document at index time (and again whenever a document changes).
  • One embedding per search query at search time (skipped when the semantic ratio is 0).

OpenAI's embedding models are billed per token; the document template and its byte cap control how much of each document is sent.

Sizing

On CelerSearch Cloud, semantic and hybrid search is included on the Semantic AI plans (Pro AI, Business AI, Scale AI and Ultra AI). Semantic search stores an AI representation of every document alongside its text, so AI plans hold fewer documents than keyword plans at the same tier.

Counts assume standard AI embeddings (such as text-embedding-3-small); high-precision embeddings (such as text-embedding-3-large) use about twice the capacity, and every extra embedder adds to it. See Plans for pricing and what counts toward capacity.

Developer Hooks

See also Service Hooks for the rest of the Meilisearch settings surface.

celersearch_embedder_sources

Register additional embedder sources (e.g. Meilisearch's rest, ollama, or userProvided sources). Fields render generically in the admin UI, secret fields get masking automatically, and — because sources registered here feed the same settings push — external configs inherit the rebuild persistence described above.

PHP
add_filter('celersearch_embedder_sources', function ($sources) {
    $sources[] = [
        'slug'   => 'ollama',
        'label'  => 'Ollama',
        'fields' => [
            [
                'name'       => 'url',
                'label'      => fn() => __('Ollama URL', 'my-plugin'),
                'type'       => 'text',
                'default'    => 'http://localhost:11434/api/embed',
                'engine_key' => 'url',
            ],
            [
                'name'       => 'model',
                'label'      => fn() => __('Model', 'my-plugin'),
                'type'       => 'text',
                'default'    => 'nomic-embed-text',
                'engine_key' => 'model',
            ],
            [
                'name'       => 'document_template',
                'label'      => fn() => __('Document template', 'my-plugin'),
                'type'       => 'textarea',
                // Context-less fallback; with an index in scope the default is
                // the index type's own template. Guard fields with {% if %}.
                'default'    => "{% if doc.title %}{{ doc.title }}\n{% endif %}{% if doc.content %}{{ doc.content }}{% endif %}",
                'engine_key' => 'documentTemplate',
            ],
            [
                'name'       => 'api_key',
                'label'      => fn() => __('API Key', 'my-plugin'),
                'type'       => 'password',
                'secret'     => true,
                'default'    => '',
                'engine_key' => 'apiKey',
            ],
        ],
    ];
    return $sources;
});

Each field's engine_key maps it onto the Meilisearch embedder payload; an optional sanitize callable cleans the posted value.

celersearch_service_meilisearch_embedders

Filter the final embedders payload pushed to Meilisearch — keyed by embedder name (the plugin manages the default embedder).

PHP
add_filter('celersearch_service_meilisearch_embedders', function ($embedders, $index, $service) {
    // Add a second, engine-side embedder alongside the plugin-managed one
    $embedders['custom'] = [
        'source'     => 'userProvided',
        'dimensions' => 768,
    ];
    return $embedders;
}, 10, 3);

When the array ends up empty, the embedders key is omitted from the settings push entirely — non-destructive to embedders managed outside the plugin.

celersearch_hybrid_search_params

Per-query override of the injected hybrid parameters. Return null (or an empty value) to disable hybrid for that query, or tweak the ratio:

PHP
add_filter('celersearch_hybrid_search_params', function ($hybrid, $index, $params) {
    // Lean more semantic for a specific index
    if ($hybrid && $index->get_slug() === 'products') {
        $hybrid['semanticRatio'] = 0.8;
    }
    return $hybrid;
}, 10, 3);

Callers may also set hybrid in the search params themselves — a caller-provided value (including an explicit null) always wins over the automatic injection.

Next Steps