Table of contents :

Fuzzy Product Search with Vespa

100-days-of-vespa-ai-woocommerce-022

Table of contents :

WooCommerce shoppers do not always search with the exact product name. They may type iphnoe case, omit part numbers, or use a brand spelling that differs from the catalog. Vespa can handle these near matches with its fuzzy query operator while retaining structured filters such as stock status, price, category, and product visibility.

For a WooCommerce agency, the practical pattern is to keep WooCommerce as the source of product and order data, replicate searchable product fields into Vespa, and send catalog search requests to Vespa. Fuzzy matching is then applied only to fields where spelling variation is useful, rather than indiscriminately across the entire document.

When fuzzy search is appropriate

Fuzzy search compares terms within a configurable edit distance. An edit is an insertion, deletion, substitution, or transposition, depending on the matching behavior supported by the search implementation. This makes it useful for errors such as:

  • iphnoe instead of iphone
  • bluetooh instead of bluetooth
  • shampagne instead of champagne
  • A partially mistyped model name such as WH1000XM

Fuzzy matching is not a replacement for synonyms, stemming, faceting, or semantic retrieval. A search for running shoes should generally be handled through normal text matching, synonyms, or embeddings. Fuzzy search is primarily a typo-tolerance mechanism.

Define a Vespa product document

A product document should contain the fields needed for retrieval, filtering, ranking, and display. The following schema is a compact starting point for a WooCommerce catalog:

schema product {
    document product {
        field product_id type int {
            indexing: attribute | summary
        }
        field sku type string {
            indexing: index | summary
        }
        field title type string {
            indexing: index | summary
            index: enable-bm25
        }
        field search_text type string {
            indexing: index | summary
            index: enable-bm25
        }
        field category_ids type array<int> {
            indexing: attribute | summary
        }
        field price type double {
            indexing: attribute | summary
        }
        field stock_status type string {
            indexing: attribute | summary
        }
        field catalog_visibility type string {
            indexing: attribute | summary
        }
        field popularity type double {
            indexing: attribute | summary
        }
    }

    fieldset default {
        fields: title, search_text, sku
    }

    rank-profile product_search inherits default {
        first-phase {
            expression: bm25(title) + bm25(search_text)
        }
    }

    rank-profile product_search_with_popularity inherits product_search {
        first-phase {
            expression: bm25(title) + bm25(search_text) + log10(popularity + 1)
        }
    }
}

The search_text field can contain a normalized combination of the product name, short description, brand, model number, and selected attributes. Do not automatically place every WooCommerce field into this value. Long descriptions, internal notes, and unrelated metadata can reduce precision and increase the cost of matching.

Query a field with fuzzy matching

Vespa supports a fuzzy query item that can be placed inside a field query. For example, the following YQL expression searches the title for a term with a maximum edit distance of two:

select * from sources * where title contains ({ "maxEditDistance": 2 }fuzzy("iphnoe"))

A complete HTTP request can combine that expression with attribute filters:

curl -G 'https://search.example.com/search/' \
  --data-urlencode 'yql=select * from sources * where catalog_visibility = "visible" and stock_status = "instock" and title contains ({ "maxEditDistance": 2 }fuzzy("iphnoe"))' \
  --data-urlencode 'ranking=product_search' \
  --data-urlencode 'hits=20'

The exact query expression should be generated by the application rather than concatenated from unchecked request parameters. Validate the requested field, escape user input according to the Vespa query syntax, and apply maximum lengths and rate limits at the API boundary.

Search multiple product fields

Customers may mistype a product title, a brand, or a model number. A weighted OR expression allows the application to search more than one field:

select * from sources * where (
    title contains ({ "maxEditDistance": 2 }fuzzy("bluetooh"))
    or search_text contains ({ "maxEditDistance": 2 }fuzzy("bluetooh"))
    or sku contains ({ "maxEditDistance": 1 }fuzzy("bluetooh"))
)

In practice, SKU matching usually deserves stricter rules than descriptive text. A one-character error in a short SKU can match many unrelated products, so agencies commonly use exact or prefix matching for known SKU formats and reserve fuzzy matching for longer model identifiers.

Use a fallback instead of making every query fuzzy

