Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from replicate.client import Client as ReplicateClient
from replicate.helpers import FileOutput

from backend.blocks.replicate._helper import run_replicate_with_retry
from backend.data.block import (
Block,
BlockCategory,
Expand Down Expand Up @@ -183,9 +184,10 @@ async def run_model(
if images:
input_params["image_input"] = [str(img) for img in images]

output: FileOutput | str = await client.async_run( # type: ignore
output: FileOutput | str = await run_replicate_with_retry( # type: ignore
client,
model_name,
input=input_params,
input_params,
wait=False,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from replicate.client import Client as ReplicateClient
from replicate.helpers import FileOutput

from backend.blocks.replicate._helper import run_replicate_with_retry
from backend.data.block import Block, BlockCategory, BlockSchemaInput, BlockSchemaOutput
from backend.data.model import (
APIKeyCredentials,
Expand Down Expand Up @@ -181,7 +182,9 @@ async def _run_client(
client = ReplicateClient(api_token=credentials.api_key.get_secret_value())

# Run the model with input parameters
output = await client.async_run(model_name, input=input_params, wait=False)
output = await run_replicate_with_retry(
client, model_name, input_params, wait=False
)

# Process output
if isinstance(output, list) and len(output) > 0:
Expand Down
109 changes: 66 additions & 43 deletions autogpt_platform/backend/backend/blocks/ai_music_generator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import asyncio
import logging
from enum import Enum
from typing import Literal

from pydantic import SecretStr
from replicate.client import Client as ReplicateClient
from replicate.helpers import FileOutput

from backend.blocks.replicate._helper import run_replicate_with_retry
from backend.data.block import (
Block,
BlockCategory,
Expand Down Expand Up @@ -43,12 +44,14 @@ class MusicGenModelVersion(str, Enum):
STEREO_LARGE = "stereo-large"
MELODY_LARGE = "melody-large"
LARGE = "large"
MINIMAX_MUSIC_1_5 = "minimax/music-1.5"


# Audio format enum
class AudioFormat(str, Enum):
WAV = "wav"
MP3 = "mp3"
PCM = "pcm"


# Normalization strategy enum
Expand All @@ -72,6 +75,14 @@ class Input(BlockSchemaInput):
placeholder="e.g., 'An upbeat electronic dance track with heavy bass'",
title="Prompt",
)
lyrics: str | None = SchemaField(
description=(
"Lyrics for the song (required for Minimax Music 1.5). "
"Use \\n to separate lines. Supports tags like [intro], [verse], [chorus], etc."
),
default=None,
title="Lyrics",
)
music_gen_model_version: MusicGenModelVersion = SchemaField(
description="Model to use for generation",
default=MusicGenModelVersion.STEREO_LARGE,
Expand Down Expand Up @@ -126,6 +137,7 @@ def __init__(self):
test_input={
"credentials": TEST_CREDENTIALS_INPUT,
"prompt": "An upbeat electronic dance track with heavy bass",
"lyrics": None,
"music_gen_model_version": MusicGenModelVersion.STEREO_LARGE,
"duration": 8,
"temperature": 1.0,
Expand All @@ -142,56 +154,43 @@ def __init__(self):
),
],
test_mock={
"run_model": lambda api_key, music_gen_model_version, prompt, duration, temperature, top_k, top_p, classifier_free_guidance, output_format, normalization_strategy: "https://replicate.com/output/generated-audio-url.wav",
"run_model": lambda api_key, music_gen_model_version, prompt, lyrics, duration, temperature, top_k, top_p, classifier_free_guidance, output_format, normalization_strategy: "https://replicate.com/output/generated-audio-url.wav",
},
test_credentials=TEST_CREDENTIALS,
)

async def run(
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
) -> BlockOutput:
max_retries = 3
retry_delay = 5 # seconds
last_error = None

for attempt in range(max_retries):
try:
logger.debug(
f"[AIMusicGeneratorBlock] - Running model (attempt {attempt + 1})"
)
result = await self.run_model(
api_key=credentials.api_key,
music_gen_model_version=input_data.music_gen_model_version,
prompt=input_data.prompt,
duration=input_data.duration,
temperature=input_data.temperature,
top_k=input_data.top_k,
top_p=input_data.top_p,
classifier_free_guidance=input_data.classifier_free_guidance,
output_format=input_data.output_format,
normalization_strategy=input_data.normalization_strategy,
)
if result and isinstance(result, str) and result.startswith("http"):
yield "result", result
return
else:
last_error = "Model returned empty or invalid response"
raise ValueError(last_error)
except Exception as e:
last_error = f"Unexpected error: {str(e)}"
logger.error(f"[AIMusicGeneratorBlock] - Error: {last_error}")
if attempt < max_retries - 1:
await asyncio.sleep(retry_delay)
continue

# If we've exhausted all retries, yield the error
yield "error", f"Failed after {max_retries} attempts. Last error: {last_error}"
try:
result = await self.run_model(
api_key=credentials.api_key,
music_gen_model_version=input_data.music_gen_model_version,
prompt=input_data.prompt,
lyrics=input_data.lyrics,
duration=input_data.duration,
temperature=input_data.temperature,
top_k=input_data.top_k,
top_p=input_data.top_p,
classifier_free_guidance=input_data.classifier_free_guidance,
output_format=input_data.output_format,
normalization_strategy=input_data.normalization_strategy,
)
if result and isinstance(result, str) and result.startswith("http"):
yield "result", result
else:
yield "error", "Model returned empty or invalid response"

except Exception as e:
logger.error(f"[AIMusicGeneratorBlock] - Error: {str(e)}")
yield "error", f"Failed to generate music: {str(e)}"
Comment thread
Torantulino marked this conversation as resolved.

async def run_model(
self,
api_key: SecretStr,
music_gen_model_version: MusicGenModelVersion,
prompt: str,
lyrics: str | None,
duration: int,
temperature: float,
top_k: int,
Expand All @@ -203,10 +202,24 @@ async def run_model(
# Initialize Replicate client with the API key
client = ReplicateClient(api_token=api_key.get_secret_value())

# Run the model with parameters
output = await client.async_run(
"meta/musicgen:671ac645ce5e552cc63a54a2bbff63fcf798043055d2dac5fc9e36a837eedcfb",
input={
if music_gen_model_version == MusicGenModelVersion.MINIMAX_MUSIC_1_5:
if not lyrics:
raise ValueError("Lyrics are required for Minimax Music 1.5 model")

# Validate prompt length (10-300 chars)
if len(prompt) < 10:
prompt = prompt.ljust(10, ".")
elif len(prompt) > 300:
prompt = prompt[:300]

input_params = {
"prompt": prompt,
"lyrics": lyrics,
"audio_format": output_format.value,
}
model_name = "minimax/music-1.5"
Comment on lines +205 to +220

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Minimax Music 1.5 API prompt length requirements documentation

💡 Result:

Summary of documented prompt-length limits for Minimax Music 1.5

  • AI/ML API (Minimax/music-1.5): prompt = 10–300 characters; lyrics = 10–3000 characters. [1]
  • Replicate README (Minimax/music-1.5): lyrics recommended/limited to 600 characters (notes state max output 240s and “max lyrics 600 characters”). [2]
  • FAL.ai wrapper docs: prompt = 10–300 characters; lyrics/text-to-music input max = 600 characters (other fields show lyrics 10–3000 in alternate schema). [3]

Sources
[1] AI/ML API docs — minimax/music-1.5 (Generate music sample).
[2] Replicate — minimax/music-1.5 README.
[3] FAL.ai Minimax Music v1.5 API docs.


🏁 Script executed:

#!/bin/bash
# Search codebase for context about prompt validation and Minimax API usage
rg -n "minimax|music-1.5" --type py -B 3 -A 3

Repository: Significant-Gravitas/AutoGPT

Length of output: 1368


🏁 Script executed:

#!/bin/bash
# Check if there are any comments or documentation about prompt validation
rg -n "prompt.*length|10.*300|minimax.*prompt" --type py -B 2 -A 2

Repository: Significant-Gravitas/AutoGPT

Length of output: 4921


🏁 Script executed:

#!/bin/bash
# Check lyrics validation for consistency
sed -n '207,243p' autogpt_platform/backend/backend/blocks/ai_music_generator.py

Repository: Significant-Gravitas/AutoGPT

Length of output: 1465


🏁 Script executed:

#!/bin/bash
# Check if there are tests for prompt validation behavior
fd "test" -e py --exec grep -l "ai_music_generator\|prompt.*minimax" {} \;

Repository: Significant-Gravitas/AutoGPT

Length of output: 54


Replace silent prompt padding/truncation with proper validation that raises an error.

The code silently modifies prompts outside the 10–300 character API requirement by padding short prompts with dots and truncating long prompts. This contradicts the code comment's intent to "Validate" and creates an inconsistency: lyrics validation raises ValueError for missing required fields, while prompt validation silently auto-fixes.

According to the Minimax Music 1.5 API documentation, the prompt must be strictly 10–300 characters. Users should be informed when their input violates this constraint rather than having it silently modified, which can produce unexpected results and masks the actual problem.

Replace lines 212–215 with validation that raises ValueError if the prompt is out of range, matching the pattern used for lyrics validation on line 209.

🤖 Prompt for AI Agents
In autogpt_platform/backend/backend/blocks/ai_music_generator.py around lines
207 to 222, the prompt is currently silently padded or truncated to meet the
10–300 character requirement; replace that behavior with strict validation: if
len(prompt) < 10 or len(prompt) > 300 raise a ValueError with a clear message
(e.g. "Prompt must be between 10 and 300 characters") following the same
validation pattern used for lyrics, and remove the ljust/truncate logic (i.e.,
replace lines 212–215 with the ValueError checks).

else:
input_params = {
"prompt": prompt,
"music_gen_model_version": music_gen_model_version,
"duration": duration,
Expand All @@ -216,14 +229,24 @@ async def run_model(
"classifier_free_guidance": classifier_free_guidance,
"output_format": output_format,
"normalization_strategy": normalization_strategy,
},
}
Comment on lines 229 to +232

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: For the meta/musicgen model, Enum objects output_format and normalization_strategy are passed to the Replicate API instead of their string values using .value.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

In ai_music_generator.py, when using the meta/musicgen model, the output_format and normalization_strategy parameters are passed as Enum objects directly to the run_replicate_with_retry function. The Replicate API expects string values for these parameters (e.g., "wav", "loudness"), not Enum objects (e.g., AudioFormat.WAV). This will cause the API request to be rejected with an error, leading to consistent failures for this specific model path. The code correctly uses .value for the Minimax Music 1.5 model, but this was missed for the meta/musicgen model.

💡 Suggested Fix

Update lines 229-231 in ai_music_generator.py to extract the string value from the Enum objects before passing them to the Replicate API. Change "output_format": output_format to "output_format": output_format.value and "normalization_strategy": normalization_strategy to "normalization_strategy": normalization_strategy.value.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: autogpt_platform/backend/backend/blocks/ai_music_generator.py#L229-L232

Potential issue: In `ai_music_generator.py`, when using the `meta/musicgen` model, the
`output_format` and `normalization_strategy` parameters are passed as Enum objects
directly to the `run_replicate_with_retry` function. The Replicate API expects string
values for these parameters (e.g., "wav", "loudness"), not Enum objects (e.g.,
`AudioFormat.WAV`). This will cause the API request to be rejected with an error,
leading to consistent failures for this specific model path. The code correctly uses
`.value` for the Minimax Music 1.5 model, but this was missed for the `meta/musicgen`
model.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 7721083

model_name = "meta/musicgen:671ac645ce5e552cc63a54a2bbff63fcf798043055d2dac5fc9e36a837eedcfb"

# Run the model with parameters
output = await run_replicate_with_retry(
client,
model_name,
input_params,
wait=True,
)

# Handle the output
if isinstance(output, list) and len(output) > 0:
result_url = output[0] # If output is a list, get the first element
elif isinstance(output, str):
result_url = output # If output is a string, use it directly
elif isinstance(output, FileOutput):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: FileOutput in list not extracted to URL string

When output is a list, the code assigns output[0] directly to result_url without checking if it's a FileOutput object. If the model returns list[FileOutput], result_url becomes a FileOutput object instead of a URL string. This causes the subsequent check isinstance(result, str) in the run() method to fail, incorrectly yielding an error. Other blocks like ai_image_generator_block.py and the extract_result helper correctly check isinstance(output[0], FileOutput) and extract the .url property.

Fix in Cursor Fix in Web

result_url = output.url
else:
result_url = (
"No output received" # Fallback message if output is not as expected
Expand Down
6 changes: 4 additions & 2 deletions autogpt_platform/backend/backend/blocks/flux_kontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from replicate.client import Client as ReplicateClient
from replicate.helpers import FileOutput

from backend.blocks.replicate._helper import run_replicate_with_retry
from backend.data.block import (
Block,
BlockCategory,
Expand Down Expand Up @@ -173,9 +174,10 @@ async def run_model(
**({"seed": seed} if seed is not None else {}),
}

output: FileOutput | list[FileOutput] = await client.async_run( # type: ignore
output: FileOutput | list[FileOutput] = await run_replicate_with_retry( # type: ignore
client,
model_name,
input=input_params,
input_params=input_params,
wait=False,
)

Expand Down
56 changes: 56 additions & 0 deletions autogpt_platform/backend/backend/blocks/replicate/_helper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import asyncio
import logging
from typing import Any

from replicate.client import Client as ReplicateClient
from replicate.helpers import FileOutput

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -37,3 +40,56 @@ def extract_result(output: ReplicateOutputs) -> str:
)

return result


async def run_replicate_with_retry(
client: ReplicateClient,
model: str,
input_params: dict[str, Any],
wait: bool = False,
max_retries: int = 3,
**kwargs: Any,
) -> Any:
last_error = None
retry_delay = 2 # seconds

for attempt in range(max_retries):
try:
output = await client.async_run(
model, input=input_params, wait=wait, **kwargs
)

# Check for failed status in response
is_failed = False
if isinstance(output, dict) and output.get("status") == "failed":
is_failed = True
elif hasattr(output, "status") and getattr(output, "status") == "failed":
is_failed = True

if is_failed:
# Try to get error message
error_msg = "Replicate prediction failed"
if isinstance(output, dict):
error = output.get("error")
if error:
error_msg = f"{error_msg}: {error}"
elif hasattr(output, "error"):
error = getattr(output, "error")
if error:
error_msg = f"{error_msg}: {error}"

raise RuntimeError(error_msg)

return output

except Exception as e:
last_error = e
if attempt < max_retries - 1:
wait_time = retry_delay * (2**attempt)
logger.warning(
f"Replicate attempt {attempt + 1} failed: {str(e)}. Retrying in {wait_time}s..."
)
await asyncio.sleep(wait_time)
else:
logger.error(f"Replicate failed after {max_retries} attempts: {str(e)}")
raise last_error
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
TEST_CREDENTIALS_INPUT,
ReplicateCredentialsInput,
)
from backend.blocks.replicate._helper import ReplicateOutputs, extract_result
from backend.blocks.replicate._helper import (
ReplicateOutputs,
extract_result,
run_replicate_with_retry,
)
from backend.data.block import (
Block,
BlockCategory,
Expand Down Expand Up @@ -188,9 +192,10 @@ async def run_model(
client = ReplicateClient(api_token=api_key.get_secret_value())

# Run the model with additional parameters
output: ReplicateOutputs = await client.async_run( # type: ignore This is because they changed the return type, and didn't update the type hint! It should be overloaded depending on the value of `use_file_output` to `FileOutput | list[FileOutput]` but it's `Any | Iterator[Any]`
output: ReplicateOutputs = await run_replicate_with_retry( # type: ignore This is because they changed the return type, and didn't update the type hint! It should be overloaded depending on the value of `use_file_output` to `FileOutput | list[FileOutput]` but it's `Any | Iterator[Any]`
client,
f"{model_name}",
input={
input_params={
"prompt": prompt,
"seed": seed,
"steps": steps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
TEST_CREDENTIALS_INPUT,
ReplicateCredentialsInput,
)
from backend.blocks.replicate._helper import ReplicateOutputs, extract_result
from backend.blocks.replicate._helper import (
ReplicateOutputs,
extract_result,
run_replicate_with_retry,
)
from backend.data.block import (
Block,
BlockCategory,
Expand Down Expand Up @@ -129,8 +133,8 @@ async def run_model(self, model_ref: str, model_inputs: dict, api_key: SecretStr
"""
api_key_str = api_key.get_secret_value()
client = ReplicateClient(api_token=api_key_str)
output: ReplicateOutputs = await client.async_run(
model_ref, input=model_inputs, wait=False
output: ReplicateOutputs = await run_replicate_with_retry(
client, model_ref, input_params=model_inputs, wait=False
) # type: ignore they suck at typing

result = extract_result(output)
Expand Down
Loading