Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
633 changes: 27 additions & 606 deletions app/_launch_runtime.py

Large diffs are not rendered by default.

759 changes: 759 additions & 0 deletions app/routers/llm.py

Large diffs are not rendered by default.

6 changes: 0 additions & 6 deletions tests/fixtures/architecture_wire_inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,6 @@
"classification": "symbol_importable",
"reason": "Extracts selected launch symbols with AST/exec; migrate to direct imports when that domain moves."
},
{
"file": "tests/test_minimax_song_writer_prompt.py",
"target": "app/_launch_runtime.py",
"classification": "symbol_importable",
"reason": "Extracts selected launch symbols with AST/exec; migrate to direct imports when that domain moves."
},
{
"file": "tests/test_model_selection_persistence.py",
"target": "app/_launch_runtime.py",
Expand Down
22 changes: 11 additions & 11 deletions tests/fixtures/route_table.json
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 80
},
{
Expand All @@ -818,7 +818,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 81
},
{
Expand All @@ -828,7 +828,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 82
},
{
Expand All @@ -838,7 +838,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 83
},
{
Expand All @@ -848,7 +848,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 84
},
{
Expand All @@ -858,7 +858,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 85
},
{
Expand All @@ -868,7 +868,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 86
},
{
Expand All @@ -878,7 +878,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 87
},
{
Expand All @@ -898,7 +898,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 89
},
{
Expand All @@ -908,7 +908,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 90
},
{
Expand All @@ -918,7 +918,7 @@
"status_code": null,
"response_model": null,
"include_in_schema": null,
"source": "app/_launch_runtime.py",
"source": "app/routers/llm.py",
"ordinal": 91
},
{
Expand Down
3 changes: 2 additions & 1 deletion tests/test_h3_window_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,14 +380,15 @@ def make_plan(*, golden: bool, dialogue: bool) -> dict:
def test_ui_and_runtime_use_explicit_prompt_arrays(self):
handler = (APP / "wgp.py").read_text(encoding="utf-8")
launch = (APP / "_launch_runtime.py").read_text(encoding="utf-8")
llm_router = (APP / "routers" / "llm.py").read_text(encoding="utf-8")
store = (ROOT / "ui" / "src" / "stores" / "useStore.ts").read_text(encoding="utf-8")
advanced = (ROOT / "ui" / "src" / "components" / "Sidebar" / "AdvancedSettings.tsx").read_text(encoding="utf-8")
prompt_input = (ROOT / "ui" / "src" / "components" / "Sidebar" / "PromptInput.tsx").read_text(encoding="utf-8")
main_content = (ROOT / "ui" / "src" / "components" / "MainContent" / "MainContent.tsx").read_text(encoding="utf-8")
guide = APP / "services" / "llm_guides" / "enhance" / "minimax_h3_sliding_windows.md"
self.assertIn("h3_window_prompts=None", handler)
self.assertIn("Using {len(prompts)} explicit", handler)
self.assertIn('/api/v1/llm/plan-h3-windows', launch)
self.assertIn('/api/v1/llm/plan-h3-windows', llm_router)
self.assertIn("h3_window_plan_signature", launch)
self.assertIn("api.planH3Windows", store)
self.assertIn("Plan Prompt Across Windows", advanced)
Expand Down
117 changes: 117 additions & 0 deletions tests/test_llm_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""ASGI contracts for the extracted /api/v1/llm FastAPI routers."""

from __future__ import annotations

from unittest.mock import patch

from fastapi import FastAPI
from fastapi.testclient import TestClient

from routers.llm import create_llm_prompt_router, create_llm_router


LLM_HTTP_SURFACE = [
("GET", "/api/v1/llm/status", "llm_status"),
("POST", "/api/v1/llm/load", "llm_load"),
("POST", "/api/v1/llm/unload", "llm_unload"),
("GET", "/api/v1/llm/models", "list_llm_models"),
("GET", "/api/v1/llm/stream-status", "llm_stream_status"),
("POST", "/api/v1/llm/generate", "llm_generate"),
("POST", "/api/v1/llm/test", "llm_test"),
("POST", "/api/v1/llm/write-song", "llm_write_song"),
]

LLM_PROMPT_HTTP_SURFACE = [
("POST", "/api/v1/llm/plan-h3-windows", "llm_plan_h3_windows"),
("POST", "/api/v1/llm/enhance-prompt", "llm_enhance_prompt"),
("POST", "/api/v1/llm/describe-image", "llm_describe_image"),
]


def _core_router(**overrides):
kwargs = dict(
get_services_config=lambda: {"llm_device": "cpu"},
effective_llm_routing=lambda _services: ("local", "test-model", ""),
llm_provider_credentials=lambda _provider, _services, remote_url="": ("", remote_url),
llm_default_device=lambda: "cpu",
default_llm_repo="test-repo",
ensure_llm_loaded=lambda: None,
comic_writing_llm=lambda _body: None,
)
kwargs.update(overrides)
return create_llm_router(**kwargs)


def _prompt_router(**overrides):
kwargs = dict(
get_services_config=lambda: {"nsfw_mode": False},
effective_llm_routing=lambda _services: ("local", "test-model", ""),
public_llm_providers=frozenset({"openai", "anthropic", "minimax", "grok", "deepseek"}),
ensure_llm_loaded=lambda: None,
get_model_def=lambda _model_type: {"architecture": "minimax_h3", "fps": 24},
get_lora_dir=lambda _model_type: "/tmp",
get_cached_hardware=lambda: {"gpu_vram_gb": 0.0},
get_enhancer_enabled=lambda: 0,
enhance_with_wangp=lambda *_args, **_kwargs: {"original": "x", "enhanced": "y"},
)
kwargs.update(overrides)
return create_llm_prompt_router(**kwargs)


def _route_surface(router):
found = []
for route in router.routes:
methods = sorted(
method for method in (route.methods or set()) if method not in {"HEAD", "OPTIONS"}
)
for method in methods:
found.append((method, route.path, route.endpoint.__name__))
return found


def test_llm_router_exposes_the_extracted_http_surface():
assert _route_surface(_core_router()) == LLM_HTTP_SURFACE


def test_llm_prompt_router_exposes_the_extracted_http_surface():
assert _route_surface(_prompt_router()) == LLM_PROMPT_HTTP_SURFACE


def test_generate_and_write_song_require_their_payloads():
app = FastAPI()
app.include_router(_core_router())
client = TestClient(app)
assert client.post("/api/v1/llm/generate", json={}).json()["detail"] == "prompt is required"
assert client.post("/api/v1/llm/write-song", json={}).json()["detail"] == "description is required"


def test_generate_returns_llm_text_without_loading_wgp():
app = FastAPI()
app.include_router(_core_router())
client = TestClient(app)
with patch("services.llm_service.generate", return_value="hello") as generate:
response = client.post("/api/v1/llm/generate", json={"prompt": "Say hi"})
assert response.status_code == 200
assert response.json() == {"text": "hello"}
generate.assert_called_once()


def test_plan_h3_windows_rejects_non_h3_models():
app = FastAPI()
app.include_router(_prompt_router(get_model_def=lambda _model_type: {"architecture": "ltx2"}))
client = TestClient(app)
response = client.post("/api/v1/llm/plan-h3-windows", json={
"prompt": "Clark turns toward the truck",
"model_type": "ltx2",
})
assert response.status_code == 400
assert "MiniMax H3" in response.json()["detail"]


def test_describe_image_requires_a_path():
app = FastAPI()
app.include_router(_prompt_router())
client = TestClient(app)
response = client.post("/api/v1/llm/describe-image", json={})
assert response.status_code == 400
assert response.json()["detail"] == "image_path is required"
7 changes: 4 additions & 3 deletions tests/test_minimax_h3.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
_INT8_CONVROT_PATH = _APP / "shared" / "qtypes" / "int8_convrot.py"
_WGP_PATH = _APP / "wgp.py"
_LAUNCH_PATH = _APP / "_launch_runtime.py"
_LLM_ROUTER_PATH = _APP / "routers" / "llm.py"
_LLM_SERVICE_PATH = _APP / "services" / "llm_service.py"
_DEFAULT_PATH = _APP / "defaults" / "minimax_h3.json"
_LEGACY_DEFAULT_PATH = _APP / "defaults" / "minimax_h3_legacy.json"
Expand Down Expand Up @@ -945,10 +946,10 @@ def test_h3_prompt_guides_cover_native_audio_and_director(self):
self.assertIn("proper names", ref2va_dialect_guide)

def test_h3_enhance_path_preserves_context_ir_contract(self):
launch = _read(_LAUNCH_PATH)
llm_router = _read(_LLM_ROUTER_PATH)
llm_service = _read(_LLM_SERVICE_PATH)
self.assertIn("needs_h3_context_ir", launch)
self.assertIn("enhancer_enabled > 0 and not needs_h3_context_ir", launch)
self.assertIn("needs_h3_context_ir", llm_router)
self.assertIn("enhancer_enabled > 0 and not needs_h3_context_ir", llm_router)
self.assertIn("is_h3_context_ir", llm_service)
self.assertIn("is_h3_ref2va", llm_service)
self.assertIn("is_h3_structured = is_h3_context_ir or is_h3_ref2va", llm_service)
Expand Down
32 changes: 8 additions & 24 deletions tests/test_minimax_song_writer_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,15 @@

from __future__ import annotations

import ast
import re
import unittest
from pathlib import Path


def _load_functions(*names: str):
source = Path(__file__).parents[1].joinpath("app", "_launch_runtime.py").read_text(encoding="utf-8")
tree = ast.parse(source)
selected = [
node for node in tree.body
if isinstance(node, ast.FunctionDef) and node.name in names
]
namespace = {"re": re}
exec(compile(ast.Module(body=selected, type_ignores=[]), "_launch_runtime.py", "exec"), namespace)
return tuple(namespace[name] for name in names)


_minimax_song_request_prompt, _normalize_minimax_song_output, _parse_song_output, _parse_lyria_output, _optional_lyria_warning, _ace_song_request_prompt = _load_functions(
"_minimax_song_request_prompt",
"_normalize_minimax_song_output",
"_parse_song_output",
"_parse_lyria_output",
"_optional_lyria_warning",
"_ace_song_request_prompt",

from routers.llm import (
_ace_song_request_prompt,
_minimax_song_request_prompt,
_normalize_minimax_song_output,
_optional_lyria_warning,
_parse_lyria_output,
_parse_song_output,
)


Expand Down