From 1281d7a778861fdab965b6ce8f764fbc018b55df Mon Sep 17 00:00:00 2001 From: Claudio Drews Date: Tue, 2 Jun 2026 09:45:31 -0300 Subject: [PATCH] fix: make embedding provider-agnostic via env vars - embedding.py: EMBEDDING_API_BASE and EMBEDDING_MODEL now read from env with OpenRouter defaults (preserves backward compatibility) - .env.example: document embedding backend config with Qwen3 rationale - setup/install.md: update prerequisite to reflect configurable backend - layers/05-qdrant.md: add "Why Qwen3-Embedding-8B" section (multilingual, quality, speed, cost) Closes #1 --- .env.example | 11 +++++++++++ docker/worker/services/embedding.py | 15 ++++++++++----- layers/05-qdrant.md | 15 +++++++++++++-- setup/install.md | 6 +++++- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index e6e2bd9..69d894d 100644 --- a/.env.example +++ b/.env.example @@ -46,6 +46,17 @@ REFLECTION_LOG_PATH=/home/your-user/.hermes/logs/reflection_trigger.log # MaaS env path (reflection_trigger.py loads env from this location) MAA_ENV_PATH=/home/your-user/.env +# ── Embedding Backend ───────────────────────────────── + +# Default: OpenRouter with Qwen3-Embedding-8B. +# Recommended model: multilingual (excellent for non-English content), +# high-quality 4096d embeddings, fast inference, affordable pricing. +# To use a local model (Ollama, vLLM, llama.cpp), set both vars: +# EMBEDDING_API_BASE=http://host.docker.internal:11434/v1 +# EMBEDDING_MODEL=nomic-embed-text +# EMBEDDING_API_BASE=https://openrouter.ai/api/v1 +# EMBEDDING_MODEL=qwen/qwen3-embedding-8b + # ── Strongly Recommended ────────────────────────────── # LLM extraction token limit — 1024 is too small, causes fabric truncation diff --git a/docker/worker/services/embedding.py b/docker/worker/services/embedding.py index 7ff3865..57c45c2 100644 --- a/docker/worker/services/embedding.py +++ b/docker/worker/services/embedding.py @@ -1,5 +1,6 @@ """ -Embedding client via OpenRouter. +Embedding client. Provider-agnostic — defaults to OpenRouter, configurable +via EMBEDDING_API_BASE and EMBEDDING_MODEL for local Ollama/vLLM/llama.cpp. Mandatory dimension validation. """ import os @@ -11,13 +12,17 @@ logger = logging.getLogger("cognitive-worker.embedding") OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "") EMBEDDING_DIMS = int(os.environ.get("EMBEDDING_DIMS", "4096")) -EMBEDDING_MODEL = "qwen/qwen3-embedding-8b" -API_BASE = "https://openrouter.ai/api/v1" +EMBEDDING_API_BASE = os.environ.get( + "EMBEDDING_API_BASE", "https://openrouter.ai/api/v1" +) +EMBEDDING_MODEL = os.environ.get( + "EMBEDDING_MODEL", "qwen/qwen3-embedding-8b" +) async def get_embedding(text: str) -> list[float]: """ - Generates embedding via OpenRouter. + Generates embedding via the configured backend. Validates that the returned dimensions match EMBEDDING_DIMS. """ if not OPENROUTER_API_KEY: @@ -38,7 +43,7 @@ async def get_embedding(text: str) -> list[float]: async with httpx.AsyncClient(timeout=60) as client: resp = await client.post( - f"{API_BASE}/embeddings", + f"{EMBEDDING_API_BASE}/embeddings", headers=headers, json=payload, ) diff --git a/layers/05-qdrant.md b/layers/05-qdrant.md index 6780c07..2db8ce6 100644 --- a/layers/05-qdrant.md +++ b/layers/05-qdrant.md @@ -2,9 +2,20 @@ > **Service:** Qdrant 1.17+ (Docker) > **Collection:** `knowledge_base` (4096d Cosine + BM25 sparse) -> **Embedding:** Qwen3-Embedding-8B via OpenRouter +> **Embedding:** Qwen3-Embedding-8B via OpenRouter (default; configurable) > **Endpoint:** `http://localhost:6333` +## Why Qwen3-Embedding-8B + +The default embedding model is **Qwen3-Embedding-8B** for four reasons: + +1. **Multilingual** — strong performance across 50+ languages, including Portuguese, Spanish, and other non-English content common in real-world agent use +2. **Quality** — 4096-dimensional embeddings with high semantic fidelity; consistently ranks near the top of the MTEB leaderboard +3. **Speed** — fast inference at 8B parameters, suitable for hourly ingestion pipelines without bottlenecking the ARQ worker +4. **Cost** — affordable via OpenRouter ($0.025/1M tokens at time of writing); a full wiki re-index costs cents, not dollars + +Users can switch to any OpenAI-compatible embedding API by setting `EMBEDDING_API_BASE` and `EMBEDDING_MODEL` in `.env`. If changing models, ensure `EMBEDDING_DIMS` matches both the new model's output and the Qdrant collection schema. + ## What it stores All knowledge that benefits from semantic search — wiki pages, session transcripts, raw documents, technical references. Content is ingested via the continuous ingest pipeline (hourly) and the wiki agent (scheduled). @@ -75,7 +86,7 @@ Redis queue (ARQ job) │ ▼ ARQ Worker (Docker) - │ embed via Qwen3-Embedding-8B (OpenRouter) + │ embed via configured backend (default: Qwen3-Embedding-8B) │ get_sparse_embedding() → BM25 (fastembed, local) ▼ Qdrant upsert (with dedup check) diff --git a/setup/install.md b/setup/install.md index ac40ca9..f0a9421 100644 --- a/setup/install.md +++ b/setup/install.md @@ -7,7 +7,7 @@ - Hermes Agent 0.14.0+ (tested on 0.15.2) - Python 3.11+ - Docker 24.0+ -- OpenRouter API key (for embeddings and LLM extraction) +- OpenRouter API key (or configured embedding backend — see [Layer 5: Qdrant](../layers/05-qdrant.md)) - 16 GB RAM recommended (8 GB minimum) ## Installation @@ -80,6 +80,10 @@ ICARUS_EXTRACTION_MAX_TOKENS=4096 ICARUS_EXTRACTION_MODEL=deepseek/deepseek-v4-flash EMBEDDING_DIMS=4096 +# Optional — Embedding backend (defaults to OpenRouter) +# EMBEDDING_API_BASE=https://openrouter.ai/api/v1 +# EMBEDDING_MODEL=qwen/qwen3-embedding-8b + # Optional ICARUS_OBSIDIAN=1 ICARUS_RESULT_MAX_CHARS=500