Ingestion¶
varagity.ingest.discovery
¶
Corpus discovery: scan the docs directory and bucket files by parser.
Bucketing (spec §9.1, spec_v2 §8.1): plain-text formats
(.txt/.md/.rst) share one extraction path (text_like);
.pdf needs Docling with the OCR fallback (pdf); the Office families
(incl. macro/template variants), .csv, and OpenDocument share the
no-OCR Docling office path (office); .html/.htm/.xhtml
likewise (web); bitmap images are OCR-only (image). Extensions
outside the ALLOWED_EXTENSIONS whitelist are ignored (logged at DEBUG).
The reserved <DOCS_PATH>/varagity-exports/ folder is skipped whole
(ADR-019): it holds backup bundles, never sources, and the rule must hold
even if an ALLOWED_EXTENSIONS edit ever admitted a bundle's suffix. The
CLI's ingest runs this same discovery, so it skips the folder too.
Buckets
dataclass
¶
Discovered corpus files, grouped by extraction path.
Attributes:
| Name | Type | Description |
|---|---|---|
text_like |
list[Path]
|
|
pdf |
list[Path]
|
|
office |
list[Path]
|
Office/OpenDocument/CSV files (the |
web |
list[Path]
|
|
image |
list[Path]
|
Bitmap images ( |
Source code in varagity/ingest/discovery.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
total
property
¶
Total number of bucketed files.
Returns:
| Type | Description |
|---|---|
int
|
The combined size of all buckets. |
by_bucket()
¶
List every bucket with its name, in a stable order.
The single enumeration point renderers iterate, so a future bucket added here appears everywhere without caller edits.
Returns:
| Type | Description |
|---|---|
tuple[tuple[str, list[Path]], ...]
|
|
Source code in varagity/ingest/discovery.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
discover_documents(docs_path, verbose=None)
¶
Recursively scan docs_path and bucket ingestable files.
A missing directory is not an error: the app re-scans on every start (spec §9.1), so an empty/unmounted corpus logs a warning and yields empty buckets rather than crashing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
docs_path
|
str
|
Directory to scan (usually |
required |
verbose
|
int | None
|
Console verbosity (0–2); defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
Buckets
|
The bucketed file paths, each bucket sorted for determinism. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in varagity/ingest/discovery.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
varagity.ingest.parsers.base
¶
Parser protocol and registry (the spec §5.1 registry pattern).
Each parser module defines one implementation decorated with
@register("name"); callers resolve implementations with
:func:get_parser and never import concrete parsers directly.
Parser
¶
Bases: Protocol
Interface every parser implements.
Source code in varagity/ingest/parsers/base.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
extract(path, verbose=None)
¶
Extract text and provenance from a source file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The file to parse. |
required |
verbose
|
int | None
|
Console verbosity (0–2); defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
RawDocument
|
The extracted document. |
Source code in varagity/ingest/parsers/base.py
32 33 34 35 36 37 38 39 40 41 42 43 | |
RawDocument
dataclass
¶
A parsed source document, before chunking.
Attributes:
| Name | Type | Description |
|---|---|---|
text |
str
|
The full extracted text (normalized by the parser). |
source_meta |
dict[str, Any]
|
Provenance seeded into every chunk's metadata:
|
Source code in varagity/ingest/parsers/base.py
14 15 16 17 18 19 20 21 22 23 24 25 26 | |
get_parser(name)
¶
Look up a registered parser by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Registry key (e.g. |
required |
Returns:
| Type | Description |
|---|---|
Parser
|
The registered parser instance. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no parser is registered under |
Source code in varagity/ingest/parsers/base.py
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
register(name)
¶
Class decorator registering a parser instance under name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Registry key (matches the discovery bucket, e.g. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[T], T]
|
The decorator, which instantiates and registers the class unchanged. |
Source code in varagity/ingest/parsers/base.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
varagity.ingest.parsers.text
¶
Plain-text parser for .txt and .md (spec §9.2).
Reads UTF-8, normalizes newlines, and repairs hyphen-broken words via
:func:remove_hyphen_space (carried over from the reference implementation).
TextParser
¶
Parser for the text_like bucket (.txt / .md).
Source code in varagity/ingest/parsers/text.py
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | |
extract(path, verbose=None)
¶
Read and normalize a text file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The |
required |
verbose
|
int | None
|
Console verbosity (0–2); accepted for interface uniformity — this parser renders nothing itself. |
None
|
Returns:
| Type | Description |
|---|---|
RawDocument
|
The extracted document; |
Raises:
| Type | Description |
|---|---|
UnicodeDecodeError
|
If the file is not valid UTF-8. |
OSError
|
If the file cannot be read. |
Source code in varagity/ingest/parsers/text.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | |
remove_hyphen_space(text)
¶
Rejoin words split by a hyphen at a line break or before a space.
The reference implementation's fix for extraction artifacts like
frame-\nwork and frame- work (both → framework).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
Text with possible hyphenation artifacts (newlines already
normalized to |
required |
Returns:
| Type | Description |
|---|---|
str
|
The text with split words rejoined. |
Source code in varagity/ingest/parsers/text.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
varagity.ingest.parsers.docling_base
¶
Shared Docling conversion core for every rich-document parser (spec_v2 §8.2).
The generic machinery parsers/pdf.py proved in v1 — DocumentConverter
conversion → structure-aware markdown export → hyphen repair → page/character
provenance → :class:~varagity.ingest.parsers.base.RawDocument assembly —
lifted out so the office (.docx/.pptx/.xlsx) and web
(.html/.htm) parsers reuse one markdown/table/provenance pipeline.
No OCR lives here. The two-pass OCR fallback is PDF-specific (scanned
pages) and stays in pdf.py; the formats parsed through
:class:DoclingParser carry digital text, so a single no-OCR conversion
suffices and every document is extraction="text".
Page semantics per format (verified against Docling's backends): .pptx
maps each slide to a page and .xlsx maps each sheet to a page, so
page carries the slide/sheet identity; .docx and .html expose no
pagination (document.pages is empty), so page stays None — the
same graceful degradation the text parser uses.
Docling imports are deferred to call time: importing this module (which happens on every CLI start via parser self-registration) must not pay for Docling's model machinery.
DoclingParser
¶
Single-pass Docling parser for digital-text formats (no OCR).
The shared base the office and web registrations subclass: one
plain DocumentConverter conversion through the same markdown/table/
provenance pipeline as the PDF parser, minus the two-pass OCR fallback
(these formats carry digital text — spec_v2 §8.2). The converter is
built lazily and cached on the instance (the registry instantiates one
parser per process), and the office/web backends are lightweight — no
layout-model downloads.
Source code in varagity/ingest/parsers/docling_base.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
__init__()
¶
Initialize the converter cache (nothing heavy happens here).
Source code in varagity/ingest/parsers/docling_base.py
128 129 130 | |
extract(path, verbose=None)
¶
Extract a document's text and provenance via one Docling pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The file to convert (any format Docling handles without OCR; discovery routes only this parser's bucket here). |
required |
verbose
|
int | None
|
Console verbosity (0–2); defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
RawDocument
|
The extracted document. |
RawDocument
|
for |
RawDocument
|
without pagination) and |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
ConversionError
|
If conversion fails (a malformed file) — the loader counts the file as failed and the run continues. |
Source code in varagity/ingest/parsers/docling_base.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
export_markdown(document)
¶
Export a converted document as normalized markdown.
Docling's structure-aware export (headings, GFM tables) with newlines
normalized to \n and hyphen-broken words repaired — the exact text
shape the chunkers expect from every parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
DoclingDocument
|
The converted Docling document. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The normalized markdown text. |
Source code in varagity/ingest/parsers/docling_base.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | |
page_char_counts(document)
¶
Count extracted non-whitespace characters per page.
Uses Docling item provenance: every content item (including table
cells) is attributed to the page(s) it appears on. Pages that no item
references keep a zero count — the textless-page signal the PDF OCR
trigger reads. Formats without pagination (.docx/.html) yield an
empty mapping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
DoclingDocument
|
The converted Docling document. |
required |
Returns:
| Type | Description |
|---|---|
dict[int, int]
|
|
Source code in varagity/ingest/parsers/docling_base.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
raw_document(path, text, page_counts, *, extraction)
¶
Assemble the parser result with provenance metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The source file. |
required |
text
|
str
|
The normalized extracted text. |
required |
page_counts
|
dict[int, int]
|
Per-page character counts (for page attribution; empty for non-paginated formats). |
required |
extraction
|
str
|
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
The |
RawDocument
|
class: |
RawDocument
|
chunker. |
|
RawDocument
|
for |
|
RawDocument
|
when the format has no pagination — and |
Source code in varagity/ingest/parsers/docling_base.py
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
varagity.ingest.parsers.pdf
¶
PDF parser: Docling two-pass extraction with an automatic OCR fallback.
Pass 1 converts with OCR disabled — the fast path, since OCR is a
multi-× slowdown. If the result has (near-)no text, too many textless
pages, or pass 1 raised, pass 2 re-converts with OCR enabled (plan
decision #10). Both passes share the docling_base markdown/table/
provenance core (spec_v2 §8.2), so the fallback changes how text is
recovered, never its downstream shape — and the office/web parsers
share the exact same pipeline.
The OCR engine is pluggable via the OCR_ENGINE setting: a small
factory maps engine names to Docling ocr_options (EasyOCR is the
benchmark-decided default — ADR-004; Tesseract needs only the system
binary). Documents recovered by pass 2 carry
extraction="ocr_fallback" provenance on every chunk.
Docling imports are deferred to call time: importing this module (which happens on every CLI start via parser self-registration) must not pay for Docling's model machinery.
Pattern adapted from the docling-ocr-pipeline reference notebook, kept Docling-native (no side pdf2image/pytesseract path) so both passes share one markdown/table/provenance pipeline and the engine swap is pure config.
ExtractionStats
dataclass
¶
What pass 1 recovered, as inputs to the fallback trigger.
Attributes:
| Name | Type | Description |
|---|---|---|
non_ws_chars |
int
|
Non-whitespace characters in the exported text. |
total_pages |
int
|
Page count of the source document. |
textless_pages |
int
|
Pages that contributed no text to the export. |
Source code in varagity/ingest/parsers/pdf.py
133 134 135 136 137 138 139 140 141 142 143 144 145 | |
PdfParser
¶
Parser for the pdf bucket: Docling with the two-pass OCR fallback.
Converters are built lazily and cached on the instance (the registry instantiates one parser per process), so Docling's models load once per pass type, not once per file.
Source code in varagity/ingest/parsers/pdf.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | |
__init__()
¶
Initialize the converter caches (nothing heavy happens here).
Source code in varagity/ingest/parsers/pdf.py
199 200 201 202 203 204 | |
extract(path, verbose=None)
¶
Extract a PDF's text, falling back to OCR when warranted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The |
required |
verbose
|
int | None
|
Console verbosity (0–2); defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
RawDocument
|
The extracted document. |
RawDocument
|
first page that contributed text — chunk-level attribution |
RawDocument
|
would need offset support in the shared chunker, a deferred |
RawDocument
|
enhancement) and |
RawDocument
|
2 produced the text, else |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
ConversionError
|
If conversion fails — for pass 1 only when the fallback is disabled; a pass 2 failure always propagates (the loader counts the file as failed and re-attempts it on the next run, rather than recording a permanently half-extracted document). |
Source code in varagity/ingest/parsers/pdf.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | |
extraction_stats(text, page_char_counts)
¶
Summarize an extraction pass for the fallback trigger.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The exported document text. |
required |
page_char_counts
|
dict[int, int]
|
Non-whitespace character count per page number. |
required |
Returns:
| Type | Description |
|---|---|
ExtractionStats
|
The pass's :class: |
Source code in varagity/ingest/parsers/pdf.py
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | |
get_ocr_options(engine, languages, force_full_page=False)
¶
Resolve an OCR_ENGINE name to Docling OCR options.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
str
|
Registry key ( |
required |
languages
|
list[str]
|
ISO 639-1 language codes, primary first. |
required |
force_full_page
|
bool
|
OCR whole pages even where a text layer exists. |
False
|
Returns:
| Type | Description |
|---|---|
OcrOptions
|
Engine-specific Docling |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no factory is registered under |
Source code in varagity/ingest/parsers/pdf.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
needs_ocr_fallback(stats, *, min_chars, textless_page_ratio)
¶
Decide whether pass 1's result warrants the OCR pass (plan decision #10).
Evaluated once per document, as in the reference notebook. Either trigger suffices: a (near-)empty extraction, or a high share of textless pages — the latter catches mixed scanned/digital documents whose digital pages alone pass a document-level length check.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stats
|
ExtractionStats
|
Pass 1's extraction summary. |
required |
min_chars
|
int
|
Below this many non-whitespace characters the document
counts as having no text layer ( |
required |
textless_page_ratio
|
float
|
Textless-page share at or above which the
fallback fires ( |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the document should be re-converted with OCR. |
Source code in varagity/ingest/parsers/pdf.py
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
varagity.ingest.parsers.office
¶
Office-document parser: the OOXML families, CSV, and OpenDocument (spec_v2 §8).
A thin registration over the shared Docling core: office formats carry
digital text, so a single no-OCR conversion through the same markdown/
table/provenance pipeline as the PDF parser suffices (the two-pass OCR
fallback is PDF-only). The bucket covers the .docx/.pptx/.xlsx
families (including macro-enabled and template variants — Docling's
backends open them like their base formats), single-table .csv, and
OpenDocument .odt/.ods/.odp. All backends are lightweight
(python-docx / python-pptx / openpyxl / odfdo) — no
layout-model downloads.
Provenance: .pptx maps each slide to a Docling page and
.xlsx/.ods map each sheet to one, so page carries the
slide/sheet identity; .docx, .csv, .odt, and .odp expose no
per-item pagination, so page stays None (spec_v2 §8.2).
OfficeParser
¶
Bases: DoclingParser
Parser for the office bucket (OOXML families / CSV / OpenDocument).
Source code in varagity/ingest/parsers/office.py
23 24 25 | |
varagity.ingest.parsers.web
¶
Web-document parser: .html / .htm / .xhtml (spec_v2 §8).
A thin registration over the shared Docling core: web pages carry digital
text, so a single no-OCR conversion through the same markdown/table/
provenance pipeline as the PDF parser suffices. Docling's HTML backend is
lightweight (beautifulsoup4) — no layout-model downloads.
Provenance: HTML has no pagination concept, so page stays None
(spec_v2 §8.2) — the same graceful degradation as .txt / .md.
WebParser
¶
Bases: DoclingParser
Parser for the web bucket (.html / .htm / .xhtml).
Source code in varagity/ingest/parsers/web.py
16 17 18 | |
varagity.ingest.parsers.image
¶
Image parser: single-pass Docling conversion with OCR always on.
Bitmap formats carry no text layer, so unlike the PDF parser's two-pass
fallback there is nothing to try first: every conversion runs the
configured OCR_ENGINE in full-page mode through the same markdown/
table/provenance pipeline as the other Docling-backed parsers
(docling_base). Documents carry extraction="ocr" on every chunk —
distinct from the PDF parser's "ocr_fallback", because OCR is this
format's only extraction path, not a recovery.
Docling imports are deferred to call time: importing this module (which happens on every CLI start via parser self-registration) must not pay for Docling's model machinery.
ImageParser
¶
Parser for the image bucket: Docling's image pipeline, OCR-only.
Converters are built lazily and cached per OCR configuration (the registry instantiates one parser per process; tests and long-lived processes may change OCR settings between calls), so the layout/OCR models load once per configuration, not once per file.
Source code in varagity/ingest/parsers/image.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
__init__()
¶
Initialize the converter cache (nothing heavy happens here).
Source code in varagity/ingest/parsers/image.py
43 44 45 46 47 | |
extract(path, verbose=None)
¶
OCR an image into markdown text with provenance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The image file to convert. |
required |
verbose
|
int | None
|
Console verbosity (0–2); defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
RawDocument
|
The extracted document. |
RawDocument
|
( |
RawDocument
|
as a one-page document) and |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
ConversionError
|
If conversion fails (a corrupt or unreadable image) — the loader counts the file as failed and the run continues. |
Source code in varagity/ingest/parsers/image.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | |
varagity.ingest.loader
¶
Ingestion orchestrator: parse → chunk → contextualize → embed → store (spec §9).
Contextualization (spec §9.4): when settings.CONTEXTUALIZE is
on, each chunk gets an LLM-generated situating blurb and
contextualized_content = context + "\n\n" + content is what gets
embedded; when off, the identity path (context = None,
contextualized_content = content) is preserved — the non-contextual eval
baseline (plan decision #2). A document's chunks are contextualized
sequentially, in order, so llama.cpp can reuse its prompt cache across the
shared document preamble.
Dual-write (spec §9.6): every chunk lands in both stores —
Elasticsearch (contextual BM25) first, pgvector second — within the same
per-file boundary; a store failure after client-level retries fails that
file's ingest loudly. The ordering is deliberate: the pgvector documents
row is the idempotency marker, so it must commit last — a failure between
the two writes leaves no marker, the next run re-attempts the file, and the
Elasticsearch bulk (addressed by deterministic chunk_id) overwrites
rather than duplicates.
Idempotency: a file whose (doc_id, content_hash) is already recorded is
skipped before parsing. Pipeline-setting changes (CONTEXTUALIZE, chunk
params) don't change content hashes, so re-processing an unchanged corpus
requires reingest=True (the CLI's ingest --reingest), which deletes
each discovered document from both stores before ingesting it fresh. A file
with no extractable text is never silently dropped: it gets a documents
row with n_chunks = 0, a warning, and a dedicated summary count.
Exclusion: a run holds the corpus's advisory lock shared from before its
first write until it returns (ADR-019). Ingests overlap each other as they
always have; a backup restore — which holds it exclusive — refuses an ingest
up front with :class:~varagity.stores.vector_store.CorpusLocked (and is
refused by one), whichever process each runs in.
Orchestration seam: each spec §9 stage is a named module function
(:func:parse_document, :func:chunk_document, :func:contextualize_chunks,
:func:embed_chunks, :func:store_chunks), and the run loop invokes them
through an :class:IngestStages bundle. By default the bundle holds the
functions themselves; varagity.pipeline.ingest_flow passes task-wrapped
equivalents so every stage becomes a tracked, retryable Prefect task run —
one orchestration loop, with or without Prefect.
IngestStages
dataclass
¶
Call seam through which the run loop invokes each spec §9 stage.
Defaults are the plain stage functions in this module, so constructing
the bundle without arguments changes nothing. varagity.pipeline
substitutes Prefect @task-wrapped equivalents (same signatures), so
the same loop yields tracked, retryable task runs — the orchestration
logic exists once.
Attributes:
| Name | Type | Description |
|---|---|---|
discover |
Callable[..., Buckets]
|
Corpus discovery (spec §9.1). |
parse |
Callable[..., RawDocument]
|
Text extraction (spec §9.2). |
chunk |
Callable[..., list[Document]]
|
Chunking (spec §9.3). |
contextualize |
Callable[..., list[str | None]]
|
Situating-blurb generation (spec §9.4). |
embed |
Callable[..., list[list[float]]]
|
Passage embedding (spec §9.5). |
store |
Callable[..., Any]
|
Dual-store write (spec §9.6). |
Source code in varagity/ingest/loader.py
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | |
IngestSummary
dataclass
¶
Counters for one ingest run, rendered by the CLI summary table.
Attributes:
| Name | Type | Description |
|---|---|---|
discovered |
int
|
Files found in the corpus buckets. |
ingested |
int
|
Files parsed, chunked, embedded, and stored this run. |
skipped |
int
|
Unchanged files skipped via the idempotency check. |
no_text |
int
|
Files with no extractable text (recorded as 0-chunk documents; includes known-empty files seen again). |
unsupported |
int
|
Files whose bucket has no registered parser (a defensive counter — every v1 bucket has one; it guards future buckets added to discovery before their parser lands). |
failed |
int
|
Files that raised during ingestion (logged, run continues). |
chunks |
int
|
Total chunks stored this run. |
Source code in varagity/ingest/loader.py
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
chunk_document(raw, *, verbose)
¶
Split a parsed document with the configured strategy (spec §9.3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
RawDocument
|
The parsed document (text + provenance). |
required |
verbose
|
int
|
Validated console verbosity. |
required |
Returns:
| Type | Description |
|---|---|
list[Document]
|
The chunks, provenance seeded into each chunk's metadata. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
Source code in varagity/ingest/loader.py
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
contextualize_chunks(*, document_text, chunk_texts, llm, file_name, progress, verbose)
¶
Generate one situating blurb per chunk, or the identity path (spec §9.4).
The document is fitted into its prompt budget once
(:func:~varagity.context.contextual.fit_document_preamble — a served
token count per document, not per chunk), then chunks are processed
sequentially, in document order: every call shares the byte-identical
preamble and llama.cpp reuses its prompt cache. Progress is a per-chunk
sub-bar under the run's file bar.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document_text
|
str
|
The parent document's full extracted text. |
required |
chunk_texts
|
list[str]
|
The document's chunk texts, in order. |
required |
llm
|
LLMClient | None
|
Chat client for contextualization, or |
required |
file_name
|
str
|
Source file name (labels the sub-progress bar). |
required |
progress
|
Progress
|
The run's progress display. |
required |
verbose
|
int
|
Validated console verbosity. |
required |
Returns:
| Type | Description |
|---|---|
list[str | None]
|
One context blurb per chunk, in chunk order (all |
list[str | None]
|
|
Source code in varagity/ingest/loader.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
embed_chunks(texts, *, embeddings, verbose)
¶
Embed contextualized chunk texts in e5 passage mode (spec §9.5).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
The |
required |
embeddings
|
EmbeddingsClient
|
The embeddings client. |
required |
verbose
|
int
|
Validated console verbosity. |
required |
Returns:
| Type | Description |
|---|---|
list[list[float]]
|
One embedding vector per text, in order. |
Raises:
| Type | Description |
|---|---|
APIError
|
If embedding still fails after client retries. |
Source code in varagity/ingest/loader.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | |
file_timestamps(path)
¶
Read a file's filesystem timestamps — document provenance, not ingest time.
These land on every chunk's metadata record (file_created_at /
file_modified_at), answering "how old is this source?" independently
of created_at (which records when the chunk was ingested).
Birth time is best-effort by nature: os.stat exposes
st_birthtime on macOS/Windows but not on Linux (CPython doesn't
surface statx), so :func:_birthtime_fallback shells out to GNU
coreutils there; filesystems without birth times, or a copy/download
that reset them, yield None rather than a guess (st_ctime is
inode-change time, not creation, and would lie).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The file to stat. |
required |
Returns:
| Type | Description |
|---|---|
datetime | None
|
A |
datetime | None
|
|
Source code in varagity/ingest/loader.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
ingest_corpus(docs_path=None, *, store=None, bm25=None, embeddings=None, llm=None, reingest=False, verbose=None, stages=None, on_file=None)
¶
Ingest every supported document under docs_path into both stores.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
docs_path
|
str | None
|
Corpus directory; defaults to |
None
|
store
|
ContextualVectorDB | None
|
Vector store to write to; constructed from settings (and closed on return) when omitted. |
None
|
bm25
|
ElasticsearchBM25 | None
|
BM25 store to write to; constructed from settings (and closed on return) when omitted. Its index is created idempotently at run start. |
None
|
embeddings
|
EmbeddingsClient | None
|
Embeddings client; resolved via the model registry when omitted. |
None
|
llm
|
LLMClient | None
|
Chat client for contextualization; resolved via the model
registry when omitted. Unused when |
None
|
reingest
|
bool
|
Delete each discovered document's previous ingest (from
both stores) and re-process it. Needed after pipeline-setting
changes ( |
False
|
verbose
|
int | None
|
Console verbosity (0–2); defaults to
|
None
|
stages
|
IngestStages | None
|
Per-stage call seam; the plain stage functions when omitted.
|
None
|
on_file
|
Callable[[Path, str, int], None] | None
|
Observer called after each file with |
None
|
Returns:
| Type | Description |
|---|---|
IngestSummary
|
The run's counters (one file failing is counted and logged, not |
IngestSummary
|
raised — a bad file must not abort a corpus run). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
OperationalError
|
If the vector store is unreachable. |
ConnectionError
|
If Elasticsearch is unreachable after retries (index creation at run start). |
CorpusLocked
|
If a backup restore holds the corpus — nothing was written. |
Source code in varagity/ingest/loader.py
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 | |
parse_document(parser, path, *, verbose)
¶
Extract a file's text and provenance (spec §9.2).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parser
|
Parser
|
Parser for the file's discovery bucket. |
required |
path
|
Path
|
The file to parse. |
required |
verbose
|
int
|
Validated console verbosity. |
required |
Returns:
| Type | Description |
|---|---|
RawDocument
|
The extracted document. |
Source code in varagity/ingest/loader.py
174 175 176 177 178 179 180 181 182 183 184 185 | |
store_chunks(records, vectors, *, store, bm25)
¶
Write one document's chunks to both stores (spec §9.6).
Both writes share one boundary, BM25 first: the pgvector documents
row is the idempotency marker and must commit last, so a failure in
between leaves the file re-attemptable on the next run (the
deterministic chunk_id-addressed BM25 docs then overwrite rather
than duplicate). The parent-document row's fields are taken from the
records, which all belong to one document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
list[ChunkRecord]
|
The document's chunk records (non-empty; the empty- extraction guard runs before this stage). |
required |
vectors
|
list[list[float]]
|
One embedding per record, in order. |
required |
store
|
ContextualVectorDB
|
The vector store. |
required |
bm25
|
ElasticsearchBM25
|
The BM25 store. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in varagity/ingest/loader.py
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | |