mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 20:02:20 +02:00
feat(photo-editor): NL editor planner, image ops, email/gallery server
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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c74f475679
commit
eb940f24a3
@@ -0,0 +1,71 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user