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
This commit is contained in:
yumoqing 2026-06-04 13:40:08 +08:00
parent fb7fa8c082
commit ae02a7e88c
3 changed files with 166 additions and 0 deletions

View File

@ -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
查询异步任务状态。

View File

@ -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",
]

View File

@ -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)