Replace vision hotswap with native multimodal profiles
This commit is contained in:
1 parent
8a45a0d851
commit
cb07779f5a
19 files changed
+169
-719
No files matched your search
@@ -1 +1 @@
|
||||
ExecStart=/usr/bin/mock-llama --ctx-size 73728
|
||||
ExecStart=/usr/bin/mock-llama --ctx-size 76800
|
||||
@@ -1 +1 @@
|
||||
ExecStart=/usr/bin/mock-llama --ctx-size 73728
|
||||
ExecStart=/usr/bin/mock-llama --ctx-size 76800
|
||||
@@ -25,7 +25,7 @@ def current_ctx() -> int:
|
||||
return int(line.split("--ctx-size")[1].split()[0])
|
||||
except (OSError, ValueError, IndexError):
|
||||
pass
|
||||
return 73728
|
||||
return 76800
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
+4
-4
@@ -124,7 +124,7 @@ import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
ids={m["id"]:m for m in d["data"]}
|
||||
assert set(ids)=={"qwen-fast","qwen-medium","qwen-long"}, ids
|
||||
assert ids["qwen-fast"]["context_length"]==73728
|
||||
assert ids["qwen-fast"]["context_length"]==76800
|
||||
assert ids["qwen-medium"]["context_length"]==94208
|
||||
assert ids["qwen-long"]["context_length"]==131072
|
||||
' && ok "drei virtuelle Modelle mit korrekten Context Windows" || bad "/v1/models"
|
||||
@@ -138,7 +138,7 @@ import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
assert d["current_profile"]=="fast", d
|
||||
assert d["upstream"]["reachable"] is True, d
|
||||
assert d["upstream"]["ctx"]==73728, d
|
||||
assert d["upstream"]["ctx"]==76800, d
|
||||
' && ok "Status zeigt Profil fast + erreichbares Upstream" || bad "/status"
|
||||
|
||||
# --- 3. Normales Forwarding (non-streaming) -------------------------------------
|
||||
@@ -199,7 +199,7 @@ echo "$RESP" | python3 -m json.tool
|
||||
echo "$RESP" | python3 -c '
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
assert d["profile"]=="fast" and d["context_length"]==73728, d
|
||||
assert d["profile"]=="fast" and d["context_length"]==76800, d
|
||||
' && ok "Profil fast wieder aktiv" || bad "Profilwechsel fast"
|
||||
|
||||
# --- 8. Virtuelles Modell triggert Profilwechsel ----------------------------------------
|
||||
@@ -270,7 +270,7 @@ echo "$RESP" | python3 -m json.tool
|
||||
echo "$RESP" | python3 -c '
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
assert d["profile"]=="fast" and d["model"]=="mock-model-73728", d
|
||||
assert d["profile"]=="fast" and d["model"]=="mock-model-76800", d
|
||||
' && ok "Recovery: /fast wartet auf Upstream, dann Erfolg" || bad "Recovery"
|
||||
|
||||
# --- 11. Bildgenerierung (Mock-Worker) -------------------------------------------------
|
||||
|
||||
@@ -23,7 +23,11 @@ from router_support import ( # noqa: E402
|
||||
enforce_artifact_retention,
|
||||
load_profile_registry,
|
||||
)
|
||||
from ai_profile_router import _normalize_vision_image # noqa: E402
|
||||
from ai_profile_router import ( # noqa: E402
|
||||
_normalize_chat_image,
|
||||
_normalize_chat_images,
|
||||
_request_has_image,
|
||||
)
|
||||
|
||||
|
||||
class AuthPolicyTests(unittest.TestCase):
|
||||
@@ -47,9 +51,9 @@ class RuntimeStoreTests(unittest.TestCase):
|
||||
def test_atomic_roundtrip_and_delete(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
store = RuntimeStore(str(Path(temp) / "state.json"))
|
||||
store.save(worker="vision", worker_pid=123, last_profile="fast")
|
||||
store.save(worker="image", worker_pid=123, last_profile="fast")
|
||||
self.assertEqual(store.load()["worker_pid"], 123)
|
||||
store.clear_worker("vision")
|
||||
store.clear_worker("image")
|
||||
state = store.load()
|
||||
self.assertNotIn("worker", state)
|
||||
self.assertEqual(state["last_profile"], "fast")
|
||||
@@ -75,18 +79,38 @@ class ProfileRegistryTests(unittest.TestCase):
|
||||
load_profile_registry("/definitely/missing/profiles.json")
|
||||
|
||||
|
||||
class VisionInputTests(unittest.TestCase):
|
||||
class ChatImageInputTests(unittest.TestCase):
|
||||
def test_small_png_data_url_is_accepted(self) -> None:
|
||||
value = "data:image/png;base64,iVBORw0KGgo="
|
||||
self.assertEqual(_normalize_vision_image(value), value)
|
||||
self.assertEqual(_normalize_chat_image(value), value)
|
||||
|
||||
def test_remote_url_is_denied_by_default(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "deaktiviert"):
|
||||
_normalize_vision_image("https://example.com/private.png")
|
||||
_normalize_chat_image("https://example.com/private.png")
|
||||
|
||||
def test_invalid_base64_is_rejected(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "Base64"):
|
||||
_normalize_vision_image("data:image/png;base64,not!base64")
|
||||
_normalize_chat_image("data:image/png;base64,not!base64")
|
||||
|
||||
def test_multimodal_part_is_retained_for_direct_forwarding(self) -> None:
|
||||
value = "data:image/png;base64,iVBORw0KGgo="
|
||||
request = {
|
||||
"model": "qwen-fast",
|
||||
"messages": [{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Was ist zu sehen?"},
|
||||
{"type": "image_url", "image_url": {"url": value}},
|
||||
],
|
||||
}],
|
||||
}
|
||||
self.assertTrue(_request_has_image(request))
|
||||
normalized = _normalize_chat_images(request)
|
||||
self.assertEqual(
|
||||
normalized["messages"][0]["content"][1]["image_url"]["url"],
|
||||
value,
|
||||
)
|
||||
self.assertEqual(request, normalized)
|
||||
|
||||
|
||||
class RetentionTests(unittest.TestCase):
|
||||
|
||||
Reference in new issue
Block a user