search backends

rag Mar 26, 2026 4 min read

if you already have your data in an external database, or wish to use vector/hybrid search, use one of our supported backends instead of the castform corpus.

all backends implement the same ChunkSource protocol, so switching backends doesn’t change the rest of your pipeline. the qa generation pipeline, default search environment, and training work identically regardless of which backend you choose.

backendClasssupported search modes
castformPostgresChunkSourcelexical
turbopufferTpufChunkSourcelexical, vector, hybrid
pineconePineconeChunkSourcevector
chromaChromaChunkSourcelexical, vector, hybrid

turbopuffer

turbopuffer supports lexical, vector, and hybrid search natively. you’ll need your own turbopuffer API key.

corpus setup

from castform.rag.corpus.turbopuffer.source import TpufChunkSource

# lexical-only (no embeddings needed)
source = TpufChunkSource(
    api_key="tpuf_...",
    namespace="my-docs",
)
source.populate_from_folder("./docs/")

to enable vector and hybrid search, index with embeddings. chunk sources take a synchronous embed_fn:

from openai import OpenAI

client = OpenAI()

def embed(texts: list[str]) -> list[list[float]]:
    response = client.embeddings.create(model="text-embedding-3-large", input=texts)
    return [item.embedding for item in response.data]

source = TpufChunkSource(
    api_key="tpuf_...",
    namespace="my-docs",
    embed_fn=embed,
)

search client

search clients take an async embed_fn; OpenAIEmbedder is built for that side (it is pickle-safe, so it can ship inside the environment bundle):

import os

from benchmax.auth import StaticBearerAuth
from castform.rag.corpus.embed import OpenAIEmbedder
from castform.rag.corpus.turbopuffer.search import TpufSearch

search = TpufSearch(
    namespace="my-docs",
    embed_fn=OpenAIEmbedder(
        model="text-embedding-3-large",
        base_url="https://api.openai.com/v1",
        auth=StaticBearerAuth(os.environ["OPENAI_API_KEY"]),
    ),
)

TpufSearch reads TPUF_API_KEY when it runs. Pass token_provider= only when the runtime supplies the key through another per-call source.

parameterdefaultdescription
namespacerequiredturbopuffer namespace
region"aws-us-east-1"turbopuffer region
embed_fnNoneembedding function; required for vector/hybrid
content_attrNonemetadata fields to concatenate as content
token_providerTPUF_API_KEYoptional per-call key provider

pinecone

pinecone provides managed vector search. supports vector search only. you’ll need your own pinecone API key.

corpus setup

from castform.rag.corpus.pinecone.source import PineconeChunkSource

source = PineconeChunkSource(
    api_key="pc_...",
    index_name="my-docs",
)
source.populate_from_folder("./docs/")

by default, pinecone uses its hosted inference API (multilingual-e5-large) for embeddings. to use a custom embedding function:

source = PineconeChunkSource(
    api_key="pc_...",
    index_name="my-docs",
    embed_fn=embedder,
)

for existing indexes with custom metadata field names, use field_mapping:

source = PineconeChunkSource(
    api_key="pc_...",
    index_name="existing-index",
    field_mapping={"content": "body_text", "file_path": "source_file"},
)

search client

from castform.rag.corpus.pinecone.search import PineconeSearch

search = PineconeSearch(
    index_name="my-docs",
)

PineconeSearch reads PINECONE_API_KEY when it runs. Pass token_provider= only when the runtime supplies the key through another per-call source.

parameterdefaultdescription
index_namerequiredpinecone index name
index_hostNonedirect host URL (skips index lookup)
namespace""pinecone namespace
embed_fnNonecustom embedding function
embed_model"multilingual-e5-large"hosted inference model (used when no embed_fn)
field_mappingNonemaps custom metadata field names for existing indexes
token_providerPINECONE_API_KEYoptional per-call key provider

chroma

chroma is an open-source embedding database you can self-host. supports vector, lexical (BM25), and hybrid search.

corpus setup

client-server mode is required for training since the model needs network access to the server during remote training.

from castform.rag.corpus.chroma.source import ChromaChunkSource

source = ChromaChunkSource(
    collection_name="my-docs",
    host="chroma.example.com",
    port=8000,
)
source.populate_from_folder("./docs/")

chroma auto-detects which search modes are available:

moderequiresdescription
vectoralways availableembedding-based similarity search
lexicalBM25 via Chroma Search APIkeyword matching
hybridboth vector + BM25reciprocal rank fusion

search client

from castform.rag.corpus.chroma.search import ChromaSearch

search = ChromaSearch(
    collection_name="my-docs",
    host="chroma.example.com",
    port=8000,
)
parameterdefaultdescription
collection_namerequiredchroma collection name
hostNoneself-hosted chroma server hostname (omit for chroma cloud)
port8000chroma server port
embed_fnNonecustom embedding function
enable_bm25Trueattempt to use BM25 if available on server
content_attrNonemetadata fields to concatenate as content