Fuzzy matching can increase recall, but it may also introduce false positives. A robust storefront search flow normally tries exact and ordinary text matching first, then uses fuzzy matching when the first request returns too few results.

  1. Normalize the query by trimming whitespace and applying the catalog’s case and punctuation rules.
  2. Run the normal BM25 query against title, brand, and search text.
  3. If the result count is below a threshold, run a fuzzy query with a conservative edit distance.
  4. Show a correction or alternate-query message only when the fuzzy results are materially better.
  5. Preserve the customer’s filters, pagination limits, and visibility rules in both requests.

For example, the normal query might be:

select * from sources * where (
    userInput(@query)
) and stock_status = "instock"

The fallback can target the most useful fields explicitly:

select * from sources * where (
    title contains ({ "maxEditDistance": 1 }fuzzy("running"))
    or search_text contains ({ "maxEditDistance": 1 }fuzzy("running"))
) and stock_status = "instock"

The application should build the fallback from the customer’s actual query terms. The example is intentionally explicit so that the distance and fields are visible; production code should tokenize and validate terms before creating the Vespa query.

Keep WooCommerce filters outside the fuzzy text clause

WooCommerce filters such as product category, stock status, price range, and catalog visibility should remain structured Vespa constraints. They should not be appended to the text that is being fuzzily matched.

select * from sources * where
    category_ids contains 42
    and price >= 25
    and price <= 100
    and stock_status = "instock"
    and catalog_visibility = "visible"
    and search_text contains ({ "maxEditDistance": 1 }fuzzy("wireles headphones"))

When category or taxonomy values are represented as attributes, they can be filtered efficiently without affecting the edit-distance behavior. The same applies to numeric price filters and boolean-like catalog state fields.

Feed changes from WooCommerce

A webhook or queue consumer should update the Vespa document whenever a product is created, edited, deleted, or changes stock status. A typical document update contains only fields needed by search:

curl -X PUT \
  'https://search.example.com/document/v1/product/product/docid/12345' \
  -H 'Content-Type: application/json' \
  --data '{
    "fields": {
      "product_id": 12345,
      "sku": "WH-1000XM5",
      "title": "Wireless Noise Cancelling Headphones",
      "search_text": "Sony wireless noise cancelling headphones WH-1000XM5 black",
      "category_ids": [42, 77],
      "price": 349.99,
      "stock_status": "instock",
      "catalog_visibility": "visible",
      "popularity": 812
    }
  }'

Use an idempotent document identifier based on the WooCommerce product ID. For variable products, choose deliberately between one Vespa document per parent product and one document per variation. If customers buy variations separately and search or filter by variation-specific SKU, price, or stock, indexing variations as separate documents usually provides more accurate results.

Control edit distance and prefix matching

Do not begin with a large edit distance across the entire catalog. A distance of one is often a safer default for ordinary product words, while a distance of two may be useful for longer terms. Short terms need special handling because a small number of edits represents a large percentage of the word.

Requiring a matching prefix can reduce accidental matches. For example, a product search service may allow a larger edit distance only after the first few characters match. Configure this deliberately for the Vespa version and query syntax used by the deployment, and test terms at the minimum and maximum lengths supported by the storefront.

Measure relevance with real WooCommerce queries

Use search logs and click or add-to-cart events to evaluate fuzzy search. Create test cases containing common misspellings, model numbers, brand names, short terms, and queries with filters. Compare:

  • Zero-result rate before and after fuzzy fallback
  • First relevant product position
  • Click-through and add-to-cart rate
  • False-positive rate for short or ambiguous terms
  • Latency at the catalog’s real document count and traffic level

Also test expensive combinations, such as fuzzy text search with several category filters and a high result limit. Set a practical timeout and return a useful empty-state response rather than allowing a slow query to degrade the entire WooCommerce storefront.

Implementation checklist

  • Index only customer-facing product fields in the fuzzy search path.
  • Use BM25 or another explicit rank profile for ordinary text relevance.
  • Keep taxonomy, stock, visibility, and price constraints structured.
  • Use conservative edit distances, especially for short terms and SKUs.
  • Prefer normal matching first and fuzzy matching as a controlled fallback.
  • Escape and validate all user-supplied query text.
  • Keep Vespa documents synchronized with WooCommerce product and variation changes.
  • Evaluate results with real misspellings and conversion events, not only synthetic examples.
Trending posts
You might also like