RagMeUp.Client 0.4.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package RagMeUp.Client --version 0.4.0
                    
NuGet\Install-Package RagMeUp.Client -Version 0.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RagMeUp.Client" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RagMeUp.Client" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="RagMeUp.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RagMeUp.Client --version 0.4.0
                    
#r "nuget: RagMeUp.Client, 0.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RagMeUp.Client@0.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RagMeUp.Client&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=RagMeUp.Client&version=0.4.0
                    
Install as a Cake Tool

RAG engine

Document RAG over Postgres + pgvector. The engine owns ingestion, embedding, and reranking; the app owns filtered retrieval as direct SQL on tables it owns.

service/   Python FastAPI engine (`ragmeup` package): /embed, /documents, /rerank
client/    C# read-path library (RagMeUp.Client): rank, build passages, rerank, read spans
db/        schema.sql — documents + chunks tables, HNSW + GIN indexes

Each half carries its own Azure DevOps pipeline (*/azure-pipelines.yml) and releases independently — the client to nuget.org, the engine as a container image. See CI/CD.

Database

psql "$DATABASE_URL" -f db/schema.sql   # needs pgvector >= 0.7.0

Service

Models live on a volume mounted at /models, not baked into the image, so code changes rebuild and push only small layers. A one-shot seed populates the volume.

cd service
DATABASE_URL=postgres://… docker compose up --build

Compose runs model-seed first (downloads weights to the models volume), then starts engine offline against it. On OpenShift, the equivalent is a PVC at /models seeded by an init Job (or initContainer) running python -m ragmeup.download_models.

Local dev without containers: pip install -r requirements.txt then DATABASE_URL=… uvicorn ragmeup.main:app (from service/). Models download from the hub on first run.

Image flavours

One Dockerfile, two bases, selected by FLAVOR — the shared tail (deps, app, entrypoint) is written once so the flavours cannot drift:

docker build .                          # cuda, the default — GPU host (~8 GB)
docker build --build-arg FLAVOR=cpu .   # cpu — CI and a GPU-less VPS (~2.4 GB)

The CPU flavour installs torch from PyTorch's CPU wheel index; a plain pip install torch would pull the CUDA build and ~2.5 GB of nvidia-* wheels that a CPU host can never use.

Configuring a container

Everything is environment variables — the image has no config file. Only DATABASE_URL is required; every other knob has a working default, and the model paths are already set in the image.

docker run -d --name rag-engine \
  -e DATABASE_URL=postgres://user:pass@db:5432/ragdb \
  -e RAG_TORCH_THREADS=4 \
  -v models:/models -e HF_HUB_OFFLINE=1 -e TRANSFORMERS_OFFLINE=1 \
  -p 127.0.0.1:8000:8000 \
  ghcr.io/jgauffin/ragmeup-engine:latest
