Music Agent | OuOglimmer's BlogMusic Agent
# Music Agent · 多 Agent 音乐创作平台
> 项目介绍 / 功能 / 实现方式 / 难点 / 总结
## 一、项目介绍
Music Agent 是一个基于 Full Stack FastAPI Template 二
Music Agent · 多 Agent 音乐创作平台
项目介绍 / 功能 / 实现方式 / 难点 / 总结
一、项目介绍
Music Agent 是一个基于 Full Stack FastAPI Template 二次开发的多 Agent 音乐创作平台。前端是一个 Suno 风格的音乐创作工作台(三栏式:项目资源库 / 创作中心 / 波形播放器),后端同时提供两条创作路径:
- 对话创作 Agent:一个可流式对话、可调用乐理与创作工具的单 Agent,适合"边聊边改"的交互式创作;
- 多 Agent 创作流水线:LangGraph 按音乐团队角色(和声 → 乐理校正 → 旋律 → 贝斯 ∥ 鼓 → 评审 → 渲染)编排的自动化生产线,从一句自然语言描述直接产出四轨 MIDI 总谱与 MP3 成品。
围绕这两条主线,项目还配套了语音交互(ASR/TTS)、音乐知识库 RAG(向量 + BM25 混合检索)、MCP 工具生态(FastMCP 服务端 + MCP 客户端)、Agent 治理体系(意图识别、反馈、人工接管、LLM-as-judge 评估)以及零依赖的全链路可观测性,形成"创作 → 反馈 → 评估 → 改进"的完整闭环。
技术栈总览
| 层 | 技术 |
|---|
| 后端 API | FastAPI + SQLModel + Pydantic,PostgreSQL + Alembic 迁移 |
| Agent 编排 | LangGraph / LangChain,LLM 走 DashScope(OpenAI 兼容接口) |
| 乐理与音频 | music21(乐理分析/总谱写入)、fluidsynth + FluidR3_GM(MIDI→WAV)、lame / ffmpeg(→MP3) |
| RAG | ChromaDB 向量库 + 自实现 Okapi BM25 + RRF 融合 |
| MCP | MCP Python SDK,FastMCP 以 Streamable HTTP 发布于 /mcp,同时支持远程 HTTP / 本地 stdio 客户端 |
| 前端 | Next.js(App Router)+ React 19 + Tailwind CSS v4 + shadcn/ui + TanStack Query |
| 语音 | DashScope qwen3-asr-flash(识别)/ qwen-tts(合成) |
| 部署运维 | Docker Compose、Traefik 自动 HTTPS、uv / pnpm 工具链 |
二、功能
2.1 对话创作 Agent(POST /api/v1/agent/chat)
- 流式 SSE 对话,事件序列
session → meta → delta* → done/error;
- 内置乐理查询工具:和弦进行分析(调性/罗马数字)、音阶与顺阶和弦速查、BPM 时长规划、歌词结构检查;
- 内置创作工具:和弦进行生成、贝斯线生成、鼓点生成、旋律校验、多轨 MIDI 渲染;
- 支持多模态输入:图片(视觉理解)、文本/Markdown(自动入 RAG 知识库)、音频(直接转写注入本轮对话)。
2.2 多 Agent 创作流水线(POST /api/v1/agent/compose)
- 按角色编排:和声 Agent → 乐理校正(mcp42)→ 旋律 Agent → 贝斯 ∥ 鼓(并行)→ 评审 Agent → 渲染;
- 评审不通过且未超轮数上限时自动返工,评审意见注入旋律 Agent 提示词重新创作;
- 产出四轨总谱(MIDI),自动渲染为 MP3,作品可通过
agent/scores/{filename} 下载;
- 可上传一段音频,自动检测 BPM / 调性 / 拍位等特征,作为创作的"打底"参数。
2.3 语音交互
- 浏览器
MediaRecorder 录音 → agent/voice/transcriptions 语音识别(ASR);
- 文本 →
agent/voice/speech 语音合成(TTS,带 LRU 缓存),支持长文本自动分段合成再拼接;
agent/voice/config 能力协商,前端按浏览器支持的编码格式自动降级(webm-opus → ogg → mp4)。2.4 音乐知识库 RAG
- 向量 + BM25 混合检索,RRF(Reciprocal Rank Fusion)融合排序;
- 内置音乐乐理语料,开箱可用;对话中上传的文本资料自动入库,支持按会话/全局两种作用域。
2.5 MCP 工具生态
- 本项目的乐理/创作工具同时通过 FastMCP 以 Streamable HTTP 发布在
/mcp,可被任意 MCP 客户端使用;
- Agent 自身也是 MCP 客户端:内置 mcp42(乐理校正)与 audio-analyzer(音频听觉解析,stdio 本地子进程),并可通过
AGENT_MCP_SERVERS 环境变量接入任意远程 HTTP / 本地 stdio 服务,工具动态发现。
2.6 Agent 治理体系
- 意图识别:规则计分 + LLM 兜底的双通道分类,敏感词命中自动升级;
- 用户反馈:消息级点赞/点踩 + 评论;
- 人工坐席接管:低置信/敏感会话生成工单,坐席给出指导后自动注入后续对话的系统提示;
- LLM-as-judge 离线评估:按意图、提示词版本分组打分,评估结果沉淀为改进项并自动注入系统提示,形成"评估 → 改进 → 再评估"闭环。
2.7 全链路可观测性与账号体系
- 零第三方依赖的可观测性:JSON 结构化日志、trace/span 链路追踪、延迟直方图与"路由 × 阶段"耗时矩阵,超级管理员经
/api/v1/observability/* 查询;
- 开箱即用的账号体系:JWT 认证、密码哈希、邮箱找回密码、管理员面板(继承自模板)。
三、实现方式
3.1 多 Agent 编排:LangGraph 状态图(backend/app/agent/composer.py)
- 以
MusicBlueprint(TypedDict)作为贯穿全图的"共享状态板":各节点只读写自己负责的字段(chords / melody / bass / drums / review / midi_path 等),节点间不传对话历史、只传结构化数据;MIDI 等大产物落盘,状态里只传路径。
- 图结构为
harmony → theory → melody → (bass ∥ drums) → review → render:贝斯与鼓的并行通过 LangGraph 的 fan-out 边实现,两条边在 review 汇合(fan-in)。
- 返工由条件边
route_after_review() 驱动:评审不通过且 revision_round <= max_revisions 时回到旋律节点,评审意见作为 revision_feedback 注入提示词。
- 按角色分温度的 Specialist LLM:和声 0.4 / 旋律 0.9 / 评审 0.2,实例按角色缓存。
- 流式输出由
graph.astream(inputs, stream_mode=["updates","values"]) 双模式驱动:updates 翻译成用户可读的"舞台进展"事件,values 记录最终态供 done 事件取产物。
3.2 对话 Agent 主循环:手写工具调用循环(backend/app/agent/agent_core.py)
- 不使用 LangChain AgentExecutor,而是在
max_iterations 上限内手写循环:llm.astream() 逐 chunk 推给前端的同时,按 tool_call_chunks 的 index 增量拼装工具名/ID/参数 JSON;无工具调用即结束,有则构造 AIMessage(tool_calls=...) → 逐个执行 → ToolMessage 回填进入下一轮。
- 多模态消息构造(文本 + base64
image_url)、意图指南与运营改进项注入系统提示、历史窗口裁剪,均由 _build_messages() 与路由层的 build_system_prompt 协作完成。
3.3 确定性创作层与音频渲染(backend/app/agent/tools/、audio_render.py)
tools/composition.py 是不依赖 LLM 的确定性创作层:情绪 → 和弦级数模板、四种贝斯律动(root_fifth/octave/eighth 等)、GM 鼓位事件表、music21 多轨总谱写 MIDI——既作为流水线的兜底,也作为 Agent 的创作工具。
- 音频渲染
MIDI → WAV → MP3 采用异步子进程 + 两级降级:lame 缺失则交付 WAV;fluidsynth/音色库缺失则整体退到 ffmpeg(自带 fluidsynth 合成器)。渲染失败返回 None 而非抛异常,保证 MIDI 产物不受影响。
3.4 RAG 混合检索(backend/app/rag/)
hybrid.py 纯标准库零依赖:中文友好分词(ASCII 词 + CJK 单字 + CJK 二元组,二元组保证"副歌/贝斯"类术语命中)、Okapi BM25(k1=1.5, b=0.75)、RRF 融合(k=60,按排名融合,对分数刻度不敏感、免归一化)。
retriever.py:hybrid_search() = Chroma 向量召回(带 session_id ∈ {当前会话, global} 过滤)+ BM25 召回 → RRF 融合 → top_k;两路任一失败自动降级为另一路;BM25 索引按作用域缓存,以 collection count 变化做失效。
optimizer.py 网格搜索检索超参(top_k / fetch_k / 双路权重),评估与线上共享同一套 BM25/RRF 原子组件,保证"调参测的 = 线上跑的";选优排序键对"双路都启用"的组合给予加成,为嵌入模型漂移留词法兜底;结果落盘 rag_tuning.json 热加载。
evaluation.py 自动构建 golden 集(TF 高频术语作查询、出处块作标注),指标 recall@k + MRR,使用 CRC32 哈希词袋的确定性离线嵌入,全程不触网,可作回归基线。
3.5 MCP 三件套(backend/app/agent/mcp_*.py)
mcp_registry.py 注册中心:支持 streamable HTTP 与 stdio 双传输;_schema_to_pydantic() 把 MCP JSON Schema 桥接为 LangChain BaseTool 时只取字段名与 description、类型一律 Any,保证任意合法 schema 都能桥接不丢工具;跨 Server 工具重名自动去重;单个 Server 发现失败不影响其他。
call_many() 支持同一会话连续调用多工具——stdio 型 Server 只拉起一次子进程完成全部调用。
mcp_audio.py 听觉解析:analyzer 输出人类可读文本,用正则反解析为结构化特征清单(BPM/调性/置信度/拍位/音级分布),并带防御性截断;视频容器(mp4/mov/webm)解不了时用 ffmpeg 抽出音轨重试一次;任何失败返回 None 回退请求参数。
3.6 语音链路(backend/app/agent/speech.py)
- ASR 走 DashScope 兼容网关的
chat/completions:音频以 base64 data URI 塞进 input_audio 消息,模型 qwen3-asr-flash。
- TTS 走 DashScope 原生接口:单次 600 字上限,按句切 550 字段分段合成,
wave 模块按帧拼接,非 WAV 请求经 ffmpeg pipe 转码(转不了降级返回 WAV)。
- 手写 LRU 缓存(OrderedDict):key 为 sha256(voice|format|text),双限制(条数 32 / 总量 8MB)按字节驱逐,命中打指标。
3.7 上下文工程与治理闭环(backend/app/agent/memory.py、intent.py、review.py、evaluation.py)
- 记忆:超出历史窗口的旧消息由 LLM 滚动压缩进
SessionMemory.summary(游标记录已摘要条数,挂 BackgroundTask 异步执行);规则抽取用户偏好("不喜欢"优先于"喜欢");按 token 预算装配上下文(坐席指导 > 会话摘要 > 用户偏好 > RAG 资料)。
- 意图:规则关键词计分先行,置信度低于阈值且有 Key 才走 LLM(temperature=0)裁决,超时/失败回退规则,永不阻塞主链路。
- 接管:每会话去重生成工单(pending → in_progress → resolved/dismissed),in_progress 工单的 guidance 注入后续对话系统提示。
- 评估:四维度(相关性/专业性/可执行性/清晰度)0-10 打分,按 intent 与 prompt_version 分组聚合落库;改进项(ImprovementNote)激活即注入系统提示。
3.8 SSE 与前端(backend/app/agent/sse.py、frontend/src/components/Studio/)
- SSE 只用 16 行封装事件格式与响应头(
X-Accel-Buffering: no 禁反代缓冲);会话取消(CancelledError)时回滚本次新建的空会话,正常结束才成对落库用户/助手消息。
- 前端
composeStream.ts 手写 SSE 客户端(不用 EventSource,因为需要 POST + FormData + Bearer 头):TextDecoderStream + 手动按空行分帧、缓冲区处理半包、CRLF 归一化、支持 AbortSignal 中断。
- 录音 hook
useVoiceRecording.ts:MediaRecorder 按浏览器支持度选编码(webm-opus → ogg → mp4),状态机管理录音/转写,转写文本回填输入框让用户确认后再发送。
- 交互细节:流式 Markdown 渲染、打字点动画、错误重试按钮、中文输入法 Enter 发送冲突处理(composition 事件期间不触发发送)。
3.9 可观测性(backend/app/core/observability.py)
- 零第三方依赖(未引入 OpenTelemetry),三层标准库实现:
ContextVar 存 trace 上下文的 traced_span/traced(自动挂父 span,区分 ok/error/cancelled)、环形缓冲 TraceStore(512 条,可按 trace_id 回放整条链路)、带维度化 counter 与 p50/p95 直方图的 Metrics(含"路由 × 阶段"耗时矩阵)。
- 纯 ASGI 中间件接续客户端
X-Request-ID、响应回传 X-Trace-ID;manual_trace() 为 SSE/后台任务等无 HTTP 入口的链路手动开 trace。
四、难点
- LLM 输出不可信——三层防御。 LLM 返回的"乐理 JSON"可能带 markdown 围栏、字段缺失、和弦名非法。项目采用三层防御:
_extract_json() 容忍围栏提取 → music21.ChordSymbol 逐个校验、按小节数补齐/截断 → 校验仍失败则退到确定性模板(和弦走情绪级数模板,旋律走和弦琶音兜底)。最终效果是:即使完全没有 LLM API Key,整条创作流水线依然可以跑通并产出成品。
- 全链路确定性降级。 与上一条同源的架构原则:任何外部依赖挂掉都不阻塞主流程——mcp42 不可达则静默跳过乐理校正、评审 Agent 不可用则默认放行、MP3 渲染失败只丢音频不丢 MIDI、RAG 检索失败退空上下文、BM25/向量任一路失败退另一路。
- 贝斯/鼓并行后的汇合(fan-out/fan-in)。 LangGraph 中
melody 同时指向 bass 与 drums,两节点并行执行后在 review 汇合,需要在共享状态板上保证两路写入互不冲突,并正确处理"一路失败"时 join 边的推进。
- SSE 流式与工具调用的叠加。 对话 Agent 要在同一个响应里同时做到"文本逐字推流"与"工具调用参数增量拼装",手写循环需要按
tool_call_chunks 的 index 对齐拼 JSON;前端也要手写 SSE 解析(EventSource 不支持 POST/自定义头),自行处理分帧与半包缓冲。
- 协议与传输的适配。 DashScope 兼容网关没有
/audio/* 接口,ASR 需要改造走 chat/completions + base64 音频消息;TTS 又要走原生接口且单次有字数上限,需要按句切分、帧级拼接、格式转码三级处理。MCP 侧则要把任意 JSON Schema 桥接为 LangChain 工具而不丢工具,并处理 stdio 子进程的会话复用。
- 混合检索的两路对齐与调参可信度。 向量召回与 BM25 召回是两套分数体系,RRF 按排名融合解决了刻度不兼容问题,但两路 Document 需要以文本为键对齐;自动调参必须与线上共享同一套检索原子组件(内存态 Chroma + 同一 BM25/RRF 实现),否则"调参测的"和"线上跑的"不是一回事。
- 上下文预算工程。 会话摘要、用户偏好、RAG 资料、坐席指导都要塞进有限窗口,需要按优先级分配 token 预算;摘要又必须异步执行(BackgroundTask)以免拖慢首字延迟,失败时只影响新鲜度不影响可用性。
- 评估闭环的数据设计。 要支撑"按意图、按提示词版本分组聚合评估",消息表必须直接落
intent / prompt_version 字段且成对落库;会话中途取消要回滚脏数据(删除新建的空会话)。
五、总结
Music Agent 以"多 Agent 协作的确定性生产线"为核心思路,把音乐创作拆解为和声、旋律、贝斯、鼓、评审等专业角色,用 LangGraph 编排为可返工的流水线,并在每个环节为 LLM 的不确定性准备了确定性兜底——这是本项目最重要的工程决策,使得系统在依赖缺失、网络故障、输出劣化的情况下始终能交付可用产物。
项目的另一个特色是完整性:它不只是一个"调 API 的 Demo",而是覆盖了 Agent 系统落地的完整生命周期——意图识别与提示词工程管入口,记忆与 RAG 管上下文,反馈与人工接管管异常,LLM-as-judge 评估与改进项注入管迭代,零依赖可观测性管运行时;治理数据(intent、prompt_version、feedback)从消息表层面就被设计为可聚合的,评估闭环不是事后补丁而是数据模型的一部分。
工程细节上有几处值得沉淀的经验:RRF 让异构检索免归一化融合、调参与线上共享实现保证评估可信;"只取字段名的 Schema 桥接"让 MCP 工具接入零失败;手写 SSE 循环同时承载文本推流与工具调用拼装,换来了比 AgentExecutor 更细的流控。
已知待修复问题:调研中发现三处同一笔误的 Python 2 语法错误,会导致对应模块在 Python 3 下无法导入,建议尽快修复:
backend/app/agent/intent.py:267
backend/app/agent/evaluation.py:67
backend/app/core/observability.py:394
三处均为 except TypeError, ValueError:,应改为 except (TypeError, ValueError):。
可扩展方向:评审 Agent 接入音频听觉特征做客观评审、流水线支持人声/更多轨道、RAG golden 集引入真实用户问答、可观测性接入 OpenTelemetry 导出。