> For the complete documentation index, see [llms.txt](https://docs.telys.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.telys.ai/troubleshooting/search-and-recall.md).

# No results / recall

Two problems hide behind "search isn't working": **zero results** (nothing comes back) and **poor recall** (results come back, but miss the ones you expected). Zero results is almost always a scope, filter, or embedder wiring problem; poor recall is about embedder choice and the index. Work them in that order.

{% hint style="info" %}
Check the collection first with `len(col)` and `col.stats()`. A zero count is an ingest or persistence problem, not a search problem — see the "empty results" entries below.
{% endhint %}

## Empty results

<details>

<summary>Search returns nothing right after adding — data was never saved</summary>

`create_collection(...)` builds and caches the collection in memory but does **not** write to disk. `open_collection(...)` and `collections()` only see collections persisted with `save()`.

**Cause:** you added rows, then reopened the store in a new process without calling `save()`, so the reopened collection is empty (or doesn't exist).

**Fix:** save after ingest; the write to `collection.json` is the commit point.

```python
from telys import Telys
from telys.embedding import AlgentaMultigramEmbedder

emb = AlgentaMultigramEmbedder()
mem = Telys("./memory")
col = mem.create_collection("notes", dim=emb.profile.dimension,
                            partition_by="scope", embedder=emb)
col.add_texts(["first note", "second note"], ids=["n1", "n2"],
              metadata=[{"scope": "demo"}, {"scope": "demo"}])
col.save()                       # commit — id_map.npy then collection.json

# New process:
mem = Telys("./memory")
col = mem.open_collection("notes")
print(len(col))                  # 2, because it was saved
```

Within one process, the cached collection sees your data immediately; `save()` only matters across processes and restarts.

</details>

<details>

<summary>Search returns nothing — a <code>where</code> filter or partition excludes every row</summary>

Telys filters and partitions are strict equality. `Eq(column, value)` (or a single-key dict) is the **only** filter — no AND/OR/NOT or range operators. Rows live in the partition set by `partition_by`; a query sees only the partition and filter you scope it to.

**Cause:** the `where` value doesn't exactly match stored metadata, or you're searching a different partition than you wrote to.

**Fix:** drop the filter to confirm data exists, then re-apply the exact-match filter. List which ids are present with `ids()`.

```python
from telys import Eq

col.search_text("quarterly plan", top_k=5)                       # no filter — does anything come back?
col.search_text("quarterly plan", top_k=5, where=Eq("tenant_id", "acme"))
col.ids(where=Eq("tenant_id", "acme"))                           # exactly which ids match this filter
```

A list/tuple `partition_by` uses only its **first** element as the physical key — it is not composite. To scope by several fields, compose one key with `scope_key(...)`.

```python
from telys import scope_key

key = scope_key("acme", "user-42")     # single composite partition key
```

</details>

<details>

<summary><code>RuntimeError</code> on <code>search_text</code> / <code>add_texts</code> — no embedder attached</summary>

The `*_text` methods embed text before searching or adding, so they require an embedder. Raw-vector methods (`search`, `add`) don't.

**Cause:** the collection has no embedder — you created it without one, or opened it and no matching provider was registered to auto-attach.

**Fix:** attach an embedder at create/open time, or register a provider so `open_collection` can auto-attach it by `model_id` / `space_id`.

```python
from telys import Telys
from telys.embedding import AlgentaMultigramEmbedder

emb = AlgentaMultigramEmbedder()
mem = Telys("./memory")
col = mem.create_collection("notes", dim=emb.profile.dimension,
                            partition_by="scope", embedder=emb)
col.add_texts(["hello world"], ids=["a"],
              metadata=[{"scope": "demo"}])       # works — embedder present
```

With raw vectors only, use `add(...)` / `search(...)` and skip the embedder.

</details>

## Poor recall

<details>

<summary>Results come back, but the right ones are missing — wrong embedder for the task</summary>

The two built-in embedders are **lexical**, not dense. `AlgentaBigramEmbedder` and `AlgentaMultigramEmbedder` match on shared character n-grams — excellent for code, identifiers, and near-exact text, weaker for paraphrase or cross-lingual matches. For semantic paraphrase, use a dense model via `CallableEmbedder`.

**Cause:** the embedder's notion of similarity doesn't match your query intent.

**Fix:** pick the embedder that fits the workload. Collection and query must use the **same** space; you can't mix.

```python
from telys.embedding import AlgentaMultigramEmbedder, CallableEmbedder

lexical = AlgentaMultigramEmbedder()      # strong for code / identifiers / near-exact text
# dense = CallableEmbedder(fn, profile)   # bring your own model for semantic paraphrase
```

Next: [Choosing an embedder](/embeddings/choosing-an-embedder.md)

</details>

<details>

<summary>Recall drops as the collection grows — build the IVF index and tune</summary>

Small collections are searched exactly. As they grow, approximate (IVF) search trades a little recall for speed; if the index isn't built or `nprobe` is too low, recall suffers.

**Cause:** the collection is large enough for approximate search but the IVF index isn't built, or the recall target is too low.

**Fix:** raise `target_recall` per query, build the index explicitly, or run a tuning pass and apply it.

```python
# 1) Ask for more recall on a single query (local collections only):
col.search_text("payment retry logic", top_k=10, target_recall=0.98)

# 2) Build the IVF index once the collection is large:
col.build_ivf(min_rows=20000, target_recall=0.98)

# 3) Or let the tuner pick recall + nprobe, then persist it:
plan = col.tune()          # returns a TuningPlan
col.apply_tuning(plan)     # sets default_target_recall, crossover, per-partition IVF, nprobe
col.save()
```

`target_recall` is a local-collection feature; `RemoteCollection.search` does not accept it.

</details>

## Next steps

If search raises a dimension or length error instead of returning too little, the query width doesn't match the collection.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Dimension mismatches</strong></td><td>Vector width vs the collection and embedder profile.</td><td><a href="/pages/cLf0cG2Rm2TZeyApGR9I">/pages/cLf0cG2Rm2TZeyApGR9I</a></td></tr><tr><td><strong>Build the IVF index</strong></td><td>When and how to build the approximate index.</td><td><a href="/pages/vAtnT9NtTA0el7skR3vX">/pages/vAtnT9NtTA0el7skR3vX</a></td></tr></tbody></table>

Next: [Embedding dimension mismatches](/troubleshooting/dimension-mismatches.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.telys.ai/troubleshooting/search-and-recall.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
