feat(ai): semantic-search backfill worker + Ollama compose profile (RECOMMENDATIONS #4a)
CI / backend (pull_request) Successful in 49s
CI / frontend (pull_request) Successful in 12s
CI / format (pull_request) Successful in 50s
CI / db-tests (pull_request) Successful in 51s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 52s

First activation slice of semantic search (docs/discovery/05+06):
- EmbeddingBackfillWorker: fills Email.Embedding in small batches (32, 2s pause,
  newest first; subject+snippet, truncated) so interactive load is never starved.
  Exits immediately when the embedding provider is unavailable — AI-off deployments
  are untouched, Ollama hiccups back off instead of crashing the host.
- compose: optional 'ollama' service under the 'ai' profile (persistent model
  volume, commented GPU passthrough for the RTX 3080); api gets
  Ai__OllamaBaseUrl pointing at it.
- LiveDb test proves the worker persists 768-dim vectors against real pgvector
  (deterministic fake provider); LiveDb classes serialised into one xUnit
  collection (concurrent MigrateAsync on a fresh DB races — found while testing).

Verified against REAL Ollama locally: pulled nomic-embed-text in a container and
confirmed the /api/embeddings contract the provider uses returns 768-dim vectors.
Full suite 55 green (4 LiveDb vs real pgvector); format clean.

Next slice: hybrid RRF ranking in SearchService once embeddings exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-02 16:50:27 +02:00
parent 59522a6e96
commit 3b93893618
6 changed files with 226 additions and 0 deletions
+20
View File
@@ -34,6 +34,8 @@ services:
GoogleOAuth__ClientId: ${GOOGLE_CLIENT_ID:-}
GoogleOAuth__ClientSecret: ${GOOGLE_CLIENT_SECRET:-}
Ai__Mode: ${AI_MODE:-Disabled}
# Points at the compose 'ollama' service when the ai profile is up; harmless otherwise.
Ai__OllamaBaseUrl: ${OLLAMA_BASE_URL:-http://ollama:11434}
# Dev mode shows the dev banner and caps the initial sync. Set DEV_MODE=true
# and MAX_MESSAGES=1000 in deploy/.env to exercise it in this Docker setup.
App__DevMode: ${DEV_MODE:-false}
@@ -59,6 +61,23 @@ services:
ports:
- "8081:80"
# Local AI (semantic search + assistants). Enable with:
# docker compose --profile ai up -d && set AI_MODE=LocalOllama in deploy/.env
# First run: docker compose exec ollama ollama pull nomic-embed-text
# GPU (RTX 3080): uncomment the deploy block to pass the GPU through.
ollama:
image: ollama/ollama
profiles: ["ai"]
volumes:
- ollama:/root/.ollama
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
# Optional reverse proxy. Enable with: docker compose --profile proxy up
nginx:
image: nginx:alpine
@@ -74,3 +93,4 @@ services:
volumes:
pgdata:
keys:
ollama: