一个会听、会想、会跟你实时合奏的 AI。

https://github.com/Underwater-Agent/JamPalgithub.com/Underwater-Agent/JamPal

一、缘起:NVIDIA DGX Spark Hackathon

2026 年 7 月 13 日,我们(

@tttggg @Sitian Qian )组队参加了 NVIDIA DGX Spark 黑客松。音乐是我们共同的交集,也让我们很快锁定了一个方向——

做一个 AI 乐手。不是生成一首歌给你听的工具,而是能跟你实时 jam 的搭档。

你弹两个小节,它听。它在接下来的两个小节回应你。循环往复。像一个真正的 jam session

我们叫它 JamPal


二、背景:为什么这件事值得做

硬件:DGX Spark

DGX Spark 是 NVIDIA 的桌面级 AI 计算机。ARM64 架构,搭载 Grace CPU 和 GPU,整机功耗不超过 300W,可以放在桌面上安静运行。它的核心卖点就是本地大模型推理——不需要云端 API,不需要 GPU 集群,一台机器就能跑一个像样的 LLM。

这也恰好是黑客松的核心命题:端侧算力能做什么以前只能在云端做的事?我们的答案是——实时音乐协作

LLM + 音乐:天然的时间冲突

大模型做音乐有一个根本性的矛盾。音乐是时间的艺术——一个延迟超过 50 毫秒,乐手就能感觉到。但 LLM 的推理延迟通常在秒级,复杂 prompt 甚至到十几秒。

这就是为什么大多数 AI 音乐工具走的是”生成一段音频”路线——离线生成,不需要实时交互。但我们想做的是 live jam,是交互,是对话。这个矛盾必须从架构层面解决,不能靠加速推理来硬抗。

Strudel:浏览器的活编程音乐引擎

在浏览器里做实时音乐调度,我们选择了 Strudel。Strudel 是一个基于 Web Audio API 的活编程音乐环境,支持精确的节拍调度、MIDI 输出、钢琴卷帘可视化。最关键的是——它是浏览器的原生能力,不需要任何后端。

对 JamPal 来说,Strudel 承担两个角色:音乐运行时(精确的节拍调度和音频合成)和解释层(用户能看到 AI 的决策如何转化为具体的乐谱变更)。大模型输出 JSON 建议,本地编译器把建议编译成 Strudel pattern——安全、可验证、可解释。


三、团队与分工:三条线并行

四个人,三个角色:

角色负责技术栈
A — 前端React/Strudel 界面,Web MIDI 输入,钢琴卷帘,可视化TypeScript, React, Vite, Strudel
B — 音乐引擎乐句分析,响应生成,规则 critic,安全编译器TypeScript, 音乐理论
C — 服务端WebSocket 网关,LLM 集成,会话账本,评估体系Node.js, TypeScript, Qwen / StepFun

