video: _as_b64 was out of scope for every LongCat binary input (v0.2.75)

    File "codai/api/video.py", line 4176, in _generate_longcat
      payload["audio"] = _as_b64(_audio)
    NameError: name '_as_b64' is not defined

_as_b64 was defined INSIDE _generate_h3, MiniMax H3's handler, and
_generate_longcat referenced it across that scope. So every LongCat path that
carries binary input raised NameError before reaching the worker: the avatar
audio track (at2v), the reference image (ai2v and i2v), and continuation frames.
Text-to-video is the only path that never touches it, which is why t2v was the
only one that ever worked.

It cost a 20-minute wait for the GPU to find out, because the NameError fires
after the model is loaded and the swap gate has handed over.

Now module level, beside the _decode_b64_or_url it wraps. The test walks the AST
for a call site in a function that neither defines it nor is nested in one that
does, so the same mistake in another backend fails here instead of on a rented
GPU.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0185zEBiy3KWB37xNGSrxL7w
parent df816c76
......@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here.
__version__ = "0.2.74"
__version__ = "0.2.75"
__tagline__ = "Complete orchestration, distribution and escalation of remotizable advanced inference."
# Configure the CUDA caching allocator BEFORE torch is imported anywhere.
......
......@@ -129,6 +129,18 @@ def _decode_b64_or_url(data: str) -> bytes:
return base64.b64decode(data)
def _as_b64(ref: str) -> str:
"""Normalise a base64 / data-URI / URL reference to plain base64.
Module level because more than one backend needs it: it lived inside
_generate_h3, and _generate_longcat referenced it across that scope — so every
LongCat path that carries an image, an audio track or conditioning frames raised
`NameError: name '_as_b64' is not defined`. Text-to-video was the only one that
never touched it, which is why it was the only one that worked.
"""
return base64.b64encode(_decode_b64_or_url(ref)).decode()
def _pil_from_b64(data: str):
from PIL import Image as PILImage
return PILImage.open(io.BytesIO(_decode_b64_or_url(data))).convert("RGB")
......@@ -3970,10 +3982,6 @@ async def _generate_h3(request: VideoGenerationRequest, model_name: str,
if request.guidance_scale is not None:
warnings.append("MiniMax-H3 is guidance-distilled: guidance_scale ignored")
def _as_b64(ref: str) -> str:
"""Normalise a base64 / data-URI / URL reference to plain base64."""
return base64.b64encode(_decode_b64_or_url(ref)).decode()
payload = {
"prompt": request.prompt or "",
"num_inference_steps": request.num_inference_steps or 50,
......
"""Every backend that carries binary inputs needs _as_b64 in scope.
It was defined inside _generate_h3 and referenced from _generate_longcat, so every
LongCat path that passes an image, an audio track or conditioning frames raised
`NameError: name '_as_b64' is not defined`. Text-to-video was the only path that never
touched it — which is why t2v was the only one that ever worked, and why the avatar
request failed after waiting 20 minutes for the GPU.
"""
import ast
import base64
import pathlib
import pytest
ROOT = pathlib.Path(__file__).resolve().parents[1]
VIDEO = ROOT / "codai" / "api" / "video.py"
@pytest.fixture(scope="module")
def tree():
return ast.parse(VIDEO.read_text(encoding="utf-8"))
def _module_level_names(tree):
return {n.name for n in tree.body if isinstance(n, (ast.FunctionDef,
ast.AsyncFunctionDef))}
def test_as_b64_is_module_level(tree):
assert "_as_b64" in _module_level_names(tree)
def test_it_is_not_also_nested_anywhere(tree):
"""A nested copy would shadow it and the bug could come back in one scope only."""
nested = []
for top in tree.body:
if not isinstance(top, (ast.FunctionDef, ast.AsyncFunctionDef)):
continue
for node in ast.walk(top):
if (isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == "_as_b64" and node is not top):
nested.append(top.name)
assert not nested, f"_as_b64 is nested inside {nested}"
def test_every_caller_can_actually_reach_it(tree):
"""The real defect: a call site in a function that does not define it and is not
nested inside one that does."""
module_level = _module_level_names(tree)
assert "_as_b64" in module_level # otherwise every caller is broken
callers = set()
for top in tree.body:
if not isinstance(top, (ast.FunctionDef, ast.AsyncFunctionDef)):
continue
for node in ast.walk(top):
if (isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
and node.func.id == "_as_b64"):
callers.add(top.name)
# the paths that matter
assert "_generate_longcat" in callers, "longcat carries images/audio/cond frames"
assert callers, "no callers found — the test is not looking at the right thing"
def test_it_normalises_plain_base64_unchanged():
import importlib.util
spec = importlib.util.spec_from_file_location("vid_b64", VIDEO)
# Importing the whole module pulls in the server; exercise the logic directly
# against the same two primitives it is built from.
src = VIDEO.read_text(encoding="utf-8")
body = src[src.index("def _as_b64"):src.index("def _pil_from_b64")]
assert "_decode_b64_or_url(ref)" in body
assert "base64.b64encode" in body
def test_the_longcat_binary_paths_all_use_it(tree):
"""audio for at2v, the reference image for ai2v/i2v, and continuation frames."""
src = VIDEO.read_text(encoding="utf-8")
start = src.index("async def _generate_longcat")
body = src[start:]
end = body.find("\nasync def ", 1)
body = body[:end] if end > 0 else body
for field in ('payload["audio"]', 'payload["image"]', 'payload["cond_frames_b64"]'):
assert field in body, field
line = next(l for l in body.splitlines() if field in l and "_as_b64" in l)
assert "_as_b64" in line
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment