Skip to main content

架构概览

核心组件

组件作用说明
Music Tools任务创建验证权限、检查 NSFW、选择 Provider
Agent onFinish消息发布将 pending 任务发布到 NATS
NATS JetStream异步队列TASKS stream,subject tasks.music.*
kira-music-worker任务执行扣费、调用 Provider API、后处理、上传
Supabase Storage音频存储agent_message bucket
Centrifugo状态推送WebSocket 实时通知前端

音乐生成工具

generateMusic

生成完整歌曲(含歌词和可选风格提示)。
参数类型必填说明
titlestring歌曲标题(最长 200 字符),AI 可自动生成
lyricsstring歌词(1-3500 字符),支持结构标签 [Verse], [Chorus], [Bridge], [Outro]
promptstring风格/情绪描述(最长 2000 字符)
coverImageIdstring封面图片 ID
  • Provider: suno_unofficial
  • Cost: 25 credits
  • Plan: Pro+

generateInstrumental

生成纯器乐曲目。
参数类型必填说明
titlestring曲目标题
promptstring器乐风格描述(如 “cinematic orchestral”, “lo-fi hip hop”)
coverImageIdstring封面图片 ID
  • Provider: suno_instrumental
  • Cost: 25 credits
  • Plan: Pro+

addVocals

为已有器乐曲目添加 AI 人声。
参数类型必填说明
sourceAudioIdstring源器乐曲目 ID
vocalStylestring人声描述
titlestring标题
coverImageIdstring复用已有封面;如果不传则使用 Seedream 生成封面
stylestring风格标签(如 “Jazz”, “Pop”, “R&B”)
negativeTagsstring排除元素(如 “autotune, screaming”)
styleWeightnumber风格权重 (0-1)
weirdnessConstraintnumber创意程度 (0-1)
audioWeightnumber与源音频一致性 (0-1)
  • Provider: suno_add_vocals
  • Cost: 25 credits
  • Plan: Pro+

Credits 计费

工具成本所需方案
generateMusic25 creditsPro+
generateInstrumental25 creditsPro+
addVocals25 creditsPro+

计费流程

Credits 在 kira-music-worker 中扣除(非 kira-be),Tool 仅验证资格。同一 taskId 不会重复扣费。

任务生命周期

异步处理流程

1

AI Tool 创建任务

Music tool 检测 NSFW → 验证 Credits → 在 thread_version.audios JSONB 中插入记录
2

发布到 NATS

Agent onFinish 回调中,遍历 pending 状态的音乐任务,逐个发布到 tasks.music.{provider}.{taskId} subject
3

Worker 处理

kira-music-worker 消费消息,原子扣费,调用对应 Provider API,更新状态为 processing
4

后处理

封面生成(Suno Cover API,免费)+ 歌词对齐(Suno Timestamped Lyrics,$0.0025)并行 → 嵌入 ID3 标签 → 上传到 Supabase Storage → 更新状态为 completed
5

实时通知

通过 Centrifugo WebSocket 推送状态变更到前端(processing → completed/failed)

NATS 消息格式

interface MusicTaskMessage {
  taskId: string;
  userId: string;
  threadId: string;
  provider: "suno_unofficial" | "suno_instrumental" | "suno_add_vocals";
  title?: string;
  lyrics: string;
  prompt?: string;
  instrumental?: boolean;      // generateInstrumental 时为 true
  coverImageId?: string;
  sourceAudioId?: string;      // addVocals 时存在
  style?: string;              // addVocals
  vocalStyle?: string;         // addVocals
  negativeTags?: string;       // addVocals
  styleWeight?: number;        // addVocals
  weirdnessConstraint?: number; // addVocals
  audioWeight?: number;        // addVocals
}

WebSocket 通知格式

// Centrifugo 频道: {userId}/{threadId}#{userId}
{
  type: "audio_status";
  taskId: string;
  audio: {
    taskId: string;
    status: "processing" | "completed" | "failed";
    audioId?: string;       // completed 时存在
    coverId?: string;       // completed 时存在
    durationMs?: number;
    syncedLyrics?: Array<{ text: string; start: number; end: number }>;
    errorMessage?: string;  // failed 时存在
    url?: string;           // completed 时 Signed URL
    coverUrl?: string;      // completed 时 Signed URL
  }
}

