跳到正文
返回博客
作者
Zinian
发布时间
2026年7月6日
阅读时间
10 分钟阅读

AI 工程

做 AI DJ,难的不是选歌

Claudio FM 是一个 AI 私人电台。做它的过程里我发现,让一个 AI DJ 可信,难点不在选歌,而在教它何时闭嘴、说什么、以及在它必然出错时别把电台拖垮。

Claudio FM 是我做的一个 AI 私人电台。你打开它,按下播放,它就开始为你经营一档节目:看现在几点、你最近在听什么,挑几首歌,在歌曲之间用一个 AI DJ 的声音开场、串场,该安静的时候留白。

它不是歌单生成器,也不是等你下命令的聊天机器人——更像后台有个人,在你没说话的时候也把这档节目主持下去。创意的种子来自抖音博主 mmguo 的电台风格:音乐,加上一个会讲述的主持人。

做之前我以为最难的是选歌,做完才发现,选歌是整件事里最简单的一环。真正难的是让这个「人」可信——它得知道什么时候说话、说什么、什么时候闭嘴,还得在它必然出错的时候,不把整档电台拖垮。

主持不是推荐

「有人主持」和「推荐了哪几首歌」是两件事。推荐是个排序问题,模型早就够用了;主持难在另一件事:知道什么时候一句都不该说。

一个 DJ 如果每段间隙都要念一句,很快就变成噪音。所以 Claudio 的 DJ 文案是一句一句拆开的短片段,该安静时就让音乐自己说话。为了让它开口不尬,它得知道正在放什么、放到哪了、下一首是什么、最近说过什么——我把这套东西做成一个独立的「陪听上下文」,在生成每段文案前喂给模型。

开不开口,是模型读完上下文后自己判断的;我没有写死「每两首歌播报一次」这类规则,只在 prompt 里留了一句约束:只在该开口的节点说,别主动高频插话。这里要先说清一个容易误会的地方:择时我交给模型判断,但接下来你会看到,「说什么」我反而收得很紧——把不确定性关进哪个笼子,是分开决定的。

这套陪听上下文是内存里的一份实时快照——现在这首歌、播放进度、队列位置、在不在说话——每五秒同步一次,遇到播放、暂停、切歌这些节点立即同步。它不写数据库:这种高频状态一旦落库,很快会变成谁都不敢动的性能负担,而它的正确性本来就不如时效性重要,出错丢掉就是。

DJ 说什么,比什么时候说更难

决定了什么时候开口,还剩一个更难的问题:开口说什么。

最容易翻车的做法是让模型自由发挥。你以为给它自由它会更像人,实际上它会更像一个不停找话说的客服——热情、正确、但没有分量。

所以我没给它自由,给了它一个结构。每段 DJ 文案都按同一个五段式来搭:先落一个具体的锚点(这首歌的一件事实——某个乐器、某段编排),再连到一个人的故事或它承担的情感,然后转向此刻(几点、什么天气、你可能在做什么),接着给一个画面(比如把你放回某个傍晚),最后一句把你送进音乐。

这个结构不是让文案变漂亮,是让它有落点。一个只会说「接下来这首很好听」的 DJ,和一个能把一首歌的某个细节连到你此刻处境的 DJ,差的不是辞藻,是它有没有在「看」你。

语气也是定死的:第一人称、克制的敬语、句间留一点呼吸、先关心听的人再说自己。它甚至分中文和日语两套人格,用一个环境变量切换——日语听众要的陪伴感,和中文听众的落点不一样。我给它写死这几条之后,它反而不像客服了:约束够准,它才不像一个模型。

每个 AI 环节都会失败,而电台不能停

真正吃掉我最多时间的,是个更朴素的问题:这套东西里每一个 AI 环节都会失败,而电台不能停。

DJ 文案靠 LLM 生成,语音靠 TTS 合成,歌靠音乐源解析——三个都不可靠。TTS 我一开始接的是云端的 MiniMax,跑着跑着就撞上配额,甩回一个 2056 usage limit exceeded,这也是后来我把默认换成本地的原因之一。网易云的歌随时可能因为版权变成灰歌,或者链接过期,点下去没声音。如果我把这些当成正常流程的一部分,那任何一个抽风,整档电台就卡在那儿。

所以我给每一层都留了退路:DJ 声音默认走本地,本地出问题再退到云端 provider;音乐源解不出可播链接,就换增强接口、再不行用 yt-dlp 兜底;那个陪听上下文只要一出错,就直接降级成空的,绝不允许它阻塞播放、生成或补歌。

能随时切的前提,是每一层都不绑死某一家——TTS、音乐源、LLM 都可以换,切换不用动上层逻辑。代价是配置项变多,光环境变量就六十多个;换来的是任何一家挂了都不致命。

