Architecture
Module-by-module map of the Pascal units that make up the project, plus the data flow across them.
Source layout
src/
mormot.db.pgvector.pas Codec: TPgVector value type,
text + binary wire format,
MRL truncation helper.
mormot.db.pgvector.binding.pas Binding: bind/scan against the
mORMot 2 PostgreSQL driver, DDL
emitters, distance op builders,
schema setup helper.
mormot.db.pgvector.orm.pas ORM helper: TOrmWithEmbedding +
dim registry + CREATE TABLE
emitter that substitutes
VECTOR(N) for the BYTEA default.
mormot.ai.embed.pas Embedding-client interface
(IEmbeddingClient), HTTP
transport interface
(IHttpTransport), live
TMormotHttpTransport with
persistent socket, retry
decorator, concurrency-limit
decorator.
mormot.ai.embed.ollama.pas Ollama-native IEmbeddingClient
impl (POST /api/embed,
EmbeddingGemma defaults, MRL
fallback).
mormot.ai.llm.pas ILlmClient interface +
TStubLlmClient (no-network
placeholder).
mormot.ai.llm.ollama.pas Ollama-native ILlmClient impl
(POST /api/generate).
mormot.ai.chunk.pas Pascal source walker +
decomposer (one chunk per
top-level declaration),
metadata schema, family
derivation, UTF-8 + BOM
handling, large-decl splitter.
mormot.ai.cli.pas Shared CLI harness:
TCliSpec / TCliResult records,
CliParse, CliFormatUsage, exit
code constants.
mormot.ai.rag.pas Orchestrator: TRagOrchestrator
with sequential Ingest,
IngestParallel, Retrieve.
examples/
01_ingest_mormot2.dpr Production ingest CLI.
02_query_cli.dpr Production retrieve CLI.
03_chat_cli.dpr Production chat CLI.
demo_real.dpr Quick all-in-one demo runner.
test/
test.pgvector.pas Codec tests.
test.pgvector.orm.pas ORM helper tests.
test.binding.pas DDL + operator builder tests.
test.binding.live.pas Live PostgreSQL bind/scan tests.
test.embed.pas IEmbeddingClient interface tests.
test.embed.ollama.pas Ollama embed-client + retry +
concurrency limit tests.
test.llm.pas Stub LLM tests.
test.llm.ollama.pas Ollama generative tests.
test.chunk.pas Walker + decomposer tests.
test.cli.pas CLI harness tests.
test.cli.binaries.pas Spawn-the-binary regression
tests for the three CLIs.
test.rag.live.pas Live RAG ingest + retrieve.
test.rag.smoke.pas Canonical mORMot 2 corpus
smoke probes.
test_runner.dpr TSynTests entry point.
Domain dependency graph
codec
(Domain A)
│
┌───────────┴───────────┐
▼ ▼
binding embedder
(Domain B) (Domain C)
│ │
▼ ▼
│ ┌───┴────┐
│ ▼ ▼
│ Ollama Stub /
│ impl other
│ (interface only)
│ │
└───────────┬───────────┘
▼
orchestrator
(Domain E) ◀──── chunker (Domain D)
│
▼
CLIs ◀──── CLI harness
(Domain F) (Domain F shared)
Ingest flow
input: source root path, IEmbeddingClient (or factory + worker count)
ChunkerEnumerate(root) walks .pas/.pp/.dpr/.inc
│
▼
ChunkerDecompose(file) one TPascalChunk per
│ top-level declaration
▼
IEmbeddingClient.EmbedBatch(chunk_contents) Ollama POST /api/embed
│ in batches of 16
▼
TPgVector[] binary wire format
│
▼
PgVectorBindBinary(stmt, paramIdx, vec) paramFormat=1 binary
│
▼
INSERT INTO mormot_chunks one row per chunk,
(... , embedding) VALUES (?, ?, ?::vector) HNSW + GIN indexes
│
▼
TRagIngestStats files / chunks / rows /
elapsed_ms
Sequential Ingest runs the loop in the calling thread.
IngestParallel(root, factory, workers) distributes files across
worker threads via TSynParallelProcess. Each worker calls the
factory once at slice start to obtain its own IEmbeddingClient (the
default TMormotHttpTransport holds a single persistent socket and
is not thread-safe). Database connections come from the thread-safe
properties pool automatically.
Retrieval flow
input: query string, top_k, family filter, alpha
IEmbeddingClient.Embed(query) Ollama POST /api/embed
│
▼
TPgVector same wire format
│
▼
PgVectorBindBinary(stmt, 1, vec) binary param
│
▼
SELECT id, ..., content,
1.0 - (embedding <=> ?::vector) AS vsim,
ts_rank_cd(ts, plainto_tsquery('simple', ?)) AS trank,
α·vsim + (1−α)·trank AS combined
FROM mormot_chunks
[WHERE family = ?]
ORDER BY combined DESC
LIMIT ?
│
▼
TRagRetrievalResultDynArray sorted, top-k, full
metadata + 3 score
components per row
The hybrid blend uses α · cosine_similarity + (1 − α) · ts_rank. By
convention α = 1.0 is pure vector and α = 0.0 is pure text. The
default is 0.7.
Chat flow
The chat CLI composes the retrieve + LLM steps:
query
│
▼ Retrieve(query, top_k, family, α)
top-K chunks
│
▼ BuildRagPrompt
prompt = [system instructions]
[chunks as context]
Question: <query>
Answer:
│
▼ ILlmClient.Complete(prompt)
LLM response
│
▼ stdout
Provenance (the file:line ranges of the retrieved chunks) is printed
to ErrOutput so the answer on stdout can be piped while the user
still sees where the model's facts came from.
Storage schema
The canonical mormot_chunks table layout:
CREATE TABLE mormot_chunks (
id BIGSERIAL PRIMARY KEY,
unit_name TEXT NOT NULL,
family TEXT NOT NULL, -- core | orm | rest | ...
symbol TEXT,
kind TEXT, -- type | proc | func |
-- comment | unit-header
file_path TEXT NOT NULL,
line_start INT,
line_end INT,
content TEXT NOT NULL,
tokens INT,
embedding VECTOR(768),
ts TSVECTOR GENERATED ALWAYS AS
(to_tsvector('simple', content)) STORED,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX ON mormot_chunks USING hnsw (embedding vector_cosine_ops)
WITH (m=16, ef_construction=64);
CREATE INDEX ON mormot_chunks USING gin (ts);
CREATE INDEX ON mormot_chunks (family);
CREATE INDEX ON mormot_chunks (unit_name);
The embedding column dimension is parametrized; PgVectorSchemaConfig
defaults to 768 (full EmbeddingGemma) but accepts any of {128, 256,
512, 768} for MRL-truncated indexing.
Production transport stack
For deployments that run hundreds of embed calls in a row, wrap the live transport in two decorators:
inner := TMormotHttpTransport.Create({receive_timeout_ms=}120000);
retrying := TRetryingHttpTransport.Create(inner,
{max_attempts=}3, {initial_delay_ms=}250);
limited := TConcurrencyLimitedHttpTransport.Create(retrying,
{limit=}4);
client := TOllamaEmbeddingClient.Create(limited, base_url, model, dim);
The result: a single keep-alive socket reused across requests, automatic exponential-backoff retries on HTTP 5xx / 429 / network errors, and a hard cap on in-flight concurrent calls so the server (typically a single-instance Ollama with one model loaded) is not asked to queue more requests than it can process.