> 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/recipes/rag-over-a-codebase.md).

# RAG over a codebase

Run RAG over source code entirely on your machine — proprietary code never leaves it. Telys' lexical multigram embedder is strong at natural-language-to-code retrieval ("where do we validate the session token?" → the right function). The loop:

1. **Chunk** files into overlapping line windows.
2. **Add** each chunk with `add_texts`, carrying `path`, `start_line`, and `lang` as metadata.
3. **Retrieve** with `search_text`, optionally filtered by language or file.
4. **Assemble** the hits into a prompt for an LLM.

{% hint style="info" %}
Install with `pipx install telys`, sign in with `telys login`, then verify with `telys runtime verify` — the embedder needs the native kernel from the verified runtime. See [Install Telys](/start-here/install.md).
{% endhint %}

## Why lexical multigram for code

Dense sentence embedders are tuned for prose. Code is full of identifiers, snake\_case, camelCase, and punctuation that dense models tokenize poorly. `AlgentaMultigramEmbedder` concatenates L2-normalized unigram, bigram, and trigram hashes, so `parse invoice date` lands near `def parse_invoice_date(...)` on substring and token overlap. Use it for natural-language-to-code retrieval on-device. See [Lexical vs dense embeddings](/understand-telys/lexical-vs-dense.md) and [AlgentaMultigramEmbedder](/embeddings/multigram.md).

## Chunk and index

Chunk by fixed line windows with a small overlap, so a symbol split across a boundary stays retrievable. Store enough metadata to point back at the source.

{% code title="index\_codebase.py" lineNumbers="true" %}

```python
from pathlib import Path

from telys import Telys, Eq
from telys.embedding import AlgentaMultigramEmbedder

EXTS = {".py": "python", ".ts": "typescript", ".go": "go", ".rs": "rust", ".md": "markdown"}
WINDOW, OVERLAP = 40, 10  # lines per chunk, lines of overlap


def chunk_file(path: Path):
    lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
    step = WINDOW - OVERLAP
    for start in range(0, max(1, len(lines)), step):
        body = "\n".join(lines[start:start + WINDOW])
        if body.strip():
            yield start + 1, body  # 1-based start line


def build_index(repo: str, store: str = "./code-index", name: str = "code"):
    emb = AlgentaMultigramEmbedder()
    telys = Telys(store)
    col = telys.create_collection(
        name,
        dim=emb.profile.dimension,
        partition_by="repo",                 # one partition per repository
        embedder=emb,
        filter_columns=("path", "lang", "start_line", "text"),  # round-trip via with_metadata; filter on path/lang
    )

    texts, ids, metas = [], [], []
    for path in Path(repo).rglob("*"):
        lang = EXTS.get(path.suffix)
        if not lang or not path.is_file():
            continue
        rel = str(path.relative_to(repo))
        for start_line, body in chunk_file(path):
            texts.append(body)
            ids.append(f"{rel}:{start_line}")
            metas.append({
                "repo": name,
                "path": rel,
                "lang": lang,
                "start_line": start_line,
                "text": body,           # keep the chunk so we can rebuild context
            })

    # Insert in batches to bound memory, then commit once.
    for i in range(0, len(texts), 512):
        col.add_texts(texts[i:i + 512], ids[i:i + 512], metas[i:i + 512])
    col.save()
    print(f"indexed {len(texts)} chunks from {name}")
    return telys, col


if __name__ == "__main__":
    build_index(repo=".")
```

{% endcode %}

## Retrieve for a question

Retrieval is one `search_text` call. Pass `with_metadata=True` so each hit carries the `path`, `start_line`, and stored `text` you need to cite and build the prompt. Add `Eq` to scope to one language or file.

{% code title="retrieve.py" lineNumbers="true" %}

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


def retrieve(question: str, store: str = "./code-index", name: str = "code",
             top_k: int = 8, lang: str | None = None):
    emb = AlgentaMultigramEmbedder()
    col = Telys(store).open_collection(name, embedder=emb)
    res = col.search_text(
        question,
        top_k=top_k,
        where=Eq("lang", lang) if lang else None,
        with_metadata=True,
    )
    return [
        {"path": m["path"], "line": m["start_line"], "score": s, "text": m["text"]}
        for s, m in zip(res["scores"], res["metadata"])
    ]


def build_prompt(question: str, hits: list[dict]) -> str:
    blocks = [f"# {h['path']}:{h['line']}\n{h['text']}" for h in hits]
    context = "\n\n".join(blocks)
    return (
        "You are a codebase assistant. Answer using ONLY the snippets below, "
        "and cite each claim as path:line.\n\n"
        f"{context}\n\nQuestion: {question}"
    )


if __name__ == "__main__":
    hits = retrieve("where do we validate the session token?", lang="python")
    for h in hits:
        print(f"{h['score']:.3f}  {h['path']}:{h['line']}")
    prompt = build_prompt("where do we validate the session token?", hits)
    # Hand `prompt` to your LLM — see "Use with OpenAI & Anthropic".
```

{% endcode %}

{% hint style="warning" %}
`Eq` is the only filter Telys ships — no `And`/`Or`/`In`/range operators. To combine a language and a path constraint, fold both into one key at index time (for example `repo` = `scope_key(project, lang)`) and filter on that single key. See [Filter results](/working-with-data/filter-results.md) and [Filters (Eq)](/reference/filters.md).
{% endhint %}

## What you will see

Indexing prints the chunk count. Retrieval prints ranked source locations, the token-validation function highest:

```
indexed 1843 chunks from code
0.774  src/auth/session.py:41
0.520  src/auth/tokens.py:12
0.331  tests/test_session.py:88
```

`build_prompt` assembles those snippets into a citable context block for a chat model.

## Scaling to large repositories

For tens of thousands of chunks, build the IVF index after loading to keep searches fast at high recall. `build_ivf` is a no-op below its `min_rows` threshold, so calling it unconditionally is safe:

```python
col.build_ivf(min_rows=20000, target_recall=0.98)
col.save()
```

See [Build the IVF index](/working-with-data/build-ivf-index.md) and [Search internals (IVF)](/understand-telys/search-internals-ivf.md).

## Next steps

<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>Batch ingestion</strong></td><td>Ingest large corpora efficiently and commit once.</td><td><a href="/pages/omWERfy25S4dJh48glr5">/pages/omWERfy25S4dJh48glr5</a></td></tr><tr><td><strong>Use with OpenAI &#x26; Anthropic</strong></td><td>Feed retrieved snippets to a chat model.</td><td><a href="/pages/rqi1E0G8vx4MwH31ox8y">/pages/rqi1E0G8vx4MwH31ox8y</a></td></tr><tr><td><strong>Build the IVF index</strong></td><td>Keep large-collection search fast at high recall.</td><td><a href="/pages/vAtnT9NtTA0el7skR3vX">/pages/vAtnT9NtTA0el7skR3vX</a></td></tr></tbody></table>


---

# 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/recipes/rag-over-a-codebase.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.