Variable Default Effect
DATABASE_URL required unless RAG_TARGETS is set Postgres+pgvector for the engine's own (privileged) ingestion pool. Also the default ingestion target — the database /documents writes to when a request names none.
RAG_TARGETS unset JSON {name: dsn}, inline or a path to a file, naming the databases one engine may ingest into. Lets several applications share one loaded set of models — see sharing one engine.
RAG_TARGET_POOL_MIN / RAG_TARGET_POOL_MAX 1 / 4 Connections held per target. The ceiling is unchanged from before; the floor is 1 so an idle target costs one connection, not four.
HF_HUB_OFFLINE, TRANSFORMERS_OFFLINE unset Set to 1 when /models is seeded, so nothing ever reaches the hub at runtime.
BGE_M3_PATH /models/bge-m3 Embedder weights. Set in the image; override only to relocate the volume.
RERANKER_PATH /models/bge-reranker-v2-m3 Default reranker weights. Likewise.
RERANKER_CROSS_ENCODER_PATH /models/ms-marco-MiniLM-L6-v2 Weights for the cross-encoder backend. Set in the image and seeded into the volume, so that backend works under HF_HUB_OFFLINE=1 too. Outside a container it falls back to the hub id.
RAG_CHUNK_STRATEGY whitespace windows recursive / structure-aware selects the separator-hierarchy splitter — recommended for markdown corpora, whose structure the default windows are blind to. Changing it only affects newly ingested documents.
RAG_EMBED_DEVICE torch's choice e.g. cuda:0, cpu. Not a model selector: the embedding model is fixed by the schema's column dimensions — swapping it is a re-index event.
RAG_RERANKER_BACKEND multilingual bge cross-encoder selects the ~22M-param English-only model — a different quality/latency point, much cheaper on CPU. berget moves reranking to a hosted API, leaving nothing resident.
RAG_RERANKER_BY_LANGUAGE unset Per-language override, e.g. en:cross-encoder,sv:berget.
RAG_BERGET_API_KEY (or BERGET_API_KEY) unset Required by the berget backend. Also RAG_BERGET_MODEL, RAG_BERGET_BASE_URL, RAG_BERGET_TIMEOUT, RAG_BERGET_RETRIES — see reranking strategies.
RAG_RERANK_DEVICE torch's choice Device for the reranker specifically.
RAG_RERANK_MAX_LENGTH 512 Tokens read per (query, passage) pair; the tail is silently dropped. Near-linear on latency — the bluntest dial there is.
RAG_RERANK_QUANTIZE off 1/true enables int8 dynamic quantization: ~2.8x faster on CPU, but ~1.1 GB more resident memory. Speed for memory, not free.
RAG_TORCH_THREADS every logical core Cap intra-op threads. Set it to the physical core count on a VPS — see below.
RAG_ENRICHERS none (no-op) contextual or extraction. Both call an LLM per document at ingest, so cost is opt-in.
ENRICH_LLM_BASE_URL OpenAI Any OpenAI-compatible endpoint (vLLM, Ollama, LM Studio).
ENRICH_LLM_API_KEY not-needed Required for real OpenAI; self-hosted endpoints usually ignore it.
ENRICH_LLM_MODEL gpt-4o-mini Model used by the enrichers.
RAG_EXTRACTION_PROMPT built-in template Inline template, or a path to one, for the extraction enricher.
RAG_EXTRACTION_FIELDS unset JSON {name: type} map. Set, only declared and correctly-typed keys survive — which is what keeps the jsonb @> filter reliable.

Two of these are not really optional in production:

  • Publish on loopback, not 0.0.0.0. No endpoint is authenticated and /documents writes with a privileged connection that bypasses your RLS by design. The engine is a trusted internal service — see deployment: trust boundary & scaling.
  • Set RAG_TORCH_THREADS. Unset, torch claims every logical core, which over-subscribes SMT pairs (measured: 8 → 16 threads at 0.98x, i.e. slower), and concurrent /rerank calls then fight for the whole machine.

For a Linux VPS there are two ready-made deployments. service/deploy/vps/ is the recommended one — Podman quadlets with unattended, health-gated auto-update, and the engine on an internal network with no published port at all; see hosting the engine on a Linux VPS. service/docker-compose.vps.yml is the compose equivalent for a host without Podman: it pulls the published image, reserves no GPU, binds loopback, and reads the knobs above from a .env file.

The container also serves GET /healthz — cheap, dependency-free, and 200 once the models are resident. Both deployments gate their health check on it.

Client

The app builds an NpgsqlDataSource with UseVector() and points HttpClient.BaseAddress at the engine. Filters live in the app's own SQL, which produces the candidate chunk-id set passed to Rank.

var q        = await rag.Embed(query);            // engine /embed
long[] ids   = await app.FilteredCandidates(...); // app's WHERE: RLS, geo, joins
var matches  = await rag.Rank(conn, q, query, ids, k: 50); // same `query` as embedded
var passages = rag.BuildPassages(matches);
var top      = await rag.Rerank(query, passages, topK: 5);
var spans    = await rag.ReadSpans(conn, top);    // clean original text for /answer

Methods drop the Async suffix — the whole client is async, so it adds nothing.

Tests

Both sides isolate the expensive boundaries (the GPU models, the database) so the logic around them is tested directly, and a thin layer of integration tests exercises the real SQL against Postgres+pgvector.

A throwaway pgvector instance for the integration tests:

docker run -d --name rag-test-pg -e POSTGRES_PASSWORD=test -e POSTGRES_DB=ragtest \
  -p 55432:5432 pgvector/pgvector:pg16

Service (service/) — unit tests need no model and no DB (the model boundary is mocked); the /documents integration test needs the DB above.

