This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"""[llm] configuration — stack.toml section + env overrides.
|
||||
|
||||
Env contract:
|
||||
LLM_OLLAMA_HOSTS comma-separated Ollama base URLs (beats [llm].ollama)
|
||||
LLM_PG_HOST beats [llm].pg_host (containers set this to "postgres")
|
||||
LLM_DB_PASSWORD required for pg_url(); lives in .env, never stack.toml
|
||||
LLM_OLLAMA_HOSTS comma-separated Ollama base URLs (beats [llm].ollama)
|
||||
LLM_PG_HOST beats [llm].pg_host (containers set this to "postgres")
|
||||
LLM_DB_PASSWORD required for pg_url(); lives in .env, never stack.toml
|
||||
LLM_BUILD_ANN_INDEX beats [llm].build_ann_index ("1"/"true"/"yes" = True)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -18,6 +19,7 @@ class LlmConfig:
|
||||
embed_model: str
|
||||
instruct_model: str
|
||||
embed_dim: int
|
||||
build_ann_index: bool
|
||||
pg_host: str
|
||||
pg_port: int
|
||||
pg_db: str
|
||||
@@ -34,11 +36,17 @@ def load() -> LlmConfig:
|
||||
hosts = tuple(h.strip() for h in hosts_env.split(",") if h.strip())
|
||||
else:
|
||||
hosts = (str(section.ollama),)
|
||||
ann_env = os.environ.get("LLM_BUILD_ANN_INDEX")
|
||||
if ann_env is not None:
|
||||
build_ann_index = ann_env.strip().lower() in ("1", "true", "yes")
|
||||
else:
|
||||
build_ann_index = bool(section.build_ann_index)
|
||||
return LlmConfig(
|
||||
ollama_hosts=hosts,
|
||||
embed_model=str(section.embed_model),
|
||||
instruct_model=str(section.instruct_model),
|
||||
embed_dim=int(section.embed_dim),
|
||||
build_ann_index=build_ann_index,
|
||||
pg_host=os.environ.get("LLM_PG_HOST", str(section.pg_host)),
|
||||
pg_port=int(section.pg_port),
|
||||
pg_db=str(section.pg_db),
|
||||
|
||||
@@ -134,5 +134,8 @@ def index_docs(
|
||||
if stats["indexed"] % 100 == 0:
|
||||
log.info("indexed %(indexed)s (+%(chunks)s chunks)", stats)
|
||||
|
||||
ensure_hnsw(engine, cfg.embed_dim)
|
||||
if cfg.build_ann_index:
|
||||
ensure_hnsw(engine, cfg.embed_dim)
|
||||
else:
|
||||
log.info("ANN index skipped (build_ann_index=false); using exact search")
|
||||
return stats
|
||||
|
||||
@@ -106,6 +106,10 @@ ollama = "http://127.0.0.1:11434" # host-side default; containers override v
|
||||
embed_model = "nomic-embed-text" # 768-dim; bake-off (P33 #564) may revise
|
||||
instruct_model = "llama3.1:8b" # bake-off may revise
|
||||
embed_dim = 768
|
||||
# ANN index build (hnsw/ivfflat) SIGILLs on this Zen2 host — the mirrored
|
||||
# postgres image's pgvector is AVX-512. Leave false until the image is
|
||||
# rebuilt; exact search works regardless.
|
||||
build_ann_index = false
|
||||
pg_host = "127.0.0.1" # host-side default; containers set LLM_PG_HOST=postgres
|
||||
pg_port = 5432
|
||||
pg_db = "llm"
|
||||
|
||||
@@ -9,12 +9,14 @@ class TestLoad:
|
||||
def test_defaults_from_stack_toml(self, monkeypatch):
|
||||
monkeypatch.delenv("LLM_OLLAMA_HOSTS", raising=False)
|
||||
monkeypatch.delenv("LLM_PG_HOST", raising=False)
|
||||
monkeypatch.delenv("LLM_BUILD_ANN_INDEX", raising=False)
|
||||
cfg = llm_config.load()
|
||||
assert cfg.ollama_hosts == ("http://127.0.0.1:11434",)
|
||||
assert cfg.embed_model == "nomic-embed-text"
|
||||
assert cfg.embed_dim == 768
|
||||
assert cfg.pg_host == "127.0.0.1"
|
||||
assert cfg.pg_db == "llm"
|
||||
assert cfg.build_ann_index is False
|
||||
|
||||
def test_env_overrides(self, monkeypatch):
|
||||
monkeypatch.setenv(
|
||||
@@ -26,6 +28,16 @@ class TestLoad:
|
||||
assert cfg.ollama_hosts == ("http://rig:11434", "http://laptop:11434")
|
||||
assert cfg.pg_host == "postgres"
|
||||
|
||||
def test_build_ann_index_env_override_true(self, monkeypatch):
|
||||
monkeypatch.setenv("LLM_BUILD_ANN_INDEX", "1")
|
||||
cfg = llm_config.load()
|
||||
assert cfg.build_ann_index is True
|
||||
|
||||
def test_build_ann_index_env_override_false(self, monkeypatch):
|
||||
monkeypatch.setenv("LLM_BUILD_ANN_INDEX", "no")
|
||||
cfg = llm_config.load()
|
||||
assert cfg.build_ann_index is False
|
||||
|
||||
|
||||
class TestPgUrl:
|
||||
def test_url_includes_password_from_env(self, monkeypatch):
|
||||
|
||||
@@ -11,6 +11,7 @@ CFG = LlmConfig(
|
||||
embed_model="m",
|
||||
instruct_model="g",
|
||||
embed_dim=768,
|
||||
build_ann_index=False,
|
||||
pg_host="x",
|
||||
pg_port=5432,
|
||||
pg_db="llm",
|
||||
@@ -20,7 +21,7 @@ CFG = LlmConfig(
|
||||
DOC = Doc(key="K1", text="Some body text.", metadata={"docket": "D"})
|
||||
|
||||
|
||||
def _run(docs, state_rows, force=False):
|
||||
def _run(docs, state_rows, force=False, cfg=CFG):
|
||||
"""Run index_docs with everything external mocked; return mocks."""
|
||||
store = MagicMock()
|
||||
engine = MagicMock()
|
||||
@@ -30,22 +31,23 @@ def _run(docs, state_rows, force=False):
|
||||
patch("llm.index._engine", return_value=engine),
|
||||
patch("llm.index.vectorstore", return_value=store),
|
||||
patch("llm.index.embed_texts", return_value=[[0.0] * 3]),
|
||||
patch("llm.index.ensure_hnsw") as mock_ensure_hnsw,
|
||||
patch("llm.index.HostPool") as MockPool,
|
||||
):
|
||||
MockPool.return_value.check.return_value = ["http://h1:11434"]
|
||||
stats = index_docs(
|
||||
docs,
|
||||
collection="comments",
|
||||
cfg=CFG,
|
||||
cfg=cfg,
|
||||
pool=MockPool.return_value,
|
||||
force=force,
|
||||
)
|
||||
return stats, store, conn
|
||||
return stats, store, conn, mock_ensure_hnsw
|
||||
|
||||
|
||||
class TestIndexDocs:
|
||||
def test_new_doc_embedded_and_recorded(self):
|
||||
stats, store, conn = _run([DOC], state_rows=[])
|
||||
stats, store, conn, _ensure_hnsw = _run([DOC], state_rows=[])
|
||||
assert stats == {"indexed": 1, "skipped": 0, "chunks": 1}
|
||||
store.add_embeddings.assert_called_once()
|
||||
kwargs = store.add_embeddings.call_args.kwargs
|
||||
@@ -53,12 +55,12 @@ class TestIndexDocs:
|
||||
|
||||
def test_unchanged_doc_skipped(self):
|
||||
h = content_hash(DOC.text)
|
||||
stats, store, _ = _run([DOC], state_rows=[("K1", h)])
|
||||
stats, store, _, _ensure_hnsw = _run([DOC], state_rows=[("K1", h)])
|
||||
assert stats["skipped"] == 1
|
||||
store.add_embeddings.assert_not_called()
|
||||
|
||||
def test_changed_doc_deletes_old_chunks_first(self):
|
||||
stats, store, conn = _run([DOC], state_rows=[("K1", "stalehash")])
|
||||
stats, store, conn, _ensure_hnsw = _run([DOC], state_rows=[("K1", "stalehash")])
|
||||
assert stats["indexed"] == 1
|
||||
deletes = [
|
||||
c
|
||||
@@ -77,10 +79,31 @@ class TestIndexDocs:
|
||||
|
||||
def test_force_reembeds_unchanged(self):
|
||||
h = content_hash(DOC.text)
|
||||
stats, store, _ = _run([DOC], state_rows=[("K1", h)], force=True)
|
||||
stats, store, _, _ensure_hnsw = _run([DOC], state_rows=[("K1", h)], force=True)
|
||||
assert stats["indexed"] == 1
|
||||
|
||||
def test_empty_doc_counts_skipped(self):
|
||||
empty = Doc(key="K2", text=" ", metadata={})
|
||||
stats, store, _ = _run([empty], state_rows=[])
|
||||
stats, store, _, _ensure_hnsw = _run([empty], state_rows=[])
|
||||
assert stats == {"indexed": 0, "skipped": 1, "chunks": 0}
|
||||
|
||||
|
||||
class TestAnnIndexGating:
|
||||
def test_build_ann_index_false_skips_ensure_hnsw(self):
|
||||
_stats, _store, _conn, mock_ensure_hnsw = _run([DOC], state_rows=[], cfg=CFG)
|
||||
mock_ensure_hnsw.assert_not_called()
|
||||
|
||||
def test_build_ann_index_true_calls_ensure_hnsw(self):
|
||||
cfg = LlmConfig(
|
||||
ollama_hosts=("http://h1:11434",),
|
||||
embed_model="m",
|
||||
instruct_model="g",
|
||||
embed_dim=768,
|
||||
build_ann_index=True,
|
||||
pg_host="x",
|
||||
pg_port=5432,
|
||||
pg_db="llm",
|
||||
pg_user="llm",
|
||||
)
|
||||
_stats, _store, _conn, mock_ensure_hnsw = _run([DOC], state_rows=[], cfg=cfg)
|
||||
assert mock_ensure_hnsw.called
|
||||
|
||||
Reference in New Issue
Block a user