Document navigation
search and retrieve rank chunks by relevance across the whole index. Document navigation does the opposite: it stays inside one source document and returns its chunks in reading order. Use it to read what comes before and after a hit, to read a section, or to find an exact term in a document that you already know is relevant.
A Mistral Search Index exposes four document navigation methods:
Why document navigation matters
Retrieval returns chunks, not answers. A chunk is a fragment of a document, cut at a fixed size. The evidence that a question needs is often spread around it:
- The answer continues past the hit. A chunk introduces a procedure, and the steps are in the next chunk.
- The hit lacks its context. A table row matches, but its header, its unit, or the paragraph that qualifies it is two chunks earlier.
- The key term is elsewhere in the document. The hit mentions a clause, but the definition of the clause is further down, and a new global search ranks other documents first.
Searching again with a rephrased query is a poor fix: it ranks the whole index again, returns chunks that the caller has already seen, and can't express "the paragraph after this one." Document navigation answers these needs directly and cheaply, because each call reads a known part of a known document.
Agentic Search builds on this approach. An agent runs a loop. It searches the index to find promising sources, then navigates inside them: it opens a hit, reads around it, or greps for a term. It searches again with exclude_ids to skip what it has already read, until it has enough evidence. The Agentic Search tools open, navigate, read, and grep are built on the methods on this page.
Positions in a document
These methods locate chunks by their source document and their position in that document. Every chunk in a SearchResult carries the following fields:
source_id: the document it belongs to.start_offsetandend_offset: its character span in that document, half-open ([start, end)).id: its own identifier, forget_chunk.
All four methods return a list of SearchResult objects in reading order (ascending start_offset). They don't rank results, so score is always 0.0. They only consider one chunk type, ChunkType.CONTENT by default. To read summaries or other chunk types instead, set content_type. If you pass an unknown source_id, the method raises SourceNotFoundError. Reaching the start or the end of a document returns an empty list.
get_chunk
get_chunk fetches one chunk by its id and returns it with its position, or None if no chunk has that ID. Use it when you only have the ID of a hit, for example because an agent passed the ID back in a tool call:
hit = await index.get_chunk(chunk_id)
if hit is not None:
print(hit.chunk.source_id, hit.chunk.start_offset, hit.chunk.end_offset)navigate returns the top_k chunks immediately after a position (NavigationDirection.NEXT) or immediately before it (NavigationDirection.PREVIOUS):
NEXTreturns the first chunks that start at or afterend_offset.PREVIOUSreturns the last chunks that end at or beforestart_offset.
To read the context around a hit, combine both directions:
from mistralai.search.toolkit.search import NavigationDirection
chunk = results[0].chunk
before = await index.navigate(
chunk.source_id, chunk.start_offset, chunk.end_offset,
NavigationDirection.PREVIOUS, top_k=2,
)
after = await index.navigate(
chunk.source_id, chunk.start_offset, chunk.end_offset,
NavigationDirection.NEXT, top_k=2,
)
context = "\n".join(r.chunk.content for r in [*before, results[0], *after])top_k defaults to 1. To continue through the document, call navigate again from the last chunk that you received.
read
read returns the chunks that fall entirely within a character range of a document, up to top_k (default 20). To read from the start or to the end of the document, pass None for the corresponding bound:
# The first chunks of the document, for example its title and introduction
intro = await index.read(source_id, None, 2000, top_k=5)
# A known section
section = await index.read(source_id, 12_000, 18_000)read doesn't include chunks that cross a bound. To include the chunks at the edges, widen the range or use navigate.
grep
grep finds the chunks of one document that contain an exact piece of text, without vector or keyword ranking. Use it when you already know the term that you need, such as a clause number, a product name, or an error code. It returns up to top_k matches (default 5), in reading order:
from mistralai.search.toolkit.search import GrepMode
matches = await index.grep(source_id, "termination for convenience")
matches = await index.grep(source_id, "notice period days", mode=GrepMode.TERM)mode | Matches chunks that contain |
|---|---|
GrepMode.PHRASE (default) | The pattern as written, as one piece of text |
GrepMode.TERM | Every space-separated term of the pattern, in any order |
Matching is case-insensitive and works on substrings. For example, "cat" matches "Category". A phrase must match the spacing of the text exactly. A blank pattern returns no chunks.
Example: From a hit to an answer
The following function finds the best hit for a question, reads two chunks on each side of it, and greps the same document for a term that the caller needs. It returns the passages in reading order, ready to pass to a model:
from mistralai.search.toolkit.mistral_search_index.retriever import (
KeywordRetriever,
NearestNeighbourRetriever,
RRFRetriever,
)
from mistralai.search.toolkit.search import NavigationDirection
async def gather_evidence(index, question: str, term: str) -> list[str]:
hits = await index.retrieve(
RRFRetriever(
top_k=1,
retrievers=[
KeywordRetriever(query=question, top_k=10),
NearestNeighbourRetriever(query=question, top_k=10),
],
)
)
if not hits:
return []
hit = hits[0].chunk
before = await index.navigate(
hit.source_id, hit.start_offset, hit.end_offset, NavigationDirection.PREVIOUS, top_k=2
)
after = await index.navigate(
hit.source_id, hit.start_offset, hit.end_offset, NavigationDirection.NEXT, top_k=2
)
matches = await index.grep(hit.source_id, term)
chunks = {r.chunk.id: r.chunk for r in [*before, hits[0], *after, *matches]}
return [c.content for c in sorted(chunks.values(), key=lambda c: c.start_offset)]An agent follows the same steps, one tool call at a time, and decides at each step whether it has enough evidence. To give these steps to an agent as tools, see Agentic Search.
See also
- Agentic Search: the agent loop and the MCP tools built on these methods.
- Retrieval: find the hits that navigation starts from.
- Document model: how sources, chunks, and offsets relate.
- Navigate, read, and grep: the REST endpoints behind these methods.