A hall visual that stays on the general branch uses the Efficient model from the 2026-10-08 intelligence-radar tree. render-hall writes only portraits whose files are still absent. Assistant: grok Assistant-Session: 01a11ab6-1534-7d63-8606-662b92a80ed6
141 lines
4.5 KiB
Python
141 lines
4.5 KiB
Python
"""Decision tree and hall portrait planning."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from llm_connect.cli import main
|
|
from llm_connect.hall_portraits import plan_missing_portraits, reveal_portrait
|
|
from llm_connect.image_selection import classify_image_branch, selection_for_prompt
|
|
|
|
HALL_PROMPT = (
|
|
"Constellation dialect. Square, dark indigo. A gold-wire helix. "
|
|
"Fine technical-illustration linework, no logos, no readable text."
|
|
)
|
|
|
|
|
|
def test_hall_house_prompt_is_general_efficient() -> None:
|
|
selection = selection_for_prompt(HALL_PROMPT)
|
|
assert classify_image_branch(HALL_PROMPT) == "general"
|
|
assert selection.tier == "efficient"
|
|
assert selection.model == "google/gemini-nano-banana-2.1"
|
|
assert selection.quote_usd == 0.04
|
|
assert selection.max_cost_usd == 0.08
|
|
|
|
|
|
def test_negated_text_does_not_become_text_heavy() -> None:
|
|
assert classify_image_branch("no logos, no readable text, pale gold") == "general"
|
|
|
|
|
|
def test_abstract_marks_stay_on_the_general_branch() -> None:
|
|
prompt = (
|
|
"a ledger glows with the words rendered as abstract marks, not readable text. "
|
|
"no logos, no lettering"
|
|
)
|
|
assert classify_image_branch(prompt) == "general"
|
|
|
|
|
|
def test_requested_words_take_the_text_branch() -> None:
|
|
selection = selection_for_prompt("A poster whose headline is the words HELIX")
|
|
assert selection.branch == "text_heavy"
|
|
assert selection.model == "openai/gpt-image-2.5-sunburst"
|
|
|
|
|
|
def test_vector_and_self_host_follow_the_tree() -> None:
|
|
assert selection_for_prompt("Export as a vector svg icon").model.startswith("recraft/")
|
|
try:
|
|
selection_for_prompt("self-host the open weights on our own hardware")
|
|
except Exception as exc:
|
|
assert "OpenRouter" in str(exc)
|
|
else:
|
|
raise AssertionError("self-host branch should refuse")
|
|
|
|
|
|
def _entry(visual: str, comment: str | None, entry_id: str = "hall-worker-ada-1") -> str:
|
|
commented = f"<!--  -->\n" if comment else ""
|
|
return f"""---
|
|
id: {entry_id}
|
|
status: draft
|
|
---
|
|
|
|
# Ada — a seat
|
|
|
|
## Visual prompt
|
|
|
|
> {visual}
|
|
|
|
{commented}
|
|
## Handoff
|
|
|
|
Next.
|
|
"""
|
|
|
|
|
|
def test_plan_skips_a_portrait_that_is_already_on_disk(tmp_path: Path) -> None:
|
|
entries = tmp_path / "entries"
|
|
visuals = tmp_path / "visuals"
|
|
entries.mkdir()
|
|
visuals.mkdir()
|
|
(entries / "one.md").write_text(_entry(HALL_PROMPT, "Ada"), encoding="utf-8")
|
|
(entries / "two.md").write_text(_entry(HALL_PROMPT, None, "hall-worker-bea-2"), encoding="utf-8")
|
|
(visuals / "bea-file.jpg").write_bytes(b"x")
|
|
# two.md has no comment, so its derived file is the id. Put that file on disk.
|
|
(visuals / "hall-worker-bea-2.jpg").write_bytes(b"x")
|
|
|
|
jobs = plan_missing_portraits(tmp_path)
|
|
assert [job.entry_id for job in jobs] == ["hall-worker-ada-1"]
|
|
assert jobs[0].output_path == visuals / "ada.jpg"
|
|
assert jobs[0].relative_path == "../visuals/ada.jpg"
|
|
assert jobs[0].alt == "Ada"
|
|
assert "no readable text" in jobs[0].prompt
|
|
|
|
|
|
def test_reveal_portrait_uncomments_the_intended_line() -> None:
|
|
text = _entry(HALL_PROMPT, "Ada")
|
|
updated = reveal_portrait(text, "../visuals/ada.jpg", "Ada")
|
|
assert "<!--" not in updated
|
|
assert "" in updated
|
|
assert "status: draft" in updated
|
|
|
|
|
|
def test_plan_reads_a_prompt_that_is_not_a_blockquote(tmp_path: Path) -> None:
|
|
entries = tmp_path / "entries"
|
|
visuals = tmp_path / "visuals"
|
|
entries.mkdir()
|
|
visuals.mkdir()
|
|
body = """---
|
|
id: hall-worker-cy-3
|
|
status: draft
|
|
---
|
|
|
|
# Cy — a seat
|
|
|
|
## Visual prompt
|
|
|
|
**Dialect:** Constellation. Square, no logos, no readable text.
|
|
|
|
A gold-wire orrery suspended in dark indigo.
|
|
|
|
## Handoff
|
|
|
|
Next.
|
|
"""
|
|
(entries / "three.md").write_text(body, encoding="utf-8")
|
|
jobs = plan_missing_portraits(tmp_path)
|
|
assert len(jobs) == 1
|
|
assert "orrery" in jobs[0].prompt
|
|
assert jobs[0].output_path.name == "hall-worker-cy-3.jpg"
|
|
|
|
|
|
def test_cli_plan_is_dry_and_names_the_efficient_model(tmp_path: Path, capsys) -> None:
|
|
entries = tmp_path / "entries"
|
|
visuals = tmp_path / "visuals"
|
|
entries.mkdir()
|
|
visuals.mkdir()
|
|
(entries / "one.md").write_text(_entry(HALL_PROMPT, "Ada"), encoding="utf-8")
|
|
code = main(["image", "plan", "--hall", str(tmp_path), "--json"])
|
|
assert code == 0
|
|
payload = json.loads(capsys.readouterr().out)
|
|
assert payload["count"] == 1
|
|
assert payload["jobs"][0]["model"] == "google/gemini-nano-banana-2.1"
|
|
assert payload["max_batch_cost_usd"] == 0.08
|
|
assert not (visuals / "ada.jpg").exists()
|