Recipes
Multi-model recipes against POST /v1/recipes/run. Role-specific option prefixes
(tts., avatar., conversation., …) are the caller's responsibility: put them directly
in TaskRequestBody.Options keys.
Run a recipe
using Mabs.AudioCpp;
using Mabs.AudioCpp.Models;
RecipeRunRequest recipe = new()
{
Recipe = "audiobook",
Request = new TaskRequestBody
{
Text = "Once upon a time.",
Options = new Dictionary<string, string>
{
["tts.voice"] = "af_bella",
["tts.language"] = "en",
},
},
BusyTimeoutMs = 120000,
};
// One entry per role the recipe requires.
recipe.Models["tts"] = "kokoro";
TaskResult result = await client.Recipes.RunAsync(recipe);
Console.WriteLine(result.Text);
Stream a recipe
RunStreamAsync forces stream: true and uses the same
task.stream.event / task.stream.done envelope as generic task streaming; with
IncludeArtifacts artifact-only events count toward ttft_ms when timing is enabled.
RecipeRunRequest recipe = new()
{
Recipe = "audiobook",
IncludeArtifacts = true,
};
recipe.Models["tts"] = "kokoro";
await foreach (TaskStreamEvent evt in client.Recipes.RunStreamAsync(recipe))
{
if (evt.Kind == TaskStreamEventKind.Done)
{
foreach (AudioArtifact artifact in evt.Result?.Artifacts ?? Array.Empty<AudioArtifact>())
Console.WriteLine($"{artifact.Kind} {artifact.Id} {artifact.Payload.Length} bytes");
}
}
RunAsync rejects Stream = true — use RunStreamAsync for that.
Discover recipes
foreach (RecipeEntry entry in await client.ListRecipesAsync())
{
Console.WriteLine($"{entry.Id}: {entry.Description}");
foreach (RecipeRole role in entry.Roles)
Console.WriteLine($" role {role.Name} task={role.Task} mode={role.Mode}");
}
See Error handling for failure shapes.