Files
ely-eye/backend/tests/test_training_split.py
ZacharyZhang-NY 9cb46c18d3 Keep every file inside the rules the repo already wrote down
The 500-line rule and the one-line-comment rule were enforced only from
memory, so they drifted. bench/filesize_check.py now checks the budget on
every tracked source file, and it passes.

Split, with behaviour proven unchanged by comparing each moved function body
against HEAD byte for byte (53 training bodies, 21 benchmark bodies, 0
differences):

  db.py        503 -> 396   store_search.py     105
  bench/run.py 609 -> 351   query_set.py         80, report.py 211
  benchmarks.py 619 -> 424  benchmark_scoring.py 262
  training.py 1630 ->  94   training_{common,encode,metrics,datasets,lora,retrieval}

TrainingService stays as a thin facade for the CLI; the facade's parameter
lists are pinned against the module functions by inspect, which is what
catches a leaked self.

Deleted the eight top-level functions no file called, after fixing the scan
that had reported 201 false positives by skipping the defining file. The
v1/v2/v3 sweep found nothing to delete and says so: every remaining version
tag is an on-disk format marker, a migration step, a fixture name, or a
third-party model name.

Collapsed 39 multi-line comment blocks (86 lines) to one line each, keeping
the measured facts and error strings that justify the code.

269 tests pass; ruff clean; mypy 24 errors in 9 files against a 24-in-6
baseline, net zero; pyright 4, the same four HEAD measures.
2026-10-08 08:20:21 -04:00

153 lines
5.7 KiB
Python

"""Behaviour tests for the training modules after the 1630-line file was split."""
from __future__ import annotations
import inspect
import json
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import pytest
from ely_eye import training_datasets, training_lora, training_retrieval
from ely_eye.config import Settings
from ely_eye.db import Database
from ely_eye.schemas import (
EvidenceAtom,
Modality,
SourceRecord,
TrainingAdapterKind,
TrustScores,
)
from ely_eye.training import TrainingService
from ely_eye.training_common import sha256_bytes
from ely_eye.training_datasets import (
atom_id_for_path,
atoms_for_path,
latest_cartridge_id,
model_for_kind,
validate_dataset,
)
from ely_eye.training_lora import write_lora_config
def _settings(root: Path) -> Settings:
return Settings(home=root, model_id="base/model", planner_model_id="planner/model")
def _database(root: Path) -> Database:
database = Database(_settings(root))
database.init()
return database
def _sample(sample_id: str) -> dict[str, Any]:
return {"id": sample_id, "task": "recall", "inputs": [{"text": "where are the keys"}], "output": {"text": "the drawer"}}
def test_validate_dataset_reads_jsonl_and_names_the_line_that_broke(tmp_path: Path) -> None:
good = tmp_path / "data.jsonl"
good.write_text(json.dumps(_sample("a")) + "\n" + json.dumps(_sample("b")) + "\n", encoding="utf-8")
samples = validate_dataset(good)
assert [sample.id for sample in samples] == ["a", "b"]
broken = tmp_path / "broken.jsonl"
broken.write_text(json.dumps(_sample("a")) + "\nnot json\n", encoding="utf-8")
with pytest.raises(ValueError, match="Invalid training sample at line 2"):
validate_dataset(broken)
empty = tmp_path / "empty.jsonl"
empty.write_text("\n", encoding="utf-8")
with pytest.raises(ValueError, match="Training dataset is empty"):
validate_dataset(empty)
def test_model_for_kind_uses_the_planner_only_for_the_router(tmp_path: Path) -> None:
settings = _settings(tmp_path)
assert model_for_kind(settings, TrainingAdapterKind.hme_router) == "planner/model"
assert model_for_kind(settings, TrainingAdapterKind.hme_core_lora) == "base/model"
def test_latest_cartridge_id_is_none_until_a_cartridge_is_written(tmp_path: Path) -> None:
database = _database(tmp_path)
assert latest_cartridge_id(database) is None
database.write_cartridge("old", "old", tmp_path / "old", "{}", None, 10, datetime(2026, 1, 1, tzinfo=UTC))
database.write_cartridge("new", "new", tmp_path / "new", "{}", None, 20, datetime(2026, 3, 1, tzinfo=UTC))
assert latest_cartridge_id(database) == "new"
def test_atoms_for_path_follows_the_source_and_falls_back_to_a_hash(tmp_path: Path) -> None:
database = _database(tmp_path)
paper = tmp_path / "paper.md"
paper.write_text("the keys are in the drawer", encoding="utf-8")
other = tmp_path / "other.md"
database.upsert_sources(
[
SourceRecord(source_id=source_id, path=str(path), kind="text", mime="text/markdown", sha256="a", size_bytes=1)
for source_id, path in (("paper", paper), ("other", other))
]
)
trust = TrustScores(parser=1.0)
database.upsert_atoms(
[
EvidenceAtom(
atom_id="paper:text",
modality=Modality.text,
source_id="paper",
source=str(paper),
text="drawer",
trust=trust,
),
EvidenceAtom(
atom_id="other:text",
modality=Modality.text,
source_id="other",
source=str(other),
text="shelf",
trust=trust,
),
]
)
assert [atom.atom_id for atom in atoms_for_path(database, paper)] == ["paper:text"]
expected = f"notes:{sha256_bytes(b'cold')[:12]}"
assert atom_id_for_path(database, tmp_path / "notes.md", "cold") == expected
assert atom_id_for_path(database, paper, "cold") == "paper:text"
def test_write_lora_config_ties_weights_only_for_the_mtp_adapter(tmp_path: Path) -> None:
settings = _settings(tmp_path)
core = write_lora_config(settings, tmp_path / "core", TrainingAdapterKind.hme_core_lora, rank=8, alpha=16)
payload = json.loads(core.read_text(encoding="utf-8"))
assert payload["base_model"] == "base/model"
assert payload["adapter_kind"] == "hme_core_lora"
assert (payload["rank"], payload["alpha"]) == (8, 16)
assert payload["ensure_weight_tying"] is False
mtp = write_lora_config(settings, tmp_path / "mtp", TrainingAdapterKind.hme_visual_mtp, model_id="other/model")
tied = json.loads(mtp.read_text(encoding="utf-8"))
assert tied["base_model"] == "other/model"
assert tied["ensure_weight_tying"] is True
FACADE = {
"build_prd_dataset": training_datasets,
"build_visual_dataset": training_datasets,
"build_router_dataset": training_datasets,
"build_retrieval_dataset": training_datasets,
"train_lora": training_lora,
"train_retrieval_lora": training_retrieval,
}
def test_the_service_facade_passes_every_argument_the_module_expects(tmp_path: Path) -> None:
data = tmp_path / "data.jsonl"
data.write_text(json.dumps(_sample("a")) + "\n", encoding="utf-8")
service = TrainingService(_settings(tmp_path), _database(tmp_path))
assert [sample.id for sample in service.validate_dataset(data)] == ["a"]
for name, module in FACADE.items():
# A bound method has no ``self`` parameter; the module function carries the two injected ones.
method = list(inspect.signature(getattr(service, name)).parameters)
function = list(inspect.signature(getattr(module, name)).parameters)[2:]
assert method == function, name