Skip to content

~/projects / ai-semantic-db

Semantic DB

CLI for local semantic search over your own structured data: define a schema, add records and search in plain language. No cloud, no API keys.

role
design and implementation: schema model, CLI, embeddings, tests
time
Aug – Sep 2026
type
personal
stack
Python 3.12 · Typer · SQLAlchemy · Alembic · PostgreSQL + pgvector · Ollama (bge-m3)
demo.mp4
[demo video: screen recording, 1–3 min]

01 / problem

Semantic search without a cloud service

Existing semantic search tools are cloud-based or tied to one data source. The aim was to define your own structure, keep the data on your machine and search it by meaning instead of by keyword.

02 / solution

Schema-driven collections with local embeddings

A collection is defined with a compact field grammar such as price:float:embed:unit=PLN, supporting text, enum, int, float, bool, date and string array types. Each record is rendered into one formatted text card and embedded with bge-m3 through Ollama on save, so added means searchable. Search is cosine top-k with a Rich table and an --explain mode; it refuses to run when a collection was embedded with a different model, because cross-model distances mean nothing.

  • [screenshot 1]

    [caption]

  • [screenshot 2]

    [caption]

03 / architecture

How it works

ingest
  1. schema
  2. records
  3. embeddings
  4. pgvector
query
  1. question
  2. embeddings
  3. retrieval top-k
  4. CLI

A Python 3.12 CLI built with Typer on a layered structure: domain, application (ports and use cases), infrastructure (SQLAlchemy, Alembic, Ollama client) and cli, with import-linter contracts keeping dependencies pointing inward. Records are JSONB payloads in PostgreSQL 17 with pgvector (halfvec 1024), so schemas change without per-collection DDL. Writes are use cases; reads go through a single queries facade.

04 / results

Outcome

05 / lessons

What I learned

  • JSONB payloads instead of per-collection tables trade some speed for far simpler migrations and arbitrary schemas.
  • Storing the exact rendered text next to each vector prevents silent drift when a template changes after embeddings exist.

gallery

  • [photo]

    [caption]

  • [photo]

    [caption]

  • [photo]

    [caption]