ragsage
API reference

Configuration

The frozen dataclasses you construct and pass in.

Every knob arrives in a frozen dataclass constructed by you, at your own composition root. The library reads no environment variables, here or anywhere: a config object whose values depend on the ambient process is untestable, un-injectable and silently different in production.

Pipeline policy

Tuning knobs for the two pipelines, kept out of the domain models.

These are policy, not data: how big chunks are, whether to contextualise, how many candidates to retrieve before reranking. They live in their own file so the façade signatures (ingest(source, scope, config), query(question, scope, options)) read cleanly and defaults live in exactly one place.

class ragsage.config.IngestionConfig(chunk_size: int = 512, chunk_overlap: int = 64, contextualize: bool = True)

[source]

Bases: object

How a document is turned into stored, retrievable chunks.

contextualize toggles Anthropic-style contextual retrieval — prepending an LLM-written sentence situating each chunk in its document before embedding. It is on by default (better retrieval) with a per-call off switch, matching the per-workspace toggle the backend exposes.

chunk_size : int = 512

chunk_overlap : int = 64

contextualize : bool = True

class ragsage.config.QueryOptions(retrieve_k: int = 20, rerank_k: int = 5, context_k: int = 3, min_score: float = 0.0)

[source]

Bases: object

How a question is answered: the retrieve -> rerank -> generate funnel.

Defaults follow the spec’s ratio — retrieve a wide candidate set, rerank with a cross-encoder, and hand only the top few to the model. min_score is the floor below which retrieval is treated as empty, which is what turns a weak match into an honest “not found” instead of a hallucination.

retrieve_k : int = 20

rerank_k : int = 5

context_k : int = 3

min_score : float = 0.0

Providers

Provider credentials and per-role model choices, as data the caller owns.

The library never reads os.environ. Every key, model id, and timeout arrives in this frozen dataclass, constructed by whoever embeds ragsage — a backend reading its own settings, a CLI reading flags, a test passing literals. That is the rule ADR-0002 states as “configuration is a frozen dataclass passed by the caller — never environment variables read at class-definition time”: a library that reaches for the environment is a library you cannot instantiate twice, test without monkeypatching, or reason about from the call site.

This config is deliberately small and provider-shaped. Ticket 05 folds it into one top-level RagSageConfig alongside the storage config; until then it stands alone so the provider adapters have exactly one place to be configured from.

type ragsage.providers.config.CallConfig = RunnableConfig

An opaque per-call configuration threaded, untouched, into the underlying LangChain call.

ragsage does not read it, build it, or depend on what is inside it. A consumer that wants its model calls attributed to a user — the backend binds Langfuse callbacks this way (its ADR-0003) — constructs one and hands it to the adapter; ragsage passes it straight through. That observability concern stays entirely outside this library: there is no Langfuse import anywhere in ragsage, and tracing proper reaches the engine through the injected Tracer/Span ports instead. The alias is LangChain’s own type because these adapters are LangChain clients, so naming it precisely costs nothing and keeps the pass- through type-checked.

class ragsage.providers.config.ProviderConfig(openai_api_key: str, voyage_api_key: str, embedding_model: str = 'voyage-3-large', rerank_model: str = 'rerank-2', contextualize_model: str = 'gpt-4o-mini', generation_model: str = 'gpt-4o', vision_model: str = 'gpt-4o', timeout_seconds: float = 60.0, context_max_tokens: int = 128, rewrite_max_tokens: int = 256, vision_max_tokens: int = 4096)

[source]

Bases: object

Keys, per-role model ids, and call limits for the Voyage/OpenAI adapters.

The roles are split because they are priced and shaped differently, not for symmetry: embedding and reranking go to Voyage; generation, contextualization, query rewriting, and vision go to OpenAI, with contextualization and rewriting sharing a cheap tier. embedding_model is pinned per corpus — changing it invalidates every stored vector — so it is named here rather than defaulted at each call site.

An empty API key is permitted and means “no calls to this provider will succeed”. The provider SDKs reject an empty key at construction, so build_provider_clients() substitutes a placeholder: the clients build and the process boots (dev, CI, and any harness that swaps fakes in at the ports never touches a model), while a real call fails with an auth error at request time rather than at import time.

openai_api_key : str

voyage_api_key : str

embedding_model : str = 'voyage-3-large'

rerank_model : str = 'rerank-2'

contextualize_model : str = 'gpt-4o-mini'

generation_model : str = 'gpt-4o'

vision_model : str = 'gpt-4o'

timeout_seconds : float = 60.0

context_max_tokens : int = 128

rewrite_max_tokens : int = 256

vision_max_tokens : int = 4096

Postgres

How the caller tells ragsage where its database is — and nothing more.

One frozen dataclass, constructed by the caller and validated on construction. It reads no environment variables, here or anywhere: ADR-0002 rules that out explicitly, and the research report singled out the class-definition-time get_env_value default as the specific anti-pattern to avoid. A config object whose values depend on the ambient process is untestable, un-injectable, and silently different in production; a config object whose values came from its caller is none of those. Wiring env to fields is the consumer’s job, at the consumer’s composition root.