降级成空也有代价,我不想假装它没有。上下文一旦为空,DJ 就不知道你此刻在听什么了——它不会卡住,但会退回更保守的过场:说得更短、更多留白,像一句普通的报时。我能接受,是因为它退到的是「通用但不尬」,而不是「不懂装懂地胡说」——DJ 拿不准的时候,宁可少说。

这就是我后来想清楚的原则:AI 在这个系统里是增强,不是依赖。贴合当下是增强,一旦出错就把它整个摘掉,剩下的部分仍然独立成立。

声音一致,比省事更重要

DJ 的声音用云端还是本地,我犹豫过很久。

云端省事,本地麻烦——要装运行环境、首次启动预热十几秒到半分钟、还得自己做响度标准化。但我最后把默认切到本地的一个零样本声音克隆(我给它起名叫林悠)。原因不是性能,是一致:这个电台的核心体验是有一个固定的人在陪你,声音一旦时好时坏、甚至因为配额被换了个音色,那个「人」就散了。

这里的「一致」也不是免费的:零样本克隆的音色锁在四段固定参考音频上——温柔、希望、坚定、难过各一段,换掉参考,音色就漂。至于预热那十几秒到半分钟,就是上一节说的「默认走本地」在开机时一次付清的,运行时不再等。剩下的代价是 setup 变复杂、用户得装依赖,所以我没删云端,留成可切换的兜底。

单人电台,是刻意选的形状

Claudio 到今天还是一个严格的单人电台:一个本地听众、一个播放会话、一个后台进程。没有多用户、云同步、登录。这不是没来得及做,是故意没做——一旦引入多用户,就要处理账号、权限、数据隔离、并发,这些会把一个陪你听歌的小东西变成一个需要运维的服务。

功能上也一样克制。电台有一个 request line,你可以像打进电台一样发一句话,它把你的话当成一个信号、调整接下来的节目方向,但我刻意没做成「你说什么它立刻放什么」——那样它就退回成一个点播器了。它接受信号,但仍然由 DJ 来编排,因为一旦有求必应,「有人主持」的感觉就没了。

有些东西我至今没做完

我不太喜欢把一个项目写得像已经收工了。Claudio 有几块到现在还是残缺的,我也知道它们残缺。

陪听上下文现在只知道在放哪首歌、放到哪了,但它还看不到这首歌的曲风,也拿不到歌词——数据结构里我把这两个位置都留好了,只是还没有哪一路数据把它们喂进去。这意味着 DJ 现在能贴合「此刻」,但还不能贴合「这首歌正唱到哪一句」。我没硬做完,是因为口子留对之后,它们是增强不是修复:缺了电台照常转,补上 DJ 会更贴。

还有一类更难收拾的残缺,在原生界面的动效上。我给这个 macOS 客户端定了十几条交互底线——第一次点击必须立刻有反馈、生成中不能静默卡住、进度条要能拖、动效要短要轻要能被打断、要尊重系统的「减弱动态效果」。问题是这些 build 能过、测试能过,但「它摸起来对不对」没法自动验证。到今天这一块我仍然靠人一帧一帧看、靠截图对比。

一个还在迭代的东西,本来就该有几块是明确知道「还没到位」的。我把它们写出来,是因为假装它们不存在,比它们残缺更糟。

可靠性不在模型里

做完 Claudio,我对「AI 应用靠不靠得住」有了一个更具体的看法。可靠性几乎不在模型有多强——把三个最强的模型串起来,只要没人想过它们会怎么失败,照样是个随时卡死的产品。

但我想说的,比「要处理失败」更进一步:可靠性是默认路径的设计问题,不是异常处理的补丁问题。真正决定这档电台稳不稳的,不是我写了多少 try-catch,而是我一开始就把默认声音放在本地、把陪听上下文设计成能凭空消失、把每个 provider 都做成可替换的——失败发生时不需要抢救,因为系统本来就是照着「某一层随时会没」的前提搭的。

一个 AI DJ 值不值得信,不看它说得多漂亮,看它出错的时候,音乐还在不在放

相关文章

和这个主题相邻的更多笔记。

用本地工具和云模型搭建个人 AI 工作台

精选
AI 工程
2026年5月8日8 分钟阅读更新于 2026年5月10日

一个实用的 AI 工作环境:能起草、测试和发布,又不会把流程缠在一起。

ai-agent
automation
codex
阅读全文

为技术作品集网站设计真正有说服力的案例研究

精选
产品构建
2026年5月5日6 分钟阅读更新于 2026年5月7日

怎样组织案例研究,才能证明判断力,而不只是展示结果。

portfolio
case-study
writing
阅读全文

把 KizunaIndex 做成公共索引之后,我学到的几件事

精选
产品构建
2026年5月1日7 分钟阅读更新于 2026年5月3日

公共索引真正有用,往往不是因为页面多复杂,而是因为内容模型足够小、足够清楚、足够容易改。

nextjs
data-modeling
public-index
阅读全文

下一步

继续浏览文章列表,或者把这篇文章里的问题展开成一次具体讨论。