Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/rag/embedder.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,8 @@ def get_embedder() -> Embedder:
"""Singleton embedder."""
global _embedder
if _embedder is None:
from .env import ensure_rag_env

ensure_rag_env()
_embedder = Embedder()
return _embedder
29 changes: 29 additions & 0 deletions src/rag/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Load Meridian .env for RAG indexing and retrieval."""

from __future__ import annotations

from pathlib import Path

from dotenv import load_dotenv

REPO_ROOT = Path(__file__).resolve().parents[2]
_env_loaded = False


def ensure_rag_env() -> Path:
"""Load repo-root ``.env`` once without overriding existing env vars."""
global _env_loaded
if not _env_loaded:
load_dotenv(REPO_ROOT / ".env")
_env_loaded = True
return REPO_ROOT


def reset_rag_env_state() -> None:
"""Reset env-load flag and RAG singletons (tests only)."""
global _env_loaded
_env_loaded = False
from . import embedder, qdrant_client

embedder._embedder = None # type: ignore[attr-defined]
qdrant_client._store = None # type: ignore[attr-defined]
4 changes: 3 additions & 1 deletion src/rag/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import structlog

from .collections import RagCollection, upsert_documents
from .env import REPO_ROOT, ensure_rag_env

logger = structlog.get_logger(__name__)

ROOT = Path(__file__).resolve().parents[2]
ensure_rag_env()
ROOT = REPO_ROOT
METRICS_PATH = ROOT / "docs" / "METRICS.md"
LIMITATIONS_PATH = ROOT / "docs" / "LIMITATIONS.md"
CAUSAL_SCOPE_PATH = ROOT / "docs" / "CAUSAL_SCOPE.md"
Expand Down
3 changes: 3 additions & 0 deletions src/rag/qdrant_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,8 @@ def get_qdrant_store() -> QdrantStore:
"""Singleton Qdrant store."""
global _store
if _store is None:
from .env import ensure_rag_env

ensure_rag_env()
_store = QdrantStore()
return _store
88 changes: 88 additions & 0 deletions tests/unit/test_rag_indexing_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Tests for RAG indexing env loading."""

from __future__ import annotations

import os
from pathlib import Path

import pytest

from src.rag.env import REPO_ROOT, ensure_rag_env, reset_rag_env_state


@pytest.fixture(autouse=True)
def _clean_rag_env(monkeypatch: pytest.MonkeyPatch) -> None:
"""Isolate env loading and singleton state between tests."""
reset_rag_env_state()
monkeypatch.delenv("QDRANT_URL", raising=False)
monkeypatch.delenv("RAG_EMBED_MODE", raising=False)
yield
reset_rag_env_state()


def test_ensure_rag_env_loads_dotenv(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
env_file = tmp_path / ".env"
env_file.write_text(
"QDRANT_URL=http://qdrant-from-dotenv:6333\nRAG_EMBED_MODE=hash\n",
encoding="utf-8",
)
monkeypatch.setattr("src.rag.env.REPO_ROOT", tmp_path)
reset_rag_env_state()

ensure_rag_env()

assert os.getenv("QDRANT_URL") == "http://qdrant-from-dotenv:6333"
assert os.getenv("RAG_EMBED_MODE") == "hash"


def test_ensure_rag_env_does_not_override_existing(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
env_file = tmp_path / ".env"
env_file.write_text("QDRANT_URL=http://from-file:6333\n", encoding="utf-8")
monkeypatch.setenv("QDRANT_URL", "http://from-shell:6333")
monkeypatch.setattr("src.rag.env.REPO_ROOT", tmp_path)
reset_rag_env_state()

ensure_rag_env()

assert os.getenv("QDRANT_URL") == "http://from-shell:6333"


def test_get_qdrant_store_reads_dotenv_before_singleton(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
env_file = tmp_path / ".env"
env_file.write_text("QDRANT_URL=http://qdrant-singleton:6333\n", encoding="utf-8")
monkeypatch.setattr("src.rag.env.REPO_ROOT", tmp_path)
reset_rag_env_state()

from src.rag.qdrant_client import get_qdrant_store

store = get_qdrant_store()

assert store.url == "http://qdrant-singleton:6333"


def test_get_embedder_reads_dotenv_before_singleton(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
env_file = tmp_path / ".env"
env_file.write_text("RAG_EMBED_MODE=hash\n", encoding="utf-8")
monkeypatch.setattr("src.rag.env.REPO_ROOT", tmp_path)
reset_rag_env_state()

from src.rag.embedder import get_embedder

embedder = get_embedder()

assert embedder.force_hash is True
assert embedder.mode == "hash"


def test_indexing_module_resolves_repo_root_docs() -> None:
from src.rag import indexing

assert indexing.ROOT == REPO_ROOT
assert indexing.METRICS_PATH == REPO_ROOT / "docs" / "METRICS.md"
assert indexing.METRICS_PATH.exists()
Loading