后处理管线

音乐生成完成后,kira-music-worker 对音频进行后处理:
显示歌词直接使用原始 task.lyrics(用户提供,已有行/段结构),无需 LLM 重新分组。Suno Timestamped Lyrics 仅提供逐词时间戳用于卡拉OK同步显示。

Suno Cover API (封面生成)

  • API: POST /api/v1/suno/cover/generate → 轮询 GET /api/v1/suno/cover/record-info
  • 输入: taskId (Suno 音乐生成任务 ID)
  • 输出: 2 张风格不同的封面图,取第一张
  • Cost: 0 credits (免费)
  • 处理: 下载 → resize 1024x1024 → JPEG → 生成 blurhash → 上传 Storage
  • 限制: 每个 taskId 只能生成一次 cover

Suno Timestamped Lyrics (歌词对齐)

  • API: POST /api/v1/generate/get-timestamped-lyrics
  • 输入: taskId + audioId (Suno 音频 clip ID)
  • 输出: alignedWords[] — 每个 word 带 startS / endS 时间戳
  • Cost: 0.5 credits ($0.0025)
  • 用途: 逐词时间戳用于卡拉OK同步显示,显示歌词直接使用原始 task.lyrics
  • 跳过: instrumental 曲目不做歌词对齐

ID3 标签嵌入

将以下元数据写入 MP3 文件:
  • Title (歌曲标题)
  • Artist ({用户名} via Kira Art)
  • Cover image (APIC frame)
  • Synced lyrics (SYLT frame, 卡拉OK格式)

成本明细 (每首歌)

项目成本来源
音乐生成$0.06 (12 credits)Suno API
封面生成$0 (免费)Suno Cover API
歌词对齐$0.0025 (0.5 credits)Suno Timestamped Lyrics
合计≈ $0.0625

数据模型

音频状态存储在 thread_version 表的 JSONB audios 数组中,无独立音频表:
interface Audio {
  // 创建时确定(不可变)
  toolCallId?: string;
  toolName?: string;
  taskId: string;
  provider: "suno_unofficial" | "suno_instrumental" | "suno_add_vocals";
  title?: string | null;
  lyrics?: string | null;
  prompt?: string | null;
  instrumental?: boolean | null;
  sourceAudioId?: string | null;
  hasVocals?: boolean | null;
  hasInstrumental?: boolean | null;
  createdAt: string;

  // Worker 更新(可变)
  status: "pending" | "processing" | "completed" | "failed"
    | "insufficient_credits";
  audioId?: string;         // 存储路径
  coverId?: string;         // 封面图片路径
  coverWidth?: number;
  coverHeight?: number;
  durationMs?: number;
  syncedLyrics?: Array<{ text: string; start: number; end: number }>;
  errorMessage?: string;
}

文件存储结构

agent_message/
├── {userId}/
│   └── {threadId}/
│       ├── music_{uuid}.mp3       # 原始音频
│       └── mc_{uuid}.jpg          # 封面图片
└── feed/
    └── {userId}/
        ├── music_{uuid}.mp3       # Feed 音频副本
        └── mc_{uuid}.jpg          # Feed 封面副本

Feed 集成

音频作为一等内容类型发布到 Feed:
{
  audioId: string;           // Feed 存储路径
  coverId?: string | null;   // 封面路径
  lyrics?: string | null;
  prompt?: string | null;
  durationMs?: number | null;
  coverWidth?: number | null;
  coverHeight?: number | null;
  syncedLyrics?: Array<{ text: string; start: number; end: number }>;
}
  • Feed 表 media_type 字段现支持 "image" / "video" / "audio"
  • 音频和封面从 message 存储复制到 feed 存储
  • 发布前进行内容审核(封面图片 + 歌词/prompt 文本)
  • 使用封面图片生成 embedding 进行向量相似度检查

清理与删除

删除消息或回溯时,自动清理关联的音频文件:
  1. thread_version.audios 提取 taskId 列表
  2. 获取对应的 audioIdcoverId 存储路径
  3. agent_message bucket 中删除文件
  4. 清理 NATS 中未消费的任务(purgeMusicTasks

相关文档