Files
gitmost/docker-compose.yml
T
agent_coder 1fc9c25681 feat(search): semantic fusion layer (Phase B PR-1) over lexical RRF (#530)
Fuse a vector-similarity branch into #529's lexical RRF. Query embed via a
global TEI sidecar (or per-workspace provider), degrading transparently to the
byte-identical Phase-A lexical path on any failure; a kill-switch and env knobs
gate it.

- ai.service: resolveEmbeddingProvider (workspace→global TEI fallback) with a
  deterministic config fingerprint; embedQuery (query prefix + short 800ms
  timeout); extract embedWithModel core shared with embedTexts.
- page-embedding.repo: vectorCandidateArm fragment (page-level NN, dim +
  active-fingerprint filtered) + fingerprint on insertChunks.
- search.service: 3-branch RRF over lexical ∪ vector candidates; try/catch wraps
  only the embed (permission filter stays outside, fail-closed); semantic
  degrade emits search.semantic.degraded; response gains semantic{state,available,reason}.
- indexer: resolve provider, prepend doc prefix, stamp fingerprint per row.
- migration 20260712T120000: add nullable page_embeddings.fingerprint + composite index.
- infra: TEI embeddings sidecar in docker-compose + EMBEDDING_*/SEARCH_* in .env.example.
- tests: 7 semantic int cases (vector-only hit, sidecar-down, no-provider,
  permission-over-union fail-closed, hung sidecar, lexical∪vector de-dup,
  fingerprint isolation) + fingerprint/prefix/timeout unit tests.

total now = permission-filtered size of (lexical ∪ vector top-N) — a documented
change from Phase A's exact lexical count (falls back to it on degrade).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 20:21:19 +03:00

87 lines
3.2 KiB
YAML

services:
docmost:
image: ghcr.io/vvzvlad/gitmost:latest
depends_on:
- db
- redis
- embeddings
environment:
APP_URL: 'http://localhost:3000'
APP_SECRET: 'REPLACE_WITH_LONG_SECRET'
DATABASE_URL: 'postgresql://docmost:STRONG_DB_PASSWORD@db:5432/docmost'
REDIS_URL: 'redis://redis:6379'
# #530 semantic search: the GLOBAL embedding provider (the `embeddings` TEI
# sidecar below). A workspace that configures its own embedding provider
# overrides this. TEI is OpenAI-compatible, so the endpoint is /v1.
EMBEDDING_ENDPOINT: 'http://embeddings:80/v1'
EMBEDDING_MODEL: 'intfloat/multilingual-e5-small'
EMBEDDING_API_KEY: 'unused'
EMBEDDING_DIMENSIONS: '384'
# MUST match the --revision the sidecar pins (see EMBEDDING_REVISION in
# .env). A revision bump changes the embedding fingerprint (PR-2 swaps
# generations); keep the two in lockstep.
EMBEDDING_REVISION: '${EMBEDDING_REVISION}'
# e5 models require these input prefixes; empty them for a non-e5 model.
EMBEDDING_QUERY_PREFIX: 'query: '
EMBEDDING_DOC_PREFIX: 'passage: '
ports:
- "3000:3000"
restart: unless-stopped
# The app already serves precompressed (brotli/gzip) static assets with
# long-lived cache headers and gzips dynamic API responses. For the best
# cold-load latency you can OPTIONALLY put a reverse proxy (caddy / nginx /
# traefik) in front with HTTP/2 (or HTTP/3) and brotli enabled — none is
# required for compression to work.
volumes:
- docmost:/app/data/storage
db:
# pgvector image (same Postgres major as postgres:18) so `CREATE EXTENSION
# vector` succeeds for the page_embeddings RAG table.
image: pgvector/pgvector:pg18
environment:
POSTGRES_DB: docmost
POSTGRES_USER: docmost
POSTGRES_PASSWORD: STRONG_DB_PASSWORD
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql
redis:
image: redis:8
command: ["redis-server", "--appendonly", "yes", "--maxmemory-policy", "noeviction"]
restart: unless-stopped
volumes:
- redis_data:/data
# #530 Text Embeddings Inference (TEI) sidecar — the GLOBAL embedding provider
# for semantic search. OpenAI-compatible, reached only on the internal network
# (no published port). The model + revision are PINNED so the embedding
# fingerprint is stable; set EMBEDDING_REVISION to a real commit sha in .env.
embeddings:
image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.9
command:
- "--model-id"
- "intfloat/multilingual-e5-small"
- "--revision"
- "${EMBEDDING_REVISION}"
restart: unless-stopped
# Cache downloaded model weights so a restart does not re-download them.
volumes:
- tei-models:/data
# GET /health is TEI's readiness probe. (Drop this block if your TEI image
# variant ships without curl.)
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:80/health || exit 1"]
interval: 30s
timeout: 5s
retries: 5
# Model download on first boot can be slow; don't flap as unhealthy meanwhile.
start_period: 120s
volumes:
docmost:
db_data:
redis_data:
tei-models: