Skip to content

Storage API

Artifacts

Persist and reload every stage's output, keyed by document checksum, on the configured backend (s3 | local).

from ingestlib.storage import artifacts

ingestlib.storage.artifacts.save_parse

save_parse(result: ParseResult) -> str

Persist a ParseResult and all its binary artifacts. Returns the doc_id.

The JSON carries every structural field (regions, bboxes, markdown, ...); page renders and figure crops are written as separate PNG objects.

ingestlib.storage.artifacts.load_parse

load_parse(
    doc_id: str, *, include_images: bool = False
) -> ParseResult

Load a persisted ParseResult.

include_images=False (default) returns pages with image_bytes=None and figure crops as empty bytes — cheap, structure-only. include_images=True fetches every PNG back into the result.

ingestlib.storage.artifacts.load_classify

load_classify(doc_id: str) -> ClassifyResult

Load a persisted ClassifyResult.

ingestlib.storage.artifacts.load_split

load_split(doc_id: str) -> SplitResult

Load a persisted SplitResult.

ingestlib.storage.artifacts.save_extract

save_extract(doc_id: str, result: ExtractResult) -> None

Persist an ExtractResult, keyed by its schema name — extractions with different schemas against the same document coexist.

ingestlib.storage.artifacts.load_extract

load_extract(doc_id: str, schema: type) -> ExtractResult

Load a persisted ExtractResult for schema, revalidating every item's value back into the schema class (they round-trip as plain dicts).

ingestlib.storage.artifacts.load_ingest_manifest

load_ingest_manifest(doc_id: str) -> dict[str, Any]

Load the vector-store sync record written by save_ingest_manifest.

ingestlib.storage.artifacts.document_exists

document_exists(doc_id: str) -> bool

True when this document was parsed and saved before (dedup check).

ingestlib.storage.artifacts.ingest_complete

ingest_complete(doc_id: str) -> bool

True when the FULL pipeline finished for this document.

Checks the ingest manifest — the last artifact the pipeline writes — so a run that died after parse/classify/split gets retried instead of skipped.

ingestlib.storage.artifacts.list_documents

list_documents() -> list[DocumentMeta]

Registry of every persisted document — id, filename, pages, category, counts.

ingestlib.storage.artifacts.get_document_meta

get_document_meta(doc_id: str) -> DocumentMeta

Registry entry for one document (self-healing, like list_documents).

ingestlib.storage.artifacts.page_image_key

page_image_key(doc_id: str, page_num: int) -> str

Artifact key of a page render — read_blob() serves it on any backend (on s3, get_s3_client().generate_presigned_url can serve it as a URL).

ingestlib.storage.artifacts.read_blob

read_blob(key: str) -> bytes

Raw bytes at an artifact key, whichever backend holds them.

The backend-agnostic way for a UI to serve page renders and figure crops when a presigned URL is not available (artifact_store: local).

ingestlib.storage.artifacts.delete_document

delete_document(doc_id: str) -> int

Remove every object under the document's prefix. Returns count deleted.

The VectorStore contract

Every connector implements this interface — code written against it runs on any backend.

ingestlib.storage.base.VectorStore

Bases: ABC

Contract for pushing split chunks into a vector database and querying them.

Implementations must make upserts idempotent per (document_id, chunk_id) — re-ingesting a document overwrites its vectors, never duplicates them.

upsert_chunks abstractmethod

upsert_chunks(
    document_id: str,
    chunks: list[Chunk],
    embeddings: list[list[float]],
    category: str = "",
    namespace: str = "",
) -> int

Store one embedding per chunk with full provenance payload.

Returns the number of vectors written. embeddings[i] belongs to chunks[i]; use _validate_upsert() to enforce the pairing. category is the document-type label (from classify) stored on every vector so queries can filter by it.

query abstractmethod

query(
    vector: list[float],
    top_k: int = 10,
    filters: dict[str, Any] | None = None,
    namespace: str = "",
    text: str | None = None,
) -> list[RetrievedChunk]

Nearest chunks to vector, best first.

filters are equality constraints on payload fields, e.g. {"category": "research_paper", "section": "methods"}. text is the original query text — connectors with a lexical/hybrid side use it for sparse search; dense-only connectors ignore it.

A hybrid connector that cannot fuse its two signals server-side (pinecone) may return MORE than top_k results — the dense top_k in order, then lexical-only extras — expecting a reranker to produce the final order; callers skipping reranking should slice [:top_k].

delete_document abstractmethod

delete_document(
    document_id: str, namespace: str = ""
) -> int

Remove every vector belonging to a document. Returns count removed.

ingestlib.storage.base.RetrievedChunk

Bases: BaseModel

One query hit — a stored chunk restored with its retrieval score.

Carries everything needed to answer AND cite: content (markdown/text), location (document_id, pages, region_ids → bboxes via the artifact store), and context (section, heading, category).

Connectors

from ingestlib.storage import (
    SqliteStore, PineconeStore, QdrantStore, PgvectorStore,
    MongodbStore, MilvusStore, OpensearchStore, WeaviateStore,
    default_store,
)

ingestlib.storage.default_store

default_store() -> VectorStore

The connector selected by config.yaml's vector_store key.

All eight constructors take hybrid: bool = True — pass hybrid=False for dense-only behavior. Connection details come from configuration, never constructor arguments — see Connect a vector store.