cd service
python -m venv .venv && .venv/Scripts/pip install -r requirements-dev.txt
.venv/Scripts/python -m pytest                       # unit only (integration auto-skips)
RAG_TEST_DATABASE_URL=postgres://postgres:test@localhost:55432/ragtest \
  .venv/Scripts/python -m pytest                     # + the /documents integration test

Client (client/) — unit tests are pure (no DB/HTTP). The integration tests use RAG_TEST_DATABASE_URL if set, otherwise spin a pgvector container via Testcontainers; they auto-skip when neither is available.

cd client
dotnet test tests/RagMeUp.Client.Tests               # unit
RAG_TEST_DATABASE_URL=postgres://… \
  dotnet test tests/RagMeUp.Client.IntegrationTests  # rank/read-path vs real pgvector

Both suites honour the same RAG_TEST_DATABASE_URL, so one local pgvector serves both. The integration tests reset their tables on start, so point it at a scratch database, not one with data you care about.

Retrieval quality (client/tests/RagMeUp.Client.EvalTests) — a labelled corpus in eval/ measured through the real read path. Needs a running engine with real weights as well as the database, and skips without one. Reports recall / MRR / nDCG rather than asserting a bar; the assertions are comparative, proving the harness can see a deliberate sabotage. See measuring retrieval quality.

RAG_EVAL_ENGINE_URL=http://127.0.0.1:8000 RAG_TEST_DATABASE_URL=postgres://… \
  dotnet test tests/RagMeUp.Client.EvalTests

The engine's own DATABASE_URL must point at that same database — it ingests, the harness reads. Do not run this concurrently with the integration suite against one database: both truncate the corpus tables. Set RAG_EVAL_DATABASE_URL to separate them.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.0 156 8/18/2026 0.5.0 is deprecated because it is no longer maintained.
0.4.0 159 8/7/2026
0.3.0 113 8/7/2026
0.2.0 139 8/4/2026
0.1.0 130 8/2/2026

0.4.0 — engine-derived structure moves into a reserved metadata namespace.

     No API change from 0.3.0: no member signature moved, so this is a drop-in
     recompile. The break is in the database contract.

     UPGRADE FROM 0.3.0 — do these together:
     1. Update the engine image at the same time as this package. A 0.3.0 client
        selects the parent_start/parent_end columns and fails outright against a
        database created from the current schema.sql; a 0.4.0 client against a
        0.3.0 database just sees no structure and falls back.
     2. Re-ingest documents whose heading paths or section spans you rely on.
        0.3.0 wrote them to a top-level `heading_path` key and to two columns;
        0.4.0 reads chunks.metadata->'rag'. Old rows are not migrated, they
        simply read as "no structure" until re-ingested.
     3. Optionally drop the now-unused columns:
        alter table chunks drop column parent_start, drop column parent_end;
        Leaving them is harmless — nothing reads or writes them.

     WHY, because it is a reversal: 0.3.0 added columns to app-owned tables and
     depended on them in the same release. With one engine serving several
     applications' databases that makes every engine upgrade a synchronised DDL
     event across independently-operated systems — and with AutoUpdate=registry,
     an unattended image pull becomes an outage for whichever app had not
     migrated. Optional engine data now lives in chunks.metadata->'rag', which
     costs no DDL and is inert to anyone not reading it. The rules are in
     docs/architecture/schema-evolution.md; what applications may rely on is in
     docs/manual/schema-compatibility.md.

     METADATA SHAPE: your own keys are untouched — the engine only ever writes
     inside `rag`. But if you compare chunk metadata for EQUALITY rather than
     containment, or enumerate its keys, `rag` now appears on structured
     documents. Use `metadata @> …`, or `metadata - 'rag'` to recover exactly
     what you wrote.

     --- 0.3.0 (previous) ---
     Reranking granularity, engine-owned length guard, small-to-big reads.
     BREAKING: Rerank(...) and ReadSpans(...) take a new optional parameter
     before CancellationToken; pass ct as a named argument. Rerank defaults to
     ScoreGranularity.Chunk (scores each member chunk, folds with max) instead of
     scoring the welded run. ConfidenceDiagnosis gained NoDiscrimination.
     RerankCalibration.UsablePassageChars and RerankOutcome.Diagnostics replace
     the passage-length constant consumers were hard-coding.