From ae02a7e88cb001b3d1705379712da5b983177adf Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 4 Jun 2026 13:40:08 +0800 Subject: [PATCH] feat: add music generation API (MiniMax Music 2.5/2.6) - Add POST /v1/music/generations endpoint (index.dspy) - Add music generation section to API docs - Update load_path.py RBAC permissions for new path - Models: music-2.6, music-2.5 (MiniMax, sync, returns audio URL) - Required params: model, catelogid=music_gen, prompt, lyrics --- docs/API.md | 84 +++++++++++++++++++++++++ scripts/load_path.py | 2 + wwwroot/v1/music/generations/index.dspy | 80 +++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 wwwroot/v1/music/generations/index.dspy diff --git a/docs/API.md b/docs/API.md index 2cc64b5..9e0b810 100644 --- a/docs/API.md +++ b/docs/API.md @@ -427,6 +427,90 @@ data: [DONE] --- +## POST /v1/music/generations + +音乐生成接口。 + +### 必填参数 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `model` | string | 模型名称,如 `"music-2.6"`, `"music-2.5"` | +| `catelogid` | string | 目录类型ID,固定为 `"music_gen"` | +| `prompt` | string | 音乐风格描述(风格、情绪、场景),如 `"流行音乐, 开心, 适合阳光明媚的下午"` | +| `lyrics` | string | 歌词内容,使用 `\n` 分隔每行,可包含结构标签 | + +### 歌词结构标签 + +歌词中可包含以下结构标签来优化生成的音乐结构: +- `[Intro]` - 前奏 +- `[Verse]` - 主歌 +- `[Pre Chorus]` - 预副歌 +- `[Chorus]` - 副歌 +- `[Bridge]` - 桥段 +- `[Outro]` - 尾声 +- `[Interlude]` - 间奏 +- `[Hook]` - 记忆点 +- `[Build Up]` - 情绪铺垫 +- `[Solo]` - 独奏 + +### 请求示例 + +```json +{ + "model": "music-2.6", + "catelogid": "music_gen", + "prompt": "Pop music, happy, suitable for a sunny day", + "lyrics": "[Intro]\n\n[Verse]\nWalking down the street\nFeeling the beat\n\n[Chorus]\nDancing in the sun\nHaving so much fun" +} +``` + +### 响应格式 + +MiniMax 音乐生成为同步接口,直接返回音频URL: + +```json +{ + "id": "luid_xxx", + "object": "music.generation", + "model": "music-2.6", + "status": "SUCCEEDED", + "audio": "https://...", + "created": 1716912000 +} +``` + +### 可用模型 + +| 模型名称 | model 参数 | 说明 | +|---------|-----------|------| +| MiniMax Music 2.6 | `music-2.6` | 最新版本,音质最佳 | +| MiniMax Music 2.5 | `music-2.5` | 支持14种段落级结构标签,物理级高保真 | + +### MiniMax Music 2.5 特性 + +Music 2.5 在「段落级强控制」与「物理级高保真」两大技术难题上实现突破: +- 开放全段落标签控制,精准支持14种结构变体 +- 长度限制:歌词内容 [1, 3500] 个字符 +- prompt 长度限制:[10, 300] 个字符 + +### MiniMax Music 2.0 特性(已过期) + +Music 2.0 能根据文本描述和歌词直接生成包含人声的完整歌曲: +- prompt 长度限制:[10, 300] 个字符 +- lyrics 长度限制:[10, 3000] 个字符 +- 状态:已过期(expired_date: 2026-01-01) + +### 错误响应 + +| 状态码 | 说明 | +|--------|------| +| 400 | 缺少必填参数或模型不存在 | +| 403 | 未登录 | +| 429 | 账户余额不足 | + +--- + ## GET /v1/tasks 查询异步任务状态。 diff --git a/scripts/load_path.py b/scripts/load_path.py index d607dec..14f8f49 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -163,6 +163,7 @@ PATHS_LOGINED = [ f"/{MOD}/v1/models/index.dspy", f"/{MOD}/v1/tasks/index.dspy", f"/{MOD}/v1/video/generations/index.dspy", + f"/{MOD}/v1/music/generations/index.dspy", # 其他子目录 f"/{MOD}/list_llmcatelogs/index.dspy", @@ -182,6 +183,7 @@ PATHS_V1_CUSTOMER = [ f"/{MOD}/v1/chat/completions/index.dspy", f"/{MOD}/v1/video/generations/index.dspy", f"/{MOD}/v1/image/generations/index.dspy", + f"/{MOD}/v1/music/generations/index.dspy", f"/{MOD}/v1/models/index.dspy", f"/{MOD}/v1/tasks/index.dspy", ] diff --git a/wwwroot/v1/music/generations/index.dspy b/wwwroot/v1/music/generations/index.dspy new file mode 100644 index 0000000..1bf5e24 --- /dev/null +++ b/wwwroot/v1/music/generations/index.dspy @@ -0,0 +1,80 @@ +# OpenAI-compatible Music Generation API +# POST /v1/music/generations +# Required params: model, catelogid, prompt, lyrics +# Optional params: output_format, audio_setting +# +# Example request: +# { +# "model": "music-2.6", +# "catelogid": "music_gen", +# "prompt": "Pop music, happy, suitable for a sunny day", +# "lyrics": "[Intro]\n\n[Verse]\nWalking down the street\nFeeling the beat\n\n[Chorus]\nDancing in the sun\nHaving so much fun" +# } +# +# Response (sync for MiniMax): +# { +# "id": "luid_xxx", +# "object": "music.generation", +# "model": "music-2.6", +# "status": "SUCCEEDED", +# "audio": "https://...", +# "created": 1234567890 +# } + +userid = await get_user() +userorgid = await get_userorgid() +if userid is None: + debug('need login') + return openai_403() + +# Validate required parameters +if not params_kw.model: + d = return_error('Missing required parameter: model') + return json_response(d, status=400) + +if not params_kw.catelogid: + d = return_error('Missing required parameter: catelogid') + return json_response(d, status=400) + +if not params_kw.prompt: + d = return_error('Missing required parameter: prompt') + return json_response(d, status=400) + +if not params_kw.lyrics: + d = return_error('Missing required parameter: lyrics') + return json_response(d, status=400) + +lctype = params_kw.catelogid + +env = request._run_ns +async with get_sor_context(env, 'llmage') as sor: + # Look up llm by model name and catalog type through llm_api_map + sql = """select distinct a.* from llm a +join llm_api_map m on a.id = m.llmid +join llmcatelog b on m.llmcatelogid = b.id +where (b.id = ${lctype}$ OR b.name = ${lctype}$) + and a.model=${model}$ + and a.status = 'published'""" + recs = await sor.sqlExe(sql, { + 'lctype': lctype, + 'model': params_kw.model + }) + if len(recs) == 0: + debug(f'{params_kw.model=} not found for catalog {lctype}') + return openai_400() + params_kw.llmid = recs[0].id + +debug(f'{params_kw.llmid=}') + +# Check balance +f = await checkCustomerBalance(params_kw.llmid, userid, userorgid) +if not f: + debug(f'{userid=} balance not enough') + return openai_429() + +# Generate task ID and attach to params +if not params_kw.transno: + params_kw.transno = getID() + +# Call inference (music generation via MiniMax is synchronous) +return await inference(request, env=env)