---
url: 'https://docs.mychatbot.app/search.md'
description: >-
  MyChatBot's product search is a smart, multi-step search: a single query is
  matched several ways at once — by meaning, by keywords, by category, and
  (optionally) by product…
---

# How product search works

MyChatBot's product search is a **smart, multi-step search**: a single query is matched several ways at once — by meaning, by keywords, by category, and (optionally) by product image — then blended and reranked so your AI sales assistant gets one clean, ranked product list back. Read this page when you want the whole flow at a glance before tuning filters, prompts, or image search.

Your assistant reaches this search over the **[Product (commerce) MCP](/search/product-mcp)**. The catalog it searches is whatever product feed you connected in the app.

::: tip In the app: connect a catalog
Product search runs against a product feed you connect under **Knowledge base → Products** ([app.mychatbot.app/knowledge-base](https://app.mychatbot.app/knowledge-base)). Add or refresh a feed there, and MyChatBot indexes it so your assistant can search it. The **Connect AI tools** button on the Products card gives you the ready-made MCP URL to point an agent (or Claude / Cursor) at that catalog.
:::

## Direct vs agentic search

There are **two ways** your assistant can search a catalog, chosen per integration by the
**Agentic Search** toggle in the app. You'll find it when you configure a **Products**,
**Product Feed**, **Product Spreadsheet**, or **Instagram products** source. The toggle
governs *only* product search — FAQ and document knowledge is always simply referenced, with
no modes.

| | **Direct** — toggle OFF (default) | **Agentic** — toggle ON |
|---|---|---|
| How it works | **One** semantic lookup against your catalog; the matching products are handed straight to the assistant | A small **search agent** works the query in steps — discovers a category's filters/attributes, runs several refined searches, and compares — before answering |
| Filters | Supported | Supported, and the assistant chooses them itself |
| Custom search prompt | — | You can give it guidance on how to search *your* catalog |
| Speed | Fastest | A bit slower (it does more work) |
| Best for | Straightforward "find me X" queries | Vague, comparative, or attribute-heavy questions on large / complex catalogs |

::: tip Rule of thumb
Start with **direct** (toggle off) for speed. Turn on **Agentic Search** when your catalog is
large and attribute-rich, or when customers ask nuanced, multi-constraint questions
("a warm waterproof jacket under $150 for hiking"). The step-by-step pipeline below is a
single **direct** lookup — agentic mode simply runs that lookup several times, refining as it
goes.
:::

You flip this toggle where you connect the catalog — see
[Getting inventory in](/search/getting-inventory-in). For Instagram catalogs, the same toggle
lives in the [Instagram products](/search/getting-inventory-in#instagram-products) setup.
See also the [Search tools reference](/search/search-tools),
[Filters, attributes & params](/search/filters-and-params), and
[Troubleshooting](/search/troubleshooting).

## Cheat sheet

The search runs top-to-bottom. Each step below operates on the results of the step above it.

| # | Step | What it does | Skipped when |
|---|------|--------------|--------------|
| 1 | **Understand the query** | Matches the query both by meaning (semantic) and by exact words | Query is code-like (SKU/barcode) or a pasted URL → semantic match dropped so exact matching wins |
| 2 | **Search the whole catalog** | Blends meaning + product-name + keyword + exact-phrase matches into one ranked list | — |
| 3 | **Attribute-filter fallback** | Retries a filtered search a different way if the first pass came back empty | No filters used, or the first pass already returned results |
| 4 | **Pick the best categories** | Groups results by category and ranks the most relevant categories | — |
| 5 | **Rank inside each category** | Re-runs the same blended search inside each top category, in parallel | — |
| 6 | **Rerank** | A relevance model reorders the list for a cleaner top result | `enable_reranking=false`, ≤1 result, or a code/URL query |
| 7 | **Blend in an image match** | Mixes a product-image match into the text results | No `image_url` supplied |

::: warning You build `filters` — the search never parses natural language
`semantic_product_search` matches the whole `query` string, but it only enforces the `filters` **your assistant constructs**. There is **no** natural-language-to-filter extraction: nothing turns "red", "summer", or "under $50" into constraints for you. Splitting the shopper's sentence into `query` text plus structured `filters` is the assistant's job — see [From a shopper sentence to a search call](#from-a-shopper-sentence-to-a-search-call).
:::

### How the search index is organized

Each catalog you connect gets its own search index. The search reads it three ways:

| View | What it holds | Used by |
|------|---------------|---------|
| **Whole catalog** | Every product — matchable by meaning, name, and keywords, plus your filterable attributes | Steps 2–4 (blend + category discovery) |
| **Per-category slice** | The same products, scoped to a single leaf category | Step 5 (per-category ranking) |
| **Image index** | A visual fingerprint of each product image | Step 7 (image blending) |

::: tip Exact names and phrases outrank loose semantic matches
Steps 2 and 5 blend four kinds of match — meaning (semantic), product-name keywords, general keywords, and exact phrases. Name and exact-phrase matches are deliberately weighted to win over loosely-semantic ones, which is why a specific product name usually surfaces its exact item at rank 1. MyChatBot tunes this relevance balance for your catalog — contact [support](https://t.me/MyChatBot_Support_bot) if you need it adjusted.
:::

### Where each filter kind resolves

Every `filters` operator is enforced as a hard constraint on a broad, category-less search — a filter is never silently dropped. The kinds differ only in whether they also *guide* candidate retrieval:

| Filter kind | Example filter | On a broad, category-less search |
|---|---|---|
| Equality / membership | `color eq red`, `stickers in […]` | Enforced — and guides candidate retrieval |
| String substring | `name contains ASHEN` | Enforced (case-insensitive) — and guides candidate retrieval |
| Range / negation / prefix | `price lt 50`, `country ne UK`, `name startswith Ele` | Enforced as a strict constraint on results (doesn't steer retrieval — keep intent in `query`) |

## Step by step

### 1. Understand the query

The query is matched both semantically (by meaning) and by exact words. Two query shapes skip the semantic step to protect exact matching:

* **Code-like** — SKUs, barcodes, model numbers.
* **URL-like** — a pasted product link.

For these, the semantic match is dropped so exact/code matching dominates, and reranking (step 6) is skipped — they already have a decisive exact answer.

### 2. Search the whole catalog

The four match types above run against the whole-catalog index and are blended into one list. Any caller-supplied `filters` are applied as a hard constraint on every match type — all ten operators are enforced. **Equality / membership** (`eq` / `in`) and **string substrings** (`contains`) additionally guide which candidates are retrieved; range, `ne`, and prefix filters constrain the results without steering retrieval. The search fetches extra depth internally so later steps have headroom to rerank.

### 3. Attribute-filter fallback

Defense-in-depth for catalogs indexed before searchable attributes existed. If a **filtered** search comes back empty or errors, the search retries **once** through the typed per-category attribute columns. It only engages when filters are present, so it can never change a healthy, non-empty result.

::: warning Filters suddenly return nothing on an older catalog
If a filter that used to work starts returning `[]` on an older catalog, its searchable attributes were probably indexed before this feature existed. The fallback auto-heals most requests, but the permanent fix is a full re-index — **not a self-serve toggle**. In the app, remove and re-add the product integration (**Knowledge base → Products**), or contact [MyChatBot support](https://t.me/MyChatBot_Support_bot) with your integration and they'll run a full re-index. See [Filters, attributes & params](/search/filters-and-params).
:::

### 4. Pick the best categories

Results are grouped by category and each category is scored by how highly its products ranked. Category order is then:

1. **Term-match categories first** — categories whose top products contain **all** query terms in their name.
2. **Frequency-ranked categories** — the remaining highest-scoring categories.

### 5. Rank inside each category

The selected top categories are searched **concurrently**, each against its own per-category slice, using the identical blended search from step 2.

::: warning Big catalogs fan out wide
A query that spans many categories runs many category searches at once, which can be slow on a very large catalog. If searches feel slow or time out, keep queries specific (or split a huge catalog into more focused integrations), and contact [support](https://t.me/MyChatBot_Support_bot) to tune how many categories are searched.
:::

### 6. Rerank

If reranking is on (the default), the relevance service is available, and there's more than one result, the blended list is reordered by a relevance model for a cleaner top result. Reranking is **skipped** for code-like and URL-like queries — they already have a decisive exact ranking — and a rerank failure falls back to the pre-rerank order.

### 7. Blend in an image match

When `image_url` is supplied, an image-fingerprint search runs against the image index and is blended with the text results. If the image step fails, the search falls back to the text results. Deep dive: [Image search](/search/image-search).

## Copy-paste: a full agentic query

The agent-facing tool is `semantic_product_search` on the [Product (commerce) MCP](/search/product-mcp). A text + filter call:

```json
{
  "tool": "semantic_product_search",
  "arguments": {
    "query": "waterproof winter boots",
    "limit": 20,
    "enable_reranking": true,
    "truncate_description": true,
    "filters": [
      { "attribute": "color",    "operator": "eq", "value": "black" },
      { "attribute": "stickers", "operator": "in", "value": ["Подарунок", "Sale"] }
    ]
  }
}
```

Returns `{ count, products[], categories[] }` — the `categories[]` list reflects the category selection from step 4, so your assistant can offer to drill down.

When the shopper supplies only part of a product name, filter the reserved top-level
`name` field with `contains`:

```json
{
  "tool": "semantic_product_search",
  "arguments": {
    "query": "evening dress",
    "filters": [
      { "attribute": "name", "operator": "contains", "value": "ASHEN" }
    ]
  }
}
```

`contains` is a case-insensitive literal substring match, so this can return a product
named `The Ashen Evening Dress`. It requires a nonblank string; `*` and `?` are treated as
ordinary characters rather than wildcards. Use `eq`/`in`, not `contains`, for membership
in list-shaped fields — a native list such as `stickers` or a comma-separated coverage
list. `contains` matches inside words (`contains "Neath"` matches `Rosneath`), while `eq`
matches the exact value or an exact list element (element match is case-insensitive).

A text **+ image** call (triggers step 7):

```json
{
  "tool": "semantic_product_search",
  "arguments": {
    "query": "red evening dress",
    "image_url": "https://example.com/photo.jpg",
    "limit": 10
  }
}
```

::: tip In the app: get the MCP URL
You don't hand-write the endpoint. Open **Knowledge base → Products**, click **Connect AI tools** on the catalog's card, and copy the generated Product MCP URL (`https://product.mychatbot.app/mcp/<account>/<integration>/stream`) or the ready-made `claude mcp add …` command / Cursor config. Full setup: [Product (commerce) MCP](/search/product-mcp).
:::

## From a shopper sentence to a search call

A sentence like **"red summer dresses under $50"** is not a single call — the assistant has to split it into free-text intent (`query`) and structured constraints (`filters`), and route the *range* part correctly. Map each fragment to where it belongs:

| Fragment | Goes to | Why |
|---|---|---|
| `dress` (the thing) + `summer` (descriptor) | `query`: `"summer dress"` | Drives the blended search and category discovery; `summer` stays in text unless the catalog exposes a filterable season attribute (check with `get_category_attributes`) |
| `red` | `filter`: `{ "attribute": "color", "operator": "eq", "value": "red" }` | A hard equality constraint — works on a whole-catalog search |
| `under $50` | `filter`: `{ "attribute": "price", "operator": "lt", "value": 50 }` | A **range** constraint — enforced on the whole-catalog search too, as long as the stored `price` values read as plain numbers (see the tip below) |

So the sentence can resolve in **one call**:

```json
{
  "tool": "semantic_product_search",
  "arguments": {
    "query": "summer dress",
    "limit": 20,
    "filters": [
      { "attribute": "color", "operator": "eq", "value": "red" },
      { "attribute": "price", "operator": "lt", "value": 50 }
    ]
  }
}
```

When you'd rather browse one category deterministically (a full on-budget listing instead
of ranked recall), split it into the classic **two-call flow**:

**Step 1** — broad search: apply the `eq` constraint and let the search surface candidate categories.

```json
{
  "tool": "semantic_product_search",
  "arguments": {
    "query": "summer dress",
    "limit": 20,
    "filters": [
      { "attribute": "color", "operator": "eq", "value": "red" }
    ]
  }
}
```

Read `categories[]` from the response and pick the dresses leaf category (say its id is `dresses_042`).

**Step 2** — list that leaf category with the price ceiling:

```json
{
  "tool": "filter_category_products",
  "arguments": {
    "category_id": "dresses_042",
    "filters": [
      { "attribute": "color", "operator": "eq", "value": "red" },
      { "attribute": "price", "operator": "lt", "value": 50 }
    ],
    "limit": 100
  }
}
```

::: tip A price ceiling is enforced on the broad search too
`price lt 50` on a broad `semantic_product_search` call clips the results — every operator is enforced, category or not. The bound may be a number or a numeric string (`50` / `"50"` behave identically), and the comparison is numeric even when the attribute is stored as text. The one practical caveat is about the *values*: they must read as plain numbers — a decorated price like `"1 200 грн"` cannot be range-compared (next tip). Use the two-call category flow when you want a complete on-budget listing rather than the ranked top matches.
:::

::: tip Confirm `price` is actually a filterable attribute
`price` can be used as a range filter **only when your feed exposed it as a filterable attribute whose values read as numbers** — a numeric-typed attribute is ideal, and a text attribute holding plain numeric values (`"50"`) works too; decorated text (`"1 200 грн"`) does not. Verify per category with `get_category_attributes(category_id)` (is `price` in `attribute_names[]`?) and `get_category_attribute_values(category_id, "price")` (do the values look like plain numbers?). If the feed buried price somewhere unusual and it wasn't picked up as a filterable attribute, the assistant can still see it in the product details but **can't filter by it** — and a `price lt 50` filter silently matches nothing. Fixing this needs a full re-index (not a self-serve toggle): remove and re-add the integration in **Knowledge base → Products**, or contact [MyChatBot support](https://t.me/MyChatBot_Support_bot) with your integration.
:::

::: tip All ten operators, both tools, one contract
`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `contains`, `startswith`, and `endswith`
carry the same semantics on `semantic_product_search` and the category tools. `contains`
is case-insensitive and applies only to string fields; `startswith`/`endswith` are
case-sensitive and test the whole stored string; `ne` also excludes case-insensitive
list elements; ranges compare numerically only when the value is numeric. Full operator
reference: [Search tools](/search/search-tools).
:::

## Best practices

**Do**

* Decompose the shopper's sentence yourself — send free-text intent as `query`, and build hard constraints (color, size, in-stock) as structured `filters`. The search does **no** natural-language-to-filter extraction.
* Let the search pick categories — send a clean natural-language `query` and read `categories[]` back to guide follow-ups (and to get the `category_id` you need for range filters).
* Use `filters` for hard constraints (color, size, in-stock), not for describing intent — intent belongs in `query`.
* Use `name contains <fragment>` when the shopper knows only part of a product name; do not add wildcard characters.
* Set `enable_reranking=false` for pure SKU / code lookups; reranking adds latency with no benefit there.
* Reach for `get_products_by_ids` when you already know the exact IDs — skip the whole ranking flow.

**Don't**

* Don't range-filter attributes whose values don't read as plain numbers — confirm with `get_category_attribute_values` before relying on a price clip; decorated values like `"1 200 грн"` cannot be compared. Bounds may be numbers or numeric strings.
* Don't stuff SKUs into range filters — code-like attributes are excluded from the filterable attribute copy; put codes in `query` or use `get_products_by_ids`.
* Don't assume rank stability with generic product names — a common word in many product names can flip rank 1; filter by category or vendor first, or contact support to tune relevance.
* Don't paste an image expecting it to override a text-disambiguated answer — image matches are **blended**, not authoritative (see the rank-1 guard in [Image search](/search/image-search)).

## Test it

Regression-test with the known-hits pattern: define a query plus the product IDs you expect, run `semantic_product_search`, and assert the IDs appear. Introspect a catalog first with `get_category_attributes`, `get_category_attribute_values`, and `get_available_filters` (facets) to learn what's actually filterable — including whether `price` holds values that read as plain numbers. Full walkthrough: [Testing search](/search/testing).

## See also

* [Getting inventory in](/search/getting-inventory-in) — feeds, parsers, and how records land in the search index
* [Search tools reference](/search/search-tools) — every MCP tool, its arguments, and what it returns
* [Filters, attributes & params](/search/filters-and-params) — equality vs. range filters, and how relevance is tuned
* [Search prompts](/search/prompts) — prompt patterns that steer the search
* [Image search](/search/image-search) — image fingerprints, confidence tiers, and the rank-1 guard
* [Testing search](/search/testing) — known-hits regression tests and catalog introspection
* [Product (commerce) MCP](/search/product-mcp) — MCP URL, transport, and the full tool list
* [Troubleshooting & best practices](/search/troubleshooting) — stale attributes, orphan catalogs, and rank instability
