mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-15 19:32:21 +02:00
Server-side photo editor: natural-language editor agent route
(/api/editor/agent), photo_editor tool family + style guide, image-op
provider proxy with progress, email server hardening, gallery/hwfit
routes, and graceful shutdown.
Source: vendor/photocraft (ad1cff44) editor-server files, merged onto
upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/B_conflicts.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
"""Server health audit fix: the local editor VLM returned 400s when asked for a
|
|
model id it does not serve (the client default 'Qwen3.5-9B-MLX-8bit', or the
|
|
stale caption model 'Qwen/Qwen3-VL-30B'). ``_resolve_model`` maps the requested
|
|
id to one the base actually serves."""
|
|
import asyncio
|
|
|
|
from src import editor_agent_model as eam
|
|
|
|
|
|
def _cache(base, ids):
|
|
eam._MODELS_CACHE[base] = set(ids)
|
|
|
|
|
|
def test_resolve_model_keeps_a_served_request():
|
|
base = "http://x:1/v1"
|
|
_cache(base, ["Qwen/Qwen3.5-9B", "other"])
|
|
assert asyncio.run(eam._resolve_model(base, "Qwen/Qwen3.5-9B", "Qwen/Qwen3.5-9B")) == "Qwen/Qwen3.5-9B"
|
|
|
|
|
|
def test_resolve_model_falls_back_when_requested_is_not_served():
|
|
base = "http://x:2/v1"
|
|
_cache(base, ["Qwen/Qwen3.5-9B"])
|
|
# e.g. the client's MLX build name, or a stale caption model id.
|
|
assert asyncio.run(eam._resolve_model(base, "Qwen3.5-9B-MLX-8bit", "Qwen/Qwen3.5-9B")) == "Qwen/Qwen3.5-9B"
|
|
assert asyncio.run(eam._resolve_model(base, "Qwen/Qwen3-VL-30B", "Qwen/Qwen3.5-9B")) == "Qwen/Qwen3.5-9B"
|
|
|
|
|
|
def test_resolve_model_picks_a_served_id_when_fallback_absent():
|
|
base = "http://x:3/v1"
|
|
_cache(base, ["B", "A"])
|
|
assert asyncio.run(eam._resolve_model(base, "nope", "also-nope")) == "A"
|
|
|
|
|
|
def test_resolve_model_keeps_request_when_served_list_unknown():
|
|
base = "http://x:4/v1"
|
|
_cache(base, [])
|
|
# Server unreachable / no models listed -> don't rewrite the request.
|
|
assert asyncio.run(eam._resolve_model(base, "some/model", "fallback")) == "some/model"
|
|
|
|
|
|
def test_self_check_resolves_the_model_before_calling(monkeypatch):
|
|
base = eam.local_bases()[0]
|
|
_cache(base, ["Qwen/Qwen3.5-9B"])
|
|
seen = {}
|
|
|
|
async def fake_post(b, model, messages, headers, timeout, **kwargs):
|
|
seen["model"] = model
|
|
return "DONE"
|
|
|
|
monkeypatch.setattr(eam, "_post_chat", fake_post)
|
|
out = asyncio.run(eam.self_check(
|
|
"data:image/png;base64,AAAA", "data:image/png;base64,BBBB",
|
|
"make it brighter", model="Qwen3.5-9B-MLX-8bit"))
|
|
assert seen["model"] == "Qwen/Qwen3.5-9B"
|
|
assert out["done"] is True
|
|
|
|
|
|
def test_caption_resolves_the_caption_model(monkeypatch):
|
|
seen = {}
|
|
|
|
async def fake_post(b, model, messages, headers, timeout, **kwargs):
|
|
seen["base"] = b
|
|
seen["model"] = model
|
|
return "a photo"
|
|
|
|
monkeypatch.setattr(eam, "_post_chat", fake_post)
|
|
import src.editor_agent_model as m
|
|
_cache(m.caption_base(), ["Qwen/Qwen3.5-9B"])
|
|
txt = asyncio.run(m.caption_image("data:image/png;base64,AAAA"))
|
|
assert txt == "a photo"
|
|
assert seen["model"] == "Qwen/Qwen3.5-9B"
|