Knowledge-lib is an attempt at standardizing ingestion and retrieval via an API interface. My rationale for doing this is:
We need to think about this from the perspective of, what exactly should a system specialized on "knowledge" entail? We know that LLMs are primarily text machines, so if you are building a system where an LLM must generate based on some passed context, you ultimately have to build a system/pipeline that converts multiple modalities into textual representations (columns, embeddings, key words, metadata, summaries, graphs, etc.) that you can retrieve across various RAG apps as you build them. For this purpose; you need a unified ingestion service. This service can (and will) utilize multiple models, techniques in order to transform N modalities (text, audio, video, PDFs, etc) into a textual representation.
As such we should first begin by properly representing the "objects" and data flow transfer that such an ingestion system would entail. Details on those are in /docs/schemas.md
Secondly, we must think about retrieval. Assuming that all forms of data are ingested into a set of representations that can then be retrieved via SQL queries on the corresponding table that each schema holds. This allows for a lot of cross cutting, as the SQL layer is also matched with a minio/s3 layer, and since the models are multimodal, you can augment generation with actually passing in images (if needed) and parts of PDFs. But the primary layer is text.
Standardizing this into one API service, while it may add a bit of latency, unifies an approach for all long running agents.
.env used by app/Alembic and Docker Compose (Makefile passes --env-file .env)emb_v1 with HNSW index (vector_cosine_ops)text_fts tsvector + GIN indextext_fts in sync with text on INSERT/UPDATEdocuments, chunks, and pages.env (app + Compose):Makefile convenience:
Export SciFact into per-document .txt files (no external deps):
Outputs (under evals/datasets):
evals/datasets/beir/processed/scifact/docs/*.txt — UTF-8 text files, one per BEIR doc (title + blank line + body)evals/datasets/beir/processed/scifact/manifest.jsonl — metadata per doc (source_uri, sha256, etc.)evals/datasets/beir/eval/scifact/queries.jsonl — BEIR queries for the chosen split (if present)evals/datasets/beir/eval/scifact/qrels/test.tsv — relevance labels (or split fallback)Run server:
uv run uvicorn src.app.main:app --reloadHealth (unversioned):
GET /health/live — liveness (process up; no DB).GET /health/ready — readiness (checks DB with SELECT 1).GET /health/healthz (compat; same as readiness).v1 (business endpoints):
POST /v1/ingest
{ "source": { "source_type": "raw_text", "title": "Demo", "text": "hello world ..." } }{ "container_id": "<uuid>", "pages_created": 1, "segments_created": 1 }POST /v1/search
{ "query": "hello", "k": 20 }{ "results": [ { "modality": "text", "segment_id": "...", "container_id": "...", "page_no": 1, "score": 0.42, "snippet": "..." } ] }.env (single source of truth for app/Alembic/Compose):
.env.example.env is ignored (do not commit secrets)Alembic and the app load the root .env (via python‑dotenv). Docker Compose also reads it via the Makefile (--env-file .env).
Tables:
text + text_fts TSVECTOR with GIN index; emb_v1 VECTOR(1536) with HNSW index)Indexes:
chunks.text_ftschunks.emb_v1 (vector_cosine_ops)(container_id, page_no) and container_idFTS maintenance:
trg_chunks_fts computes:
NEW.text_fts := to_tsvector('simple'::regconfig, unaccent(coalesce(NEW.text,'')))Extensions (auto‑created by migration):
Lexical (FTS):
SELECT segment_id, ts_rank_cd(text_fts, to_tsquery('simple', 'kyc:*')) AS score
FROM text_segments
WHERE text_fts @@ to_tsquery('simple', 'kyc:*')
ORDER BY score DESC
LIMIT 50;
Vector (ANN):
SELECT segment_id, 1 - (emb_v1 <=> :q_emb) AS cosine_sim
FROM text_segments
ORDER BY emb_v1 <=> :q_emb
LIMIT 50;
Hybrid: fetch top‑N from each, fuse (e.g., RRF), then rerank.
Rule of thumb: do not edit an already‑applied migration; add a new revision for changes.
.env with secrets — the example is committed insteadlocalhost:5432:
make updocker logs kl-postgresdocker inspect -f '{{.State.Health.Status}}' kl-postgrestext_fts, not a generated column, to comply with Postgres immutability rules.Knowledge-lib is an attempt at standardizing ingestion and retrieval via an API interface. My rationale for doing this is:
We need to think about this from the perspective of, what exactly should a system specialized on "knowledge" entail? We know that LLMs are primarily text machines, so if you are building a system where an LLM must generate based on some passed context, you ultimately have to build a system/pipeline that converts multiple modalities into textual representations (columns, embeddings, key words, metadata, summaries, graphs, etc.) that you can retrieve across various RAG apps as you build them. For this purpose; you need a unified ingestion service. This service can (and will) utilize multiple models, techniques in order to transform N modalities (text, audio, video, PDFs, etc) into a textual representation.
As such we should first begin by properly representing the "objects" and data flow transfer that such an ingestion system would entail. Details on those are in /docs/schemas.md
Secondly, we must think about retrieval. Assuming that all forms of data are ingested into a set of representations that can then be retrieved via SQL queries on the corresponding table that each schema holds. This allows for a lot of cross cutting, as the SQL layer is also matched with a minio/s3 layer, and since the models are multimodal, you can augment generation with actually passing in images (if needed) and parts of PDFs. But the primary layer is text.
Standardizing this into one API service, while it may add a bit of latency, unifies an approach for all long running agents.
.env used by app/Alembic and Docker Compose (Makefile passes --env-file .env)emb_v1 with HNSW index (vector_cosine_ops)text_fts tsvector + GIN indextext_fts in sync with text on INSERT/UPDATEdocuments, chunks, and pages.env (app + Compose):Makefile convenience:
Export SciFact into per-document .txt files (no external deps):
Outputs (under evals/datasets):
evals/datasets/beir/processed/scifact/docs/*.txt — UTF-8 text files, one per BEIR doc (title + blank line + body)evals/datasets/beir/processed/scifact/manifest.jsonl — metadata per doc (source_uri, sha256, etc.)evals/datasets/beir/eval/scifact/queries.jsonl — BEIR queries for the chosen split (if present)evals/datasets/beir/eval/scifact/qrels/test.tsv — relevance labels (or split fallback)Run server:
uv run uvicorn src.app.main:app --reloadHealth (unversioned):
GET /health/live — liveness (process up; no DB).GET /health/ready — readiness (checks DB with SELECT 1).GET /health/healthz (compat; same as readiness).v1 (business endpoints):
POST /v1/ingest
{ "source": { "source_type": "raw_text", "title": "Demo", "text": "hello world ..." } }{ "container_id": "<uuid>", "pages_created": 1, "segments_created": 1 }POST /v1/search
{ "query": "hello", "k": 20 }{ "results": [ { "modality": "text", "segment_id": "...", "container_id": "...", "page_no": 1, "score": 0.42, "snippet": "..." } ] }.env (single source of truth for app/Alembic/Compose):
.env.example.env is ignored (do not commit secrets)Alembic and the app load the root .env (via python‑dotenv). Docker Compose also reads it via the Makefile (--env-file .env).
Tables:
text + text_fts TSVECTOR with GIN index; emb_v1 VECTOR(1536) with HNSW index)Indexes:
chunks.text_ftschunks.emb_v1 (vector_cosine_ops)(container_id, page_no) and container_idFTS maintenance:
trg_chunks_fts computes:
NEW.text_fts := to_tsvector('simple'::regconfig, unaccent(coalesce(NEW.text,'')))Extensions (auto‑created by migration):
Lexical (FTS):
SELECT segment_id, ts_rank_cd(text_fts, to_tsquery('simple', 'kyc:*')) AS score
FROM text_segments
WHERE text_fts @@ to_tsquery('simple', 'kyc:*')
ORDER BY score DESC
LIMIT 50;
Vector (ANN):
SELECT segment_id, 1 - (emb_v1 <=> :q_emb) AS cosine_sim
FROM text_segments
ORDER BY emb_v1 <=> :q_emb
LIMIT 50;
Hybrid: fetch top‑N from each, fuse (e.g., RRF), then rerank.
Rule of thumb: do not edit an already‑applied migration; add a new revision for changes.
.env with secrets — the example is committed insteadlocalhost:5432:
make updocker logs kl-postgresdocker inspect -f '{{.State.Health.Status}}' kl-postgrestext_fts, not a generated column, to comply with Postgres immutability rules.