Skip to content

Commit 92fa7ad

Browse files
committed
fix(agents): ignore unsupported music generation hints
1 parent 9b2b22f commit 92fa7ad

7 files changed

Lines changed: 395 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
1111
### Changes
1212

1313
- Agents/video generation: add the built-in `video_generate` tool so agents can create videos through configured providers and return the generated media directly in the reply.
14+
- Agents/music generation: ignore unsupported optional hints such as `durationSeconds` with a warning instead of hard-failing requests on providers like Google Lyria.
1415
- Providers/ComfyUI: add a bundled `comfy` workflow media plugin for local ComfyUI and Comfy Cloud workflows, including shared `image_generate`, `video_generate`, and workflow-backed `music_generate` support, with prompt injection, optional reference-image upload, live tests, and output download.
1516
- Tools/music generation: add the built-in `music_generate` tool with bundled Google (Lyria) and MiniMax providers plus workflow-backed Comfy support, including async task tracking and follow-up delivery of finished audio.
1617
- Providers: add bundled Qwen, Fireworks AI, and StepFun providers, plus MiniMax TTS, Ollama Web Search, and MiniMax Search integrations for chat, speech, and search workflows. (#60032, #55921, #59318, #54648)

docs/tools/music-generation.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ Direct generation example:
119119
| `format` | string | Output format hint (`mp3` or `wav`) when the provider supports it |
120120
| `filename` | string | Output filename hint |
121121

122-
Not all providers support all parameters. The shared tool validates provider
123-
capability limits before it submits the request.
122+
Not all providers support all parameters. OpenClaw still validates hard limits
123+
such as input counts before submission, but unsupported optional hints are
124+
ignored with a warning when the selected provider or model cannot honor them.
124125

125126
## Async behavior for the shared provider-backed path
126127

src/agents/tools/music-generate-tool.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ describe("createMusicGenerateTool", () => {
7575
provider: "google",
7676
model: "lyria-3-clip-preview",
7777
attempts: [],
78+
ignoredOverrides: [],
7879
tracks: [
7980
{
8081
buffer: Buffer.from("music-bytes"),
@@ -154,6 +155,7 @@ describe("createMusicGenerateTool", () => {
154155
provider: "google",
155156
model: "lyria-3-clip-preview",
156157
attempts: [],
158+
ignoredOverrides: [],
157159
tracks: [
158160
{
159161
buffer: Buffer.from("music-bytes"),
@@ -270,4 +272,86 @@ describe("createMusicGenerateTool", () => {
270272
expect(text).toContain("supportedFormats=mp3");
271273
expect(text).toContain("instrumental");
272274
});
275+
276+
it("warns when optional provider overrides are ignored", async () => {
277+
vi.spyOn(musicGenerationRuntime, "listRuntimeMusicGenerationProviders").mockReturnValue([
278+
{
279+
id: "google",
280+
defaultModel: "lyria-3-clip-preview",
281+
models: ["lyria-3-clip-preview"],
282+
capabilities: {
283+
supportsLyrics: true,
284+
supportsInstrumental: true,
285+
supportsFormat: true,
286+
supportedFormatsByModel: {
287+
"lyria-3-clip-preview": ["mp3"],
288+
},
289+
},
290+
generateMusic: vi.fn(async () => {
291+
throw new Error("not used");
292+
}),
293+
},
294+
]);
295+
vi.spyOn(musicGenerationRuntime, "generateMusic").mockResolvedValue({
296+
provider: "google",
297+
model: "lyria-3-clip-preview",
298+
attempts: [],
299+
ignoredOverrides: [
300+
{ key: "durationSeconds", value: 30 },
301+
{ key: "format", value: "wav" },
302+
],
303+
tracks: [
304+
{
305+
buffer: Buffer.from("music-bytes"),
306+
mimeType: "audio/mpeg",
307+
fileName: "molty-anthem.mp3",
308+
},
309+
],
310+
});
311+
vi.spyOn(mediaStore, "saveMediaBuffer").mockResolvedValueOnce({
312+
path: "/tmp/molty-anthem.mp3",
313+
id: "molty-anthem.mp3",
314+
size: 11,
315+
contentType: "audio/mpeg",
316+
});
317+
318+
const tool = createMusicGenerateTool({
319+
config: asConfig({
320+
agents: {
321+
defaults: {
322+
musicGenerationModel: { primary: "google/lyria-3-clip-preview" },
323+
},
324+
},
325+
}),
326+
});
327+
if (!tool) {
328+
throw new Error("expected music_generate tool");
329+
}
330+
331+
const result = await tool.execute("call-google-generate", {
332+
prompt: "OpenClaw anthem",
333+
instrumental: true,
334+
durationSeconds: 30,
335+
format: "wav",
336+
});
337+
const text = (result.content?.[0] as { text: string } | undefined)?.text ?? "";
338+
339+
expect(text).toContain("Generated 1 track with google/lyria-3-clip-preview.");
340+
expect(text).toContain(
341+
"Warning: Ignored unsupported overrides for google/lyria-3-clip-preview: durationSeconds=30, format=wav.",
342+
);
343+
expect(result).toMatchObject({
344+
details: {
345+
instrumental: true,
346+
warning:
347+
"Ignored unsupported overrides for google/lyria-3-clip-preview: durationSeconds=30, format=wav.",
348+
ignoredOverrides: [
349+
{ key: "durationSeconds", value: 30 },
350+
{ key: "format", value: "wav" },
351+
],
352+
},
353+
});
354+
expect(result.details).not.toHaveProperty("durationSeconds");
355+
expect(result.details).not.toHaveProperty("format");
356+
});
273357
});

src/agents/tools/music-generate-tool.ts

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -222,34 +222,17 @@ function validateMusicGenerationCapabilities(params: {
222222
);
223223
}
224224
}
225-
if (params.lyrics?.trim() && !caps.supportsLyrics) {
226-
throw new ToolInputError(`${provider.id} does not support explicit lyrics input.`);
227-
}
228-
if (typeof params.instrumental === "boolean" && !caps.supportsInstrumental) {
229-
throw new ToolInputError(`${provider.id} does not support instrumental toggles.`);
230-
}
231-
if (typeof params.durationSeconds === "number" && !caps.supportsDuration) {
232-
throw new ToolInputError(`${provider.id} does not support duration hints.`);
233-
}
234-
if (typeof params.durationSeconds === "number" && typeof caps.maxDurationSeconds === "number") {
225+
if (
226+
typeof params.durationSeconds === "number" &&
227+
caps.supportsDuration &&
228+
typeof caps.maxDurationSeconds === "number"
229+
) {
235230
if (params.durationSeconds > caps.maxDurationSeconds) {
236231
throw new ToolInputError(
237232
`${provider.id} supports at most ${caps.maxDurationSeconds} seconds per track.`,
238233
);
239234
}
240235
}
241-
if (params.format) {
242-
if (!caps.supportsFormat) {
243-
throw new ToolInputError(`${provider.id} does not support explicit output-format overrides.`);
244-
}
245-
const supportedFormats =
246-
caps.supportedFormatsByModel?.[params.model ?? ""] ?? caps.supportedFormats ?? [];
247-
if (supportedFormats.length > 0 && !supportedFormats.includes(params.format)) {
248-
throw new ToolInputError(
249-
`${provider.id} supports ${supportedFormats.join(", ")} output${params.model ? ` for ${params.model}` : ""}.`,
250-
);
251-
}
252-
}
253236
}
254237

255238
type MusicGenerateSandboxConfig = {
@@ -419,8 +402,15 @@ async function executeMusicGenerationJob(params: {
419402
),
420403
),
421404
);
405+
const ignoredOverrides = result.ignoredOverrides ?? [];
406+
const ignoredOverrideKeys = new Set(ignoredOverrides.map((entry) => entry.key));
407+
const warning =
408+
ignoredOverrides.length > 0
409+
? `Ignored unsupported overrides for ${result.provider}/${result.model}: ${ignoredOverrides.map((entry) => `${entry.key}=${String(entry.value)}`).join(", ")}.`
410+
: undefined;
422411
const lines = [
423412
`Generated ${savedTracks.length} track${savedTracks.length === 1 ? "" : "s"} with ${result.provider}/${result.model}.`,
413+
...(warning ? [`Warning: ${warning}`] : []),
424414
...(result.lyrics?.length ? ["Lyrics returned.", ...result.lyrics] : []),
425415
...savedTracks.map((track) => `MEDIA:${track.path}`),
426416
];
@@ -446,12 +436,16 @@ async function executeMusicGenerationJob(params: {
446436
},
447437
}
448438
: {}),
449-
...(params.lyrics ? { requestedLyrics: params.lyrics } : {}),
450-
...(typeof params.instrumental === "boolean" ? { instrumental: params.instrumental } : {}),
451-
...(typeof params.durationSeconds === "number"
439+
...(!ignoredOverrideKeys.has("lyrics") && params.lyrics
440+
? { requestedLyrics: params.lyrics }
441+
: {}),
442+
...(!ignoredOverrideKeys.has("instrumental") && typeof params.instrumental === "boolean"
443+
? { instrumental: params.instrumental }
444+
: {}),
445+
...(!ignoredOverrideKeys.has("durationSeconds") && typeof params.durationSeconds === "number"
452446
? { durationSeconds: params.durationSeconds }
453447
: {}),
454-
...(params.format ? { format: params.format } : {}),
448+
...(!ignoredOverrideKeys.has("format") && params.format ? { format: params.format } : {}),
455449
...(params.filename ? { filename: params.filename } : {}),
456450
...(params.loadedReferenceImages.length === 1
457451
? {
@@ -471,6 +465,8 @@ async function executeMusicGenerationJob(params: {
471465
...(result.lyrics?.length ? { lyrics: result.lyrics } : {}),
472466
attempts: result.attempts,
473467
metadata: result.metadata,
468+
...(warning ? { warning } : {}),
469+
...(ignoredOverrides.length > 0 ? { ignoredOverrides } : {}),
474470
},
475471
};
476472
}

0 commit comments

Comments
 (0)