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
49 changes: 49 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from io import StringIO

import pytest
from rich.console import Console

from vaultrag import cli
from vaultrag.config import StartupConfigError, get_settings


def test_invalid_embedder_raises_structured_startup_error(monkeypatch):
monkeypatch.setenv("EMBEDDER", "typo")

with pytest.raises(StartupConfigError) as excinfo:
get_settings()

error = excinfo.value
assert error.variable == "EMBEDDER"
assert error.value == "typo"
assert error.valid_values == ("local", "fake")
assert "Invalid EMBEDDER='typo'" in str(error)
assert "valid values: local, fake" in str(error)


def test_invalid_llm_raises_structured_startup_error(monkeypatch):
monkeypatch.setenv("LLM", "typo")

with pytest.raises(StartupConfigError) as excinfo:
get_settings()

error = excinfo.value
assert error.variable == "LLM"
assert error.value == "typo"
assert error.valid_values == ("groq", "fake")
assert "Invalid LLM='typo'" in str(error)
assert "valid values: groq, fake" in str(error)


def test_cli_reports_invalid_embedder_without_traceback(monkeypatch):
output = StringIO()
monkeypatch.setenv("EMBEDDER", "typo")
monkeypatch.setattr(cli, "console", Console(file=output, width=120, color_system=None))

assert cli.main(["health"]) == 2

rendered = output.getvalue()
assert "configuration error" in rendered
assert "Invalid EMBEDDER='typo'" in rendered
assert "valid values: local, fake" in rendered
assert "Traceback" not in rendered
14 changes: 9 additions & 5 deletions vaultrag/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from rich.markup import escape
from rich.table import Table

from .config import get_settings
from .config import StartupConfigError, get_settings
from .conflicts import corpus_freshness, detect_conflicts, detect_stale
from .db import init_schema
from .embeddings import get_embedder
Expand Down Expand Up @@ -153,7 +153,7 @@ async def _health(args) -> int:


def _diff(args) -> int:
from .evaluate import EvalReport, CaseResult
from .evaluate import CaseResult, EvalReport

def load(p):
d = json.loads(Path(p).read_text())
Expand Down Expand Up @@ -203,9 +203,13 @@ def main(argv: list[str] | None = None) -> int:
d.set_defaults(func=_diff)

args = p.parse_args(argv)
if hasattr(args, "afunc"):
return asyncio.run(args.afunc(args))
return args.func(args)
try:
if hasattr(args, "afunc"):
return asyncio.run(args.afunc(args))
return args.func(args)
except StartupConfigError as exc:
console.print(f"[red]configuration error[/] {escape(str(exc))}")
return 2


if __name__ == "__main__":
Expand Down
46 changes: 37 additions & 9 deletions vaultrag/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,46 @@
import os
from dataclasses import dataclass

VALID_EMBEDDERS = ("local", "fake")
VALID_LLMS = ("groq", "fake")


class StartupConfigError(ValueError):
"""Raised when startup configuration is invalid."""

def __init__(self, variable: str, value: str, valid_values: tuple[str, ...]) -> None:
self.variable = variable
self.value = value
self.valid_values = valid_values
super().__init__(
f"Invalid {variable}={value!r}; valid values: {', '.join(valid_values)}"
)


@dataclass(frozen=True)
class Settings:
database_url: str = os.getenv(
"DATABASE_URL", "postgresql://vaultrag:vaultrag@localhost:5433/vaultrag"
)
embedder: str = os.getenv("EMBEDDER", "local")
embed_model: str = os.getenv("EMBED_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
llm: str = os.getenv("LLM", "groq")
groq_api_key: str | None = os.getenv("GROQ_API_KEY")
groq_model: str = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
database_url: str
embedder: str
embed_model: str
llm: str
groq_api_key: str | None
groq_model: str


def _validated_choice(variable: str, value: str, valid_values: tuple[str, ...]) -> str:
if value in valid_values:
return value
raise StartupConfigError(variable, value, valid_values)


def get_settings() -> Settings:
return Settings()
return Settings(
database_url=os.getenv(
"DATABASE_URL", "postgresql://vaultrag:***@localhost:5433/vaultrag"
),
embedder=_validated_choice("EMBEDDER", os.getenv("EMBEDDER", "local"), VALID_EMBEDDERS),
embed_model=os.getenv("EMBED_MODEL", "sentence-transformers/all-MiniLM-L6-v2"),
llm=_validated_choice("LLM", os.getenv("LLM", "groq"), VALID_LLMS),
groq_api_key=os.getenv("GROQ_API_KEY"),
groq_model=os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile"),
)
Loading