- docker-compose: swap postgres image to pgvector/pgvector:pg16
- postgres/init.sql: CREATE EXTENSION vector; image_embedding table
- plugins/embeddings.py: embed_images + search_images_semantic
(sentence-transformers all-MiniLM-L6-v2, lazy-loaded, pgvector <=> cosine)
- plugins/images.py: register_image kicks off background embed worker
- seed.py: seed_embeddings writes 4 embeddings for the mock images
- README: semantic image search section + T3 note
- 11 tests across 4 files, all green:
test_embeddings_plugin.py (4): schema, ordering, idempotency, stub
test_embeddings_real_model.py (3): real MiniLM, acceptance queries
test_register_image_hook.py (2): manifest row, end-to-end hook
test_seed_embeddings.py (2): writes 4, idempotent
- Includes T3 consistency plugin skeleton (4 stub tools)
53 lines
2.0 KiB
SQL
53 lines
2.0 KiB
SQL
-- Lore Engine POC — minimal Postgres schema.
|
|
-- Operational data that doesn't belong in the world graph.
|
|
|
|
-- pgvector: 384-dim embeddings for semantic image search.
|
|
-- (Requires the `pgvector` image or installed extension on the host OS.)
|
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
|
CREATE TABLE IF NOT EXISTS trade_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
world_id TEXT NOT NULL DEFAULT 'default',
|
|
occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
in_fiction_time TEXT,
|
|
buyer_id TEXT,
|
|
seller_id TEXT,
|
|
item_id TEXT,
|
|
quantity NUMERIC,
|
|
unit TEXT,
|
|
unit_price NUMERIC,
|
|
total_price NUMERIC,
|
|
location_id TEXT,
|
|
notes TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS trade_log_time ON trade_log (occurred_at DESC);
|
|
CREATE INDEX IF NOT EXISTS trade_log_buyer ON trade_log (buyer_id);
|
|
|
|
-- Image manifests. The actual bytes live in MinIO; this is metadata + tags
|
|
-- that the LLM can query to know what images exist.
|
|
CREATE TABLE IF NOT EXISTS image_manifest (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
image_id TEXT NOT NULL UNIQUE,
|
|
object_key TEXT NOT NULL, -- the MinIO object key
|
|
entity_id TEXT, -- linked LoreEntity (e.g. Person.id)
|
|
entity_type TEXT, -- Person / Location / Event / Item
|
|
caption TEXT NOT NULL,
|
|
tags TEXT[],
|
|
era TEXT,
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
width INT,
|
|
height INT,
|
|
bytes BIGINT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS image_manifest_entity ON image_manifest (entity_id);
|
|
CREATE INDEX IF NOT EXISTS image_manifest_tags ON image_manifest USING GIN (tags);
|
|
CREATE INDEX IF NOT EXISTS image_manifest_era ON image_manifest (era);
|
|
|
|
-- Image embeddings (pgvector). One row per embedded image. Filled by
|
|
-- plugins/embeddings.py `embed_images` (idempotent on image_id).
|
|
CREATE TABLE IF NOT EXISTS image_embedding (
|
|
image_id TEXT PRIMARY KEY REFERENCES image_manifest(image_id) ON DELETE CASCADE,
|
|
embedding vector(384) NOT NULL,
|
|
embedded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|