unstuck / tests /test_model_adapter.py
art87able's picture
feat: similar-task recall via Nebius embeddings (keyless; recall degrades off without NEBIUS_API_KEY)
79e91df verified
Raw
History Blame Contribute Delete
4.08 kB
from __future__ import annotations
import pytest
from unstuck.model_adapter import ModelAdapter, _extract_json
from unstuck.prompts import GRANULARITY_RULES
from unstuck.schema import StepValidationError
GOOD = '{"steps":[{"text":"Open the doc","category":"admin","est_minutes":5}]}'
GARBLED = "not json at all"
def make(responses: list[str]):
replies = iter(responses)
def generate(prompt: str) -> str:
return next(replies)
return generate
def test_parses_good_json_first_try_and_sets_task() -> None:
adapter = ModelAdapter(make([GOOD]))
steps = adapter.breakdown("write review")
assert steps.task == "write review"
assert steps.steps[0].text == "Open the doc"
assert steps.steps[0].category == "admin"
assert steps.steps[0].est_minutes == 5
def test_strips_surrounding_prose_and_json_code_fence() -> None:
fenced = f"Here you go:\n```json\n{GOOD}\n```\nDone."
adapter = ModelAdapter(make([fenced]))
steps = adapter.breakdown("write review")
assert steps.task == "write review"
assert steps.steps[0].text == "Open the doc"
def test_repairs_after_one_bad_reply() -> None:
adapter = ModelAdapter(make([GARBLED, GOOD]), max_repairs=1)
steps = adapter.breakdown("write review")
assert steps.task == "write review"
assert steps.steps[0].est_minutes == 5
def test_raises_after_exhausting_repairs() -> None:
adapter = ModelAdapter(make([GARBLED, GARBLED]), max_repairs=1)
with pytest.raises(StepValidationError):
adapter.breakdown("write review")
def test_breakdown_passes_granularity_to_first_prompt() -> None:
prompts: list[str] = []
def generate(prompt: str) -> str:
prompts.append(prompt)
return GOOD
adapter = ModelAdapter(generate)
adapter.breakdown("write review", granularity="tiny")
assert GRANULARITY_RULES["tiny"] in prompts[0]
def test_repair_prompt_keeps_granularity() -> None:
prompts: list[str] = []
def generate(prompt: str) -> str:
prompts.append(prompt)
return [GARBLED, GOOD][len(prompts) - 1]
adapter = ModelAdapter(generate, max_repairs=1)
adapter.breakdown("write review", granularity="tiny")
assert len(prompts) == 2
assert GRANULARITY_RULES["tiny"] in prompts[0]
assert GRANULARITY_RULES["tiny"] in prompts[1]
def test_extracts_json_despite_trailing_prose_with_braces() -> None:
noisy = f"{GOOD}\nNote: adjust {{the estimates}} if needed."
adapter = ModelAdapter(make([noisy]))
steps = adapter.breakdown("write review")
assert steps.steps[0].text == "Open the doc"
def test_extracts_json_when_output_starts_mid_prefill_style() -> None:
prefixed = f"{GOOD} and that is all"
adapter = ModelAdapter(make([prefixed]))
steps = adapter.breakdown("write review")
assert steps.steps[0].est_minutes == 5
def test_skips_invalid_brace_blob_before_real_json() -> None:
noisy = "{oops not json} " + GOOD
adapter = ModelAdapter(make([noisy]))
steps = adapter.breakdown("write review")
assert steps.steps[0].category == "admin"
def test_extract_json_returns_inner_object_when_no_steps_payload_decodes() -> None:
corrupted = (
'{"steps":[{"text":"Find the phone'
"'s cable and plug it in','category':'errand','est_minutes':5},"
'{"text":"Connect the phone via USB","category":"errand","est_minutes":10}]}'
)
payload = _extract_json(corrupted)
assert isinstance(payload, dict)
assert "steps" not in payload
assert payload == {
"text": "Connect the phone via USB",
"category": "errand",
"est_minutes": 10,
}
def test_extract_json_prefers_steps_payload_after_non_steps_object() -> None:
output = (
'First try: {"text":"Connect the phone via USB","category":"errand","est_minutes":10}\n'
'Final: {"steps":[{"text":"Open the doc","category":"admin","est_minutes":5}]}'
)
payload = _extract_json(output)
assert payload == {"steps": [{"text": "Open the doc", "category": "admin", "est_minutes": 5}]}