Connecting your RAG

RAG Connector

A RAG Connector is how Pelorus reads a retrieval system it does not own. You implement it against your RAG; Pelorus queries it, enumerates its corpus, and builds the curated Extract layer on top of what comes back.

Which direction are you going?

Two different things in these docs are called connectors, and they point opposite ways. It is worth getting straight before you write any code.

  • Connectors — how application and agent traffic reaches Pelorus. The Python client and the MCP server. Use those when you are the caller.
  • RAG Connector (this page) — how Pelorus reaches your retrieval system. Use this when you have a RAG pipeline and you want Pelorus to sit on top of it.

The contract

A connector is a read interface onto a black box. It answers two required questions — retrieve for this query, and enumerate the corpus — and nothing in the contract creates, clears, or writes a corpus. That is deliberate: a host reconstructs a connector from stored configuration on every run, so a connector that could destroy the corpus under test would carry that power into every evaluation.

from rag_connector import ChunkRecord, RagPipeline, RetrievedChunk


class MyRagConnector(RagPipeline):
    name = "my-rag"

    def query(self, text: str, top_k: int = 5) -> list[RetrievedChunk]:
        """Retrieve best-first. score is canonical: higher is always better."""
        ...

    def list_chunks(self, *, cursor=None, limit=1000):
        """One page of the corpus, plus the cursor for the next page.

        Prefer implementing this over pull_all_chunks: bulk pull, by-id reads
        and sampling all derive from paging cheaply, never the reverse.
        """
        ...

Every chunk you return carries a stable chunk_id, its doc_id and human-readable source_file, and doc_chunk_index — the ordinal within its document, which is what makes neighbor expansion possible. A corpus-wide ordinal (global_index) is optional: pass None and the host assigns ordinals at ingest.

Everything else is an optional, declared capability: by-id reads, reading the vectors your index actually holds, describing your embedding space, embedding a query, index introspection, publishing content, answer generation. Implement what your system can honestly support. The one rule that matters more than completeness: never report a capability you cannot deliver, because a caller will then run a check that silently tests nothing.

Validate it before you wire anything

Two validators ship, and they answer different questions. Run them in this order — neither needs a host application, a corpus snapshot, or an LLM key.

Is this a valid connector at all? The shared library's validator exercises the whole contract: the metadata contract, id stability across pulls, whether query() returns ids in the same format pull_all_chunks() produces, score direction, determinism.

python -m rag_connector.validate \
    --import my_pkg.my_rag:MyRagConnector \
    --query "a question your corpus can answer"

How much of Pelorus works against it? The Pelorus-side validator answers a different question, and the answer is graded rather than pass/fail — because Pelorus degrades honestly instead of refusing. A connector that can only retrieve chunks is usable: Pelorus serves the chunk tier and reports the curated tiers as unavailable. The report names which features are on, which are off, and what to implement to turn each one on.

python -m pelorus.validate_connector \
    --import my_pkg.my_rag:MyRagConnector \
    --query "a question your corpus can answer"

Both print the same report format, so a connector working in one place reads the same way in the other. Exit codes match too: 0 usable, 1 not usable, 2 the validator crashed.

The geometry Pelorus needs

Pelorus makes decisions at specific numbers — an Extract serves as an identity match at 0.98, a chunk supplements at 0.60. Those numbers were measured in cosine similarity over L2-normalized vectors, and they are only meaningful in those units. A system that scores differently is not slightly off; its numbers mean something else while looking completely ordinary.

So Pelorus asks your connector to describe its space, and refuses its calibrated features when what you report is incompatible — declared unnormalized vectors, a non-cosine metric, a dimension that does not match the index being searched. Chunk retrieval keeps working regardless: it needs no calibrated threshold.

Two things are worth knowing while you implement. Unverifiable is not the same as incompatible — if you simply do not report your normalization, you are not refused for that alone, but a space Pelorus cannot verify is one whose thresholds it cannot vouch for, and it will say so. And if you can expose the vectors your index actually holds, do: it lets Pelorus recompute a cosine itself and confirm your reported scores really are cosine rather than a rank-preserving rescale of it. A rescale ranks perfectly while quietly moving every calibrated boundary.

Know Your Embedding Model covers the model side of this in depth, including which retrieval setups need attention (score fusion, cross-encoder reranking, quantized indexes, truncated embeddings).

What works today

The contract is real; operator selection is not wired yet. The interface, both validators, and the compatibility checks all work today, and Pelorus drives a third-party connector through them in its own test suite. What the developer preview does not have is a way for you to say “use my Pinecone” — Pelorus runs its own bundled stack behind the same interface, and connector selection is the next step on the roadmap.

So this page is worth following now if you are writing a connector and want it ready — and the validators will tell you honestly where you stand. It is not yet a switch you can flip in configuration.

The contract itself lives in a separate package, rag-connector, shared with RAGauge, the evaluation harness. One vocabulary, two products: the library standardizes the retrieval shape and how a connector declares what it supports, and each product keeps its own validator for what it additionally requires. A connector you write against the contract works with both.