RAGSpine
Architecture

Package Layout

The deep, domain-grouped src/ragspine map — what lives in each of the nine domains, the dependency direction between them, and how the core stays SDK-free.

RAGSpine uses a deep, domain-grouped layout: organize by domain/feature, never by technical layer, so the folder path locates a file before you read its name. A package splits the moment it holds a second responsibility. There are nine top-level domains under src/ragspine/, each with a thin __init__.py (a docstring + a Submodules: index) that lazy-loads its children.

The tree

The same map, annotated (from the project's README.md):

src/ragspine/
├── common/         cross-cutting: company profile, sensitivity, glossary, observability
├── extraction/     documents → a frozen StyledGrid intermediate representation (IR)
│   ├── extractors/   xlsx / pptx / pdf (digital + scanned/OCR), style- & color-aware
│   ├── routing/      per-page PDF triage (digital vs scanned vs export)
│   ├── color/        controlled color-semantics registry
│   └── verification/ dual-channel cross-check → review queue
├── ingestion/      IR/text → stores
│   ├── structured/   fact ingestion + batch manifest ledger (idempotent)
│   ├── narrative/    document chunk ingestion + extraction
│   └── review/       human review-queue state machine (SME)
├── storage/        fact store (numeric) + chunk store (narrative), sqlite, full lineage
├── retrieval/      narrative RAG
│   ├── chunking/     paragraph-granular chunker + versioned chunk store
│   ├── lexical/      Okapi BM25 (CJK uni+bigram) + RRF fusion
│   ├── vector/       injectable embedding backends (default: none = pure BM25)
│   ├── rerank/       LLM listwise reranker (RRF-fallback)
│   └── link/         adapter wiring retrieval into the agent (strips RESTRICTED at exit)
├── agent/          intent parsing, clarification gateway, tool-use loop, llm provider
├── eval/           QA + extraction evaluation harnesses with baseline gates
├── service/        FastAPI app, RQ task queue, ingestion jobs, FAQ short-circuit cache
└── pipeline/       static topology export (.topology() → Mermaid / DOT / JSON)

What lives where

Dependency direction

The layering is strict and acyclic — common (and storage) sit at the bottom depending on nothing first-party; agent, extraction, and retrieval build on them; ingestion, eval, and service are coordinators; pipeline is an isolated leaf everyone may delegate into but which imports no one.

       service           (composition root — imports almost everything)
      /   |   \
ingestion eval  …
   |    \   \
extraction agent  retrieval ──► pipeline   (retrieval delegates .topology() into pipeline)
   \      |   \      /
    \    storage    /
     \     |       /
        common              (leaf — depends on nothing first-party)

Prop

Type

The core imports zero SDKs. Every external dependency enters through a Protocol, and the only files touching heavy SDKs (anthropic / openai / fastapi / redis / docling) are the edge adapters. The anthropic import is lazy — inside AnthropicProvider.__init__, not at module top — and submodules load lazily (PEP 562). A top-level import ragspine eagerly loads no domain and pulls no third-party SDK, so the engine runs fully offline with the deterministic MockProvider.

On this page