第一天(7 月 13 日)只是脚手架和一个 first commit。真正的开发从 14 日开始——三条线同时起步:

  • A 搭起了 Web MIDI 录制、Strudel runtime、React 界面骨架(PR #1)
  • B 完成了乐句分析器、规则 critic、候选生成、fallback 编排(PR #2)
  • C 建好了 WebSocket 服务、LLM 适配层、评估框架(#3)

到 14 日晚上,三个 package 全部有了雏形。我们做了一个关键决定:先让三条线独立跑通,再对接。


四、最关键的架构决策:两个循环

JamPal 的架构里有一个设计决定贯穿了全部 10 天的开发。我们把它写成了 ADR-003(架构决策记录):

两个时间尺度,互相解耦。

Browser — Fast Loop (< 100ms)         DGX Spark — Slow Loop (LLM)
┌──────────────────────────┐           ┌─────────────────────────┐
│ MIDI → 分析 → 作曲        │           │ Qwen 35B / StepFun     │
│      → 规则校验            │◄──────────│   → 建议生成             │
│      → 安全编译            │ WebSocket │   → 截止时间检查          │
│      → Strudel → 音频输出  │           │   → 会话账本             │
└──────────────────────────┘           └─────────────────────────┘

快循环在浏览器里。从 MIDI 输入到音频输出,全程在 100 毫秒内完成。这个路径是纯确定性的,不涉及任何网络调用,不依赖任何大模型。

慢循环在 DGX Spark 上,运行本地部署的 Qwen 35B(前期调试阶段使用 StepFun 云端 API 快速迭代)。每 4 到 8 个小节,浏览器通过 WebSocket 发送一个乐句摘要。大模型生成音乐建议——”试试翻转旋律轮廓”、”贝斯密度降到 0.4”、”空出第一拍”。服务器验证建议、检查截止时间,通过后才发给浏览器。

核心原则:模型建议,代码决定。 大模型的建议迟到就直接丢弃。律动从不等人。

这个设计意味着——即使大模型挂了、网络断了、JSON 解析失败了——音乐不会停。本地引擎一直在演奏。模型是锦上添花,不是必需品。

我们把这个原则写成了 19 条 ADR,从「浏览器拥有音乐时钟」到「建议需要证据支持的准入」,每一步都有书面理由。


五、大模型策略:StepFun 先行,Qwen 落地

黑客松只有 10 天。我们决定分两步走:

开发阶段(Day 1-7):用 StepFun 云端 API 调试。 原因是:

  • 即开即用,不需要在 Spark 上折腾模型部署
  • API 稳定可复现,方便写评估框架
  • prompt 迭代快——改完就能跑 smoke test

我们在 StepFun 的 step-3.7-flash 模型上完成了全部 prompt 工程、评估框架、生命周期管理、以及 production 准入(ADR-019)。

落地阶段(Day 8-10 + hackathon 后):部署 Qwen 35B 到 DGX Spark。 真正的「本地优先」承诺必须由本地模型兑现。Qwen 35B 在 Spark 的 ARM64 架构上运行,通过 TensorRT-LLM 加速推理。前期在 StepFun 上验证过的 prompt 和评估流水线直接迁移——因为整个 LLM 集成层本身就是 OpenAI 兼容的适配器,后端可替换。

这一步的逻辑是:用云端 API 验证产品逻辑,用本地部署兑现架构承诺。 两者共享同一套代码、同一套评估、同一套生命周期管理。


六、StepFun 集成:从 mock 到 production 的 5 天

StepFun 的接入是开发中最曲折的部分。

7 月 15-17 日:评估先行

我们没急着把大模型接进主流程。先写了一个评估框架(#3),用 mock conductor 生成基准,然后对比 StepFun 的输出质量。评估维度包括:

  • 成功率(JSON 解析、结构校验)
  • 延迟(P50 / P95)
  • 多样性(连续调用中建议签名的重复率)
  • 截止时间遵守(建议是否在 deadline 前到达)

这个框架后来救了我们的命——每次调 prompt、换模型参数、改超时配置,都能立即看到量化的影响。

7 月 18-19 日:生命周期与持久化

真正的难点不是调用 API,而是让异步建议在正确的时刻、以正确的状态、作用到正确的乐句上。

我们为每个建议候选设计了完整的生命周期状态机

pending → fetched → accepted → applied
                  ↘ rejected (超时)
                  ↘ expired  (missed deadline)

每一步都写入 append-only 的会话账本(#7),确保可回放、可审计、可复现。这是 ADR-011 和 ADR-019 的要求——每个 production 建议都需要「证据支持的准入」。

7 月 20 日:Production 开关

7 月 20 日凌晨,我们跑了第一次完整的 smoke test:

模式成功率P95 延迟多样性结论
Section 建议20⁄20 (100%)19.2s17 种签名✅ Production
Exchange 建议18⁄20 (90%)30.5s8 种签名⚠️ 评估中

Section 建议的 20 次调用全部成功,P95 延迟 19 秒——对于每 4 个小节触发一次的建议来说完全够用。基于 ADR-019 的 owner exception 条款,我们正式启用了 StepFun section 建议。

Exchange 建议有 2 次 provider 超时,加上更长的延迟和更低的多样性,保持在 evaluation 状态。


七、Demo 冲刺:最后 48 小时

7 月 21-22 日是 demo 冲刺。两天之内我们合并了 9 个 PR。

UI 面板(#16)

Demo 的核心展示面——可解释性。我们加了三个面板:

  • Piano Roll:用户音符(蓝)和 JamPal 回应(橙)实时滚动
  • What I Heard / What I Decided:乐句分析和决策依据,逐项展示
  • Pattern Diff:Strudel 乐谱变更,高亮差异

配合顶部状态栏(BPM、调式、风格、当前循环、模型状态),底部文本命令输入,整个 UI 形成了完整的「对话」质感。

缓存与确定性(#15, #19, #22)

Demo 不能依赖实时 API——现场网络不可控。我们建立了三层缓存策略

  1. LLM 缓存模式JAMPAL_LLM_MODE=cached,从 demo-cache.json 读取预生成的建议
  2. 确定性策略轮转(#21):多变量建议按 session 轮转,确保每次 demo 的多样性
  3. Exchange 多变量缓存(#22):为 exchange 模式准备多个变体,避免重复

这样无论是现场网络断了、StepFun 超时了、还是反复录制视频——demo 的行为完全可预测。缓存的内容来自真实的 StepFun 输出,观感和 live 模式无异。

Happy Birthday 彩蛋(#17)

Demo 需要一个「能让人记住」的瞬间。我们选了一首人人会唱的歌——Happy Birthday。

Fixture 系统生成 MIDI 输入,JamPal 分析旋律、生成伴奏、输出 MP3。从单音旋律到完整的 Funk 编曲——鼓、贝斯、和声、solo——全部自动编排。

Prompt 优化(#23)

7 月 22 日凌晨的最后一个 feature PR。我们重新设计了 prompt:加入风格指南(Funk 的特征、Lo-fi 的音色)、示例输出、音名映射。这些 prompt 设计直接复用于 Qwen 35B——因为模型输出 schema 不变,只是推理后端从 StepFun 换成了本地 Qwen。


八、三个最重要的体会

1. 先设计失败模式,再设计功能

JamPal 最被记住的功能不是 LLM 集成,而是断掉模型之后音乐还能继续

这是刻意设计的。我们在写第一行 LLM 代码之前就定了一条规则(ADR-014):确定性 fallback 是一等公民。这个决策让我们在整个开发周期里都保持清醒——每加一个 LLM 功能,都先确认 fallback 路径不受影响。

结果就是:demo 里最有力的镜头,是拔掉网线之后 JamPal 还在弹。

2. 评估框架比 prompt 工程更重要

我们在 prompt 上花的精力不到 20%。80% 的时间在造评估工具——smoke test、live evaluator、ledger、ADR-019 的证据支撑体系。

因为有评估框架,每次调 prompt 我们马上知道:成功率变了吗?延迟变了吗?多样性增加还是减少了?没有这些量化反馈,prompt 工程就是瞎猜。更重要的是——这些评估框架对 StepFun 和 Qwen 同样有效,省掉了一次完整的重新评估周期。

3. 架构决策要写下来

19 条 ADR 看起来像形式主义,但在 4 个人 10 天的节奏里,它是唯一能防止「我以为你改了那个」的东西。

每条 ADR 不超过 5 行。状态 + 原因 + 退出条件。遇到分歧就翻 ADR,没有的就当场写一条。10 天下来,没有一次因为设计理解不一致而返工。

4. 云端迭代 + 本地落地 = 最高效的组合

如果一开始就在 Spark 上部署 Qwen 35B,前三天会全部耗在模型编译、ARM64 兼容、推理框架调试上——而这些活和产品逻辑无关。

StepFun 让我们第一天就能调 prompt、第二天就能跑评估。到第八天需要本地部署时,所有 prompt 和评估管线都已经验证好了。Qwen 的上线本质上是一次「模型替换」,而不是重新开发。

先验证产品逻辑,再兑现架构承诺——这个次序在资源有限的 hackathon 里极其重要。


写于 2026 年 7 月 22 日,NVIDIA DGX Spark Hackathon 闭幕日。