Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
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: 67 additions & 42 deletions autogpt_platform/backend/backend/blocks/ai_music_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
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 +46,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 +77,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 +139,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 +156,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)}"

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 +204,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

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 +231,24 @@ async def run_model(
"classifier_free_guidance": classifier_free_guidance,
"output_format": output_format,
"normalization_strategy": normalization_strategy,
},
}
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

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