Everything isolation-related is a field rather than a literal, because ragsage runs inside somebody else’s database. The app role, and the setting name Row-Level Security reads, both belong to whoever is deploying — the defaults here are only ragsage-owned names that will not collide with the consumer’s own.

class ragsage.storage.config.PostgresConfig(dsn: str, app_role: str = 'ragsage_app', isolation_variable: str = 'ragsage.namespace', embedding_dim: int = 1024, text_search_config: str = 'english', pool_size: int = 5, max_overflow: int = 5, pool_pre_ping: bool = True)

[source]

Bases: object

Everything ragsage needs to own a connection pool against one database.

  • Parameters:
    • dsn (str) – The connection URL ragsage connects with. It connects as the owner of its own table — owners bypass Row-Level Security, which is why every scoped operation drops into app_role first (see open_scoped_session()).
    • app_role (str) – The non-privileged Postgres role scoped work runs under. Not a login identity: it is a privilege context the owner switches into with SET LOCAL ROLE so RLS is actually enforced. Reaches interpolated SQL, so it is guarded as a plain identifier.
    • isolation_variable (str) – The run-time setting carrying Scope.namespace for the duration of a transaction, and the value ragsage’s RLS policy compares rows against. Configurable because two isolation variables coexist in the deployment this was extracted from — the consumer’s app.user_id over its own tables, and this one over ragsage’s — and they must not be confused. The default is ragsage-owned for exactly that reason.
    • embedding_dim (int) – The width of the embedding column. Fixed at table-creation time and pinned per corpus — changing it invalidates every stored vector, so it belongs in config rather than as a literal in the DDL. Must match whatever the configured embedder actually returns.
    • text_search_config (str) – The Postgres text-search configuration the generated lexical column is built with ('english' was hardcoded in the consuming backend). It has to be named explicitly because the two-argument form of to_tsvector is the IMMUTABLE one, and a STORED generated column accepts nothing else — the one-argument form depends on a session setting and is only STABLE. Changing it requires rebuilding the column.
    • pool_size – Pool sizing. Deliberately explicit and deliberately modest: ragsage’s pool is a second pool reaching the same Postgres as the consumer’s, and the pair have to fit inside max_connections together.
    • max_overflow – Pool sizing. Deliberately explicit and deliberately modest: ragsage’s pool is a second pool reaching the same Postgres as the consumer’s, and the pair have to fit inside max_connections together.
    • pool_pre_ping (bool) – Check a pooled connection is alive before handing it out. On by default — connections idle across a database restart or a proxy timeout otherwise fail the first query of an unlucky request.

dsn : str

app_role : str = 'ragsage_app'

isolation_variable : str = 'ragsage.namespace'

embedding_dim : int = 1024

text_search_config : str = 'english'

pool_size : int = 5

max_overflow : int = 5

pool_pre_ping : bool = True

On this page

Pipeline policyclass ragsage.config.IngestionConfig(chunk_size: int = 512, chunk_overlap: int = 64, contextualize: bool = True)chunk_size : int = 512chunk_overlap : int = 64contextualize : bool = Trueclass ragsage.config.QueryOptions(retrieve_k: int = 20, rerank_k: int = 5, context_k: int = 3, min_score: float = 0.0)retrieve_k : int = 20rerank_k : int = 5context_k : int = 3min_score : float = 0.0Providerstype ragsage.providers.config.CallConfig = RunnableConfigclass ragsage.providers.config.ProviderConfig(openai_api_key: str, voyage_api_key: str, embedding_model: str = 'voyage-3-large', rerank_model: str = 'rerank-2', contextualize_model: str = 'gpt-4o-mini', generation_model: str = 'gpt-4o', vision_model: str = 'gpt-4o', timeout_seconds: float = 60.0, context_max_tokens: int = 128, rewrite_max_tokens: int = 256, vision_max_tokens: int = 4096)openai_api_key : strvoyage_api_key : strembedding_model : str = 'voyage-3-large'rerank_model : str = 'rerank-2'contextualize_model : str = 'gpt-4o-mini'generation_model : str = 'gpt-4o'vision_model : str = 'gpt-4o'timeout_seconds : float = 60.0context_max_tokens : int = 128rewrite_max_tokens : int = 256vision_max_tokens : int = 4096Postgresclass ragsage.storage.config.PostgresConfig(dsn: str, app_role: str = 'ragsage_app', isolation_variable: str = 'ragsage.namespace', embedding_dim: int = 1024, text_search_config: str = 'english', pool_size: int = 5, max_overflow: int = 5, pool_pre_ping: bool = True)dsn : strapp_role : str = 'ragsage_app'isolation_variable : str = 'ragsage.namespace'embedding_dim : int = 1024text_search_config : str = 'english'pool_size : int = 5max_overflow : int = 5pool_pre_ping : bool = True