multimodalart HF Staff commited on
Commit
929e40e
·
1 Parent(s): 7409072

Harden composer + share video against the failure modes in production logs (#8)

Browse files

- Harden composer + share video against the failure modes in production logs (cd3441a060463372f41524652bd6444e08b32ac4)

Files changed (1) hide show
  1. app.py +46 -23
app.py CHANGED
@@ -496,7 +496,7 @@ Given a sound instruction and/or lyrics, produce global_metadata, vocal_details
496
  Answer with ONLY a JSON object with keys: global_metadata, vocal_details, arrangement."""
497
 
498
 
499
- def _llm_json(system, user):
500
  import json as _json
501
 
502
  from openai import OpenAI
@@ -506,25 +506,35 @@ def _llm_json(system, user):
506
  last_error = None
507
  # Three DISTINCT providers, all verified enabled for this account (bare/":fastest" can route to
508
  # together, which 403s here and killed the fallbacks). Timeouts sized to measured composer latency.
509
- for model, timeout in (
510
- ("deepseek-ai/DeepSeek-V4-Flash-0731:baseten", 45),
511
- ("deepseek-ai/DeepSeek-V4-Flash-0731:deepinfra", 75),
512
- ("deepseek-ai/DeepSeek-V4-Flash-0731:novita", 100),
513
- ):
514
- try:
515
- completion = client.with_options(timeout=timeout).chat.completions.create(
516
- model=model,
517
- messages=[{"role": "system", "content": system}, {"role": "user", "content": user}],
518
- )
519
- text = completion.choices[0].message.content or ""
520
- # Tolerate fences/preambles and reject truncated replies: parse the outermost {...} span.
521
- start, end = text.find("{"), text.rfind("}")
522
- if start == -1 or end <= start:
523
- raise ValueError(f"no JSON object in composer reply (finish_reason={completion.choices[0].finish_reason})")
524
- return _json.loads(text[start : end + 1])
525
- except Exception as e:
526
- print(f"composer attempt failed ({model}): {type(e).__name__}: {e}", flush=True)
527
- last_error = e
 
 
 
 
 
 
 
 
 
 
528
  raise gr.Error(
529
  "The composer model is overloaded right now — try again in a moment, "
530
  "or write the lyrics and structured prompt directly in the Studio tab."
@@ -534,7 +544,11 @@ def _llm_json(system, user):
534
  def compose_song(description, duration):
535
  if not description.strip():
536
  raise gr.Error("Describe the song you want first.")
537
- data = _llm_json(_COMPOSER_SYSTEM, f"Song description: {description}\nTarget duration: {int(duration)} seconds.")
 
 
 
 
538
  return data["lyrics"], data["global_metadata"], data["vocal_details"], data["arrangement"]
539
 
540
 
@@ -977,7 +991,11 @@ def render_video(wav_path, title):
977
 
978
  import scipy.io.wavfile
979
 
980
- sr, wave = scipy.io.wavfile.read(wav_path)
 
 
 
 
981
  mono = wave.astype(np.float32).mean(axis=1) / 32768.0
982
  fps, size, bars = 24, 720, 56
983
  total_frames = int(len(mono) / sr * fps)
@@ -1053,7 +1071,10 @@ def render_video(wav_path, title):
1053
  if len(chunk) < window:
1054
  chunk = np.pad(chunk, (0, window - len(chunk)))
1055
  spectrum = np.abs(np.fft.rfft(chunk * np.hanning(window)))
1056
- levels = np.array([spectrum[(freqs >= band_edges[b]) & (freqs < band_edges[b + 1])].mean() for b in range(bars)])
 
 
 
1057
  levels = np.log1p(12 * np.nan_to_num(levels))
1058
  smooth = np.maximum(levels, smooth * 0.85)
1059
  frame = base.copy()
@@ -1524,6 +1545,7 @@ def compose_assist(raw_state, duration):
1524
  f"Current structured prompt, keep the lyrics coherent with it:\n"
1525
  f"Global metadata: {state['global_meta']}\nVocal details: {state['vocals']}\n"
1526
  f"Arrangement: {state['arrangement']}\nTarget duration: {int(duration)} seconds.",
 
1527
  )
1528
  state["lyrics"] = data["lyrics"]
1529
  return state, "Lyrics written — tweak them, or press Generate."
@@ -1532,6 +1554,7 @@ def compose_assist(raw_state, duration):
1532
  _PROMPT_SYSTEM,
1533
  f"Sound instruction: {instruction or description or '(none — describe a sound that fits the lyrics)'}\n"
1534
  f"Current lyrics, keep the structured prompt coherent with them:\n{state['lyrics']}",
 
1535
  )
1536
  state.update(global_meta=data["global_metadata"], vocals=data["vocal_details"], arrangement=data["arrangement"])
1537
  return state, "Structured prompt written — tweak it, or press Generate."
 
496
  Answer with ONLY a JSON object with keys: global_metadata, vocal_details, arrangement."""
497
 
498
 
499
+ def _llm_json(system, user, required=()):
500
  import json as _json
501
 
502
  from openai import OpenAI
 
506
  last_error = None
507
  # Three DISTINCT providers, all verified enabled for this account (bare/":fastest" can route to
508
  # together, which 403s here and killed the fallbacks). Timeouts sized to measured composer latency.
509
+ # Two passes over the chain: under load every provider can 429 transiently, and a second pass a few
510
+ # seconds later usually lands.
511
+ for attempt in range(2):
512
+ for model, timeout in (
513
+ ("deepseek-ai/DeepSeek-V4-Flash-0731:baseten", 45),
514
+ ("deepseek-ai/DeepSeek-V4-Flash-0731:deepinfra", 75),
515
+ ("deepseek-ai/DeepSeek-V4-Flash-0731:novita", 100),
516
+ ):
517
+ try:
518
+ completion = client.with_options(timeout=timeout).chat.completions.create(
519
+ model=model,
520
+ messages=[{"role": "system", "content": system}, {"role": "user", "content": user}],
521
+ )
522
+ text = completion.choices[0].message.content or ""
523
+ # Tolerate fences/preambles and reject truncated replies: parse the outermost {...} span.
524
+ start, end = text.find("{"), text.rfind("}")
525
+ if start == -1 or end <= start:
526
+ raise ValueError(f"no JSON object in composer reply (finish_reason={completion.choices[0].finish_reason})")
527
+ data = _json.loads(text[start : end + 1])
528
+ # Valid JSON with the wrong shape must retry too, not KeyError later.
529
+ missing = [key for key in required if key not in data]
530
+ if missing:
531
+ raise ValueError(f"composer reply missing keys: {missing}")
532
+ return data
533
+ except Exception as e:
534
+ print(f"composer attempt failed ({model}, pass {attempt + 1}): {type(e).__name__}: {e}", flush=True)
535
+ last_error = e
536
+ if attempt == 0:
537
+ time.sleep(3)
538
  raise gr.Error(
539
  "The composer model is overloaded right now — try again in a moment, "
540
  "or write the lyrics and structured prompt directly in the Studio tab."
 
544
  def compose_song(description, duration):
545
  if not description.strip():
546
  raise gr.Error("Describe the song you want first.")
547
+ data = _llm_json(
548
+ _COMPOSER_SYSTEM,
549
+ f"Song description: {description}\nTarget duration: {int(duration)} seconds.",
550
+ required=("lyrics", "global_metadata", "vocal_details", "arrangement"),
551
+ )
552
  return data["lyrics"], data["global_metadata"], data["vocal_details"], data["arrangement"]
553
 
554
 
 
991
 
992
  import scipy.io.wavfile
993
 
994
+ title = " ".join((title or "").split())[:96] or "MiniMax Music 3"
995
+ try:
996
+ sr, wave = scipy.io.wavfile.read(wav_path)
997
+ except (FileNotFoundError, OSError):
998
+ return gr.skip() # the visitor left and gradio cleaned the cached wav
999
  mono = wave.astype(np.float32).mean(axis=1) / 32768.0
1000
  fps, size, bars = 24, 720, 56
1001
  total_frames = int(len(mono) / sr * fps)
 
1071
  if len(chunk) < window:
1072
  chunk = np.pad(chunk, (0, window - len(chunk)))
1073
  spectrum = np.abs(np.fft.rfft(chunk * np.hanning(window)))
1074
+ levels = np.array([
1075
+ spectrum[m].mean() if (m := (freqs >= band_edges[b]) & (freqs < band_edges[b + 1])).any() else 0.0
1076
+ for b in range(bars)
1077
+ ])
1078
  levels = np.log1p(12 * np.nan_to_num(levels))
1079
  smooth = np.maximum(levels, smooth * 0.85)
1080
  frame = base.copy()
 
1545
  f"Current structured prompt, keep the lyrics coherent with it:\n"
1546
  f"Global metadata: {state['global_meta']}\nVocal details: {state['vocals']}\n"
1547
  f"Arrangement: {state['arrangement']}\nTarget duration: {int(duration)} seconds.",
1548
+ required=("lyrics",),
1549
  )
1550
  state["lyrics"] = data["lyrics"]
1551
  return state, "Lyrics written — tweak them, or press Generate."
 
1554
  _PROMPT_SYSTEM,
1555
  f"Sound instruction: {instruction or description or '(none — describe a sound that fits the lyrics)'}\n"
1556
  f"Current lyrics, keep the structured prompt coherent with them:\n{state['lyrics']}",
1557
+ required=("global_metadata", "vocal_details", "arrangement"),
1558
  )
1559
  state.update(global_meta=data["global_metadata"], vocals=data["vocal_details"], arrangement=data["arrangement"])
1560
  return state, "Structured prompt written — tweak it, or press Generate."