Per-provider vision blocks; parser repairs local models' almost-JSON

config.toml now carries a [vision.<provider>] block per backend —
model/base_url/key_env — with vision_provider picking the active one,
so the committed file documents every recipe and switching is a
one-line flip. Only the active block applies; typo'd block names and
keys warn like every other config mistake.

First real Ollama run (qwen2.5vl:7b) surfaced what local models emit:
almost-JSON with trailing commas. parse_vision_response now makes one
cheap repair pass before declaring a response unusable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-03 18:39:27 -04:00
co-authored by Claude Fable 5
parent 0358079da4
commit 118503e4e0
6 changed files with 99 additions and 12 deletions
+44
View File
@@ -52,3 +52,47 @@ def test_unknown_toml_keys_warn(tmp_path, monkeypatch):
def test_explicit_missing_config_errors_instead_of_silent_defaults(tmp_path):
with pytest.raises(FileNotFoundError, match="does not exist"):
load_config(tmp_path / "nope.toml")
def test_vision_blocks_apply_only_for_the_active_provider(tmp_path):
p = tmp_path / "config.toml"
p.write_text(
"""
vision_provider = "openai-compatible"
[vision.anthropic]
model = "claude-sonnet-5"
[vision."openai-compatible"]
base_url = "http://localhost:11434/v1"
model = "qwen2.5vl:7b"
key_env = ""
"""
)
cfg = load_config(p)
assert cfg.model == "qwen2.5vl:7b" # the active block's model wins
assert cfg.vision_base_url == "http://localhost:11434/v1"
assert cfg.vision_key_env == ""
# flip the provider: the other block applies, this one is inert
p.write_text(p.read_text().replace('"openai-compatible"\n', '"anthropic"\n', 1))
cfg = load_config(p)
assert cfg.model == "claude-sonnet-5"
assert cfg.vision_base_url == "" # untouched default
def test_vision_block_typos_warn(tmp_path):
p = tmp_path / "config.toml"
p.write_text(
"""
[vision.anthropic]
modle = "oops"
[vision.anthorpic]
model = "claude-sonnet-5"
"""
)
with (
pytest.warns(UserWarning, match="modle"),
pytest.warns(UserWarning, match="anthorpic"),
):
load_config(p)