Skip to content

What just happened

The quickstart's two calls — ingest() and retrieve() — produced a lot more than two return values. This page walks through all of it, because the artifacts are the mental model for everything else in these docs.

The five stages

ingest("report.pdf") ran this pipeline:

parse ──▶ classify ──▶ split ──▶ embed ──▶ upsert
  │           │           │                   │
  ▼           ▼           ▼                   ▼
        the artifact store              the vector store

The IngestResult you got back summarizes it:

r = ingest("report.pdf")
r.doc_id      # '7b6b95d79149…' — sha-256 of the file's bytes
r.category    # 'research_paper' — what classify decided
r.sections    # 6  — page groups split discovered
r.chunks      # 24 — retrieval units created
r.vectors     # 24 — embeddings upserted
r.durations   # {'parse': 41.2, 'classify': 3.1, 'split': 9.8, ...}

The document's identity

doc_id is the SHA-256 checksum of the file's content. That single fact drives several behaviors:

  • Dedup — ingesting the same bytes again returns status="skipped" without running anything. A run that failed partway is retried: only a fully completed pipeline counts.
  • Overwrite-in-place — re-ingesting after skip_existing=False replaces the document's vectors, never duplicates them.
  • A one-byte change is a new document — content addressing has no notion of "version 2 of the same file".

What's in the artifact store

Every stage's output was persisted, keyed by doc_id. With artifact_store: local you can browse it in a file manager:

artifacts/documents/{doc_id}/
├── source/report.pdf                 the original bytes
├── parse/result.json                 every region, bbox, and markdown block
├── parse/document.md                 whole-document markdown
├── parse/pages/page_0001.png …       full page renders
├── parse/figures/…png                cropped charts & figures
├── classify/result.json              category + confidence + reasoning
├── split/result.json                 sections and chunks, with provenance
├── split/ingest_manifest.json        what went to the vector store, when
└── meta.json                         registry entry (filename, pages, counts)

Load any of it back without re-running anything:

from ingestlib.storage import artifacts

artifacts.list_documents()               # registry of everything stored
parse   = artifacts.load_parse(doc_id)   # full ParseResult (structure only)
chunks  = artifacts.load_split(doc_id).chunks

Dedup means the pipeline never re-runs for an unchanged file — and your own tooling can load_parse the stored result instead of ever parsing again.

What's in the vector store

One vector per chunk. Each vector carries the chunk's full payload — markdown, text, pages, region ids, section, category — so a query hit needs no second lookup to be useful:

result = retrieve("how were participants recruited?")
hit = result.hits[0]

hit.chunk.markdown     # the chunk's content
hit.chunk.pages        # [4]
hit.chunk.region_ids   # {4: [2, 3]} — page → region ids on that page
hit.citation           # 'doc 7b6b95d79149 · p.4 · methods'

Those region_ids point back into parse/result.json's bounding boxes — which is how an answer becomes a highlight on the original page. That chain is the subject of Provenance & citations.

The embedding text

Chunks aren't embedded raw. Each one is embedded as its embedding_text — the content prefixed with a breadcrumb of where it lives:

[research_paper › methods › Participant recruitment]

Participants were recruited through community centers in Cairo…

That context line is why a query like "recruitment methodology" finds the right chunk even when the chunk's own words never say "methodology".


You now know what every stage produces and where it lives. Continue into Concepts for the deeper model, or jump straight to the how-to guides.