Skip to content

Reranking & retrieval

Retrieval in CiteNexus is a fixed, auditable pipeline: several retrievers each produce a ranked list, the lists are merged with Reciprocal Rank Fusion, and the fused head is optionally reranked by a cross-encoder you inject. Fusion lives in the core, so no plugin can bypass it — every candidate that reaches the grounding gate went through the same deterministic path.

  1. Retrieve. Each enabled retriever runs against the query and returns a ranked candidate list.
  2. Fuse. All lists are combined with RRF (k = 60): a candidate at zero-based rank contributes 1 / (k + rank + 1), merged by Evidence-Unit id.
  3. Rerank. The fused head is reordered by the injected reranker against the original query (a no-op passthrough if you inject none).
  4. Cut. The top top_k survive to grounding.

Retrievers and the signals that enable them

Section titled “Retrievers and the signals that enable them”

Which retrievers run is gated by the signals you declare on the client. Omit signals and all of them are built and queried.

Retriever Enabled by signal What it does
Vector (dense) embedding (and an embedder is set) nearest-neighbour over the vector store
Lexical (BM25) text BM25 / full-text search (Postgres tsvector or the in-core BM25)
Structure structure matches query terms to structure-node labels
Graph graph / community matches query terms to entity-node labels and returns that node’s Evidence Units (no edge walking — see graph)
Wiki wiki distilled-wiki navigation (slow path)
from citenexus import CiteNexus
# Lexical + vector only — no graph/wiki/structure built or queried.
rag = CiteNexus("./citenexus-data", embedder=..., generator=..., signals=["embedding", "text"])

By default CiteNexus applies an identity passthrough (fusion order is kept). Inject OpenAICompatibleReranker to reorder the fused head with a cross-encoder. It POSTs {model, query, documents} to {base_url}/rerank and reads the Cohere/Jina-shape results[] (relevance_score + index).

from citenexus import CiteNexus
from citenexus.retrieve import OpenAICompatibleReranker
reranker = OpenAICompatibleReranker(
base_url="http://localhost:11434/v1", # any endpoint exposing POST {base_url}/rerank
model="bge-reranker-v2-m3",
)
rag = CiteNexus(
"./citenexus-data",
embedder=..., generator=...,
reranker=reranker, # replaces the default identity passthrough
top_k=5,
)

top_k is how many candidates survive to grounding. Set a default on the client, or override it per call:

rag = CiteNexus("./citenexus-data", embedder=..., generator=..., top_k=5) # default
response = rag.ask("What notice does termination require?", k=8) # override for this call
docs = rag.retrieve("termination notice", k=20) # retrieval only, no generation

retrieve() returns the ranked candidates without generating an answer — the engine under ask(), and the escape hatch for wiring your own small model.

The fusion function is public and pure, if you fuse ranked lists yourself. It is one of the algorithms pinned byte-for-byte across the ports (conformance/cases/rrf.json), so the fused order is identical in each:

from citenexus.retrieve import rrf_fuse
fused = rrf_fuse([list_a, list_b], k=60) # lists of Candidate objects