AI Search (Semantic Search)
AI search adds an embedding-based retrieval lane alongside BM25. Long-form content stays retrievable when the wording differs from the query, and hybrid mode combines both lanes for the best of each.
1. Pick the fields to embed
- Settings → Search → AI Search → + Add more.
- Pick the
(node type, field)pairs to embed. - For each field, decide on chunking (below).
Good defaults:
| Field shape | Embed? | Chunk? |
|---|---|---|
| Titles, summaries (< 200 tokens) | Yes | No |
| Bodies, content (200–4000 tokens) | Yes | Yes |
| Long-form articles (> 4000 tokens) | Yes | Yes, with overlap |
| Identifiers, SKUs, codes | No | — |
Don't embed identifiers — they have no semantic content and you'll burn embedding budget for nothing.
2. Configure chunking
For anything longer than the embedding model's context window, enable Chunk Text. The workspace splits the field into overlapping windows and embeds each one independently. Hits return the parent node, deduplicated.
Knobs:
| Knob | Effect |
|---|---|
| Chunk size | Tokens per chunk. Default ≈ 512; shorter for tighter retrieval, longer for context. |
| Overlap | Tokens shared between adjacent chunks. 20–50 tokens prevents cutoff at sentence boundaries. |
| Min chunk size | Skip chunks shorter than this — trailing fragments often hurt precision. |
3. Similarity cutoffs
Two cutoffs control how strict matching is:
- Added cutoff — minimum cosine similarity for an embedding hit to enter the result set.
- Rerank cutoff — minimum similarity for an item to re-rank an existing BM25 hit.
Defaults are usually fine. Tune up if the LLM hallucinates over weak retrievals (raise both); tune down if relevant items are missing (lower both).
4. Semantic reranker
The reranker is a second pass that uses a cross-encoder model to re-score the top-N hits from hybrid retrieval. Enable when:
- Precision@10 matters more than throughput.
- Your corpus is large enough that BM25 + embeddings still return some near-misses.
It's an extra call per query; budget accordingly.
From code
var req = SearchRequest.For("laptop overheating");
req.BeforeTypesFacet = new HashSet<string> { N.SupportCase.Type };
req.HybridSearch = true; // Use BM25 + embeddings together
req.SemanticRerank = true; // Apply the cross-encoder reranker
var query = await Graph.CreateSearchAsUserAsync(req, CurrentUser, CancellationToken);
return query.Take(10).EmitWithScores();
For pure semantic retrieval (no BM25), use:
return (await Q().StartAtSimilarTextAsync("laptop overheating",
nodeTypes: new[] { N.SupportCase.Type }))
.Take(10)
.EmitWithScores();
Budget and performance
- Embedding cost scales with the field corpus size on first index, then with the change rate.
- Pause embedding generation under Settings → Search Index before a large backfill; resume after.
- Embedding latency on query is fixed and small (~10 ms). Hybrid mode is fast; reranking adds 50–200 ms.
LLM Search: what the assistant may search
The settings above configure the retrieval lane. A separate tab in the same place — Settings → Search →
LLM Search, next to AI Search — configures what the AI assistant's built-in
search tool is allowed to reach. The two are
independent: embedding a field does not expose it to the assistant, and exposing a data type to the
assistant does not embed anything.
The page has two tabs:
- Data Types — one row per node type, toggled between Searchable by AI and Not searchable by AI. This is a ceiling, not a grant: a user still only searches the types they can see.
- Filters — one row per filter the interpreter may apply, toggled between Available to AI and Not available to AI. Each row shows the name the model sees in bold, what kind of filter it is (field value, date range, data type, related item), and the back-end facet key behind it. Which filters are listed depends on the data types enabled in the first tab, and turning a data type off drops its filters rather than leaving them as dead configuration.
While nothing is configured, the built-in defaults apply: the _FileEntry and _WebPage data types, and
the file-kind, file-extension, source and time filters.
Both lists are part of the general configuration, so they travel with configuration export and import. A configuration file written before the setting existed omits them, and the importer leaves the workspace as configured; an explicitly empty list imports as "back on the built-in defaults".