Skip to content

【外滩大会2026】Lyra —— 放首日语歌,听着听着就看懂了 #110

Description

@xiaofenggan01

参赛项目名称

Lyra · 在听歌的过程中学习一门语言

团队 / 作者

晓风乾(个人参赛者)

我做了什么

市面上所有语言学习应用都在让你「学」,但绝大多数人打开它是靠毅力,不是靠喜欢。
我想做的是反过来:你本来就在听的那首歌,就是课本。

Lyra 是一个网页播放器。你像平时一样放一首日语歌,歌词跟着音乐逐字高亮;
听到不懂的地方,点一下那个词,它会告诉你这个词的读音、词性、词典义,
以及最关键的一层 —— 它在这一句歌词里到底什么意思
觉得没听清,点「循环这句」,播放器就在这一句上反复循环,直到你听进去。
听过的每首歌会变成夜空里的一个星座,学过的词是星座上的星点,越听越亮。

具体用百炼做了三件事:

  1. 逐字注音与切分:把整句日语歌词切成词,判断哪些是实词、哪些是助词,
    给汉字标假名读音和罗马音。日语的难点在于同一个汉字在不同词里读法完全不同
    (「行く」读 iku、「行う」读 okonau),必须结合上下文判断。

  2. 语境释义:这是产品的核心价值,也是它和「点词弹词典」的分界线。
    同样是「沈む」,词典义是「下沉、沉没」,但在「沈むように溶けてゆくように」这句里,
    模型给出的是「形容事物如沉入水中般缓慢、不可逆地消失或消融」——
    这是针对这一句生成的解释,不是词典搬运。

  3. 例句生成:用你听过的歌里出现过的词,生成新的例句,让同一个词在另一个语境里再出现一次。

工程上刻意守住的几条纪律(都有测试守门):

  • 模型只负责解释,不负责判分。 学习进度、掌握度、复习安排全部由确定性代码和数据库约束决定,
    模型永远不写掌握度 —— 因为「你学会了没有」这件事不能靠模型的自信程度来定。
  • 每个用户的学习数据物理分库隔离,一人一个数据库文件,跨用户串数据在物理上不可能。
  • 不做连胜、不做红叉、不做排行榜。 学语言最大的敌人是焦虑,不是懒。

站上已有 70 多首歌、6700 多条词与语法的理解结果,7 月上线至今持续在线。
上线前后累计有 200+ 位用户参与盲测,其中三位不同画像的用户(日语零基础、N3、只认罗马音)
各深度使用了两天 —— 下面踩坑记录第 4 条就是他们挑出来的问题。

使用的工具

  • OpenWork / 百炼 CLI
  • 百炼能力 / 模型:qwen-plus(逐字注音切分、语境释义、例句生成)、qwen-max(需要更强推理的分析任务);
    经百炼 OpenAI 兼容接口https://dashscope.aliyuncs.com/compatible-mode/v1)接入,
    使用 JSON 结构化输出模式保证返回可直接落库
  • Skill 名称:无 —— 直接经百炼 OpenAI 兼容接口调用 qwen 模型,未使用百炼 Skill / 应用框架
  • 其他:Python / FastAPI 后端,原生 JavaScript 前端(无框架、无构建步骤),
    SQLite 分库存储,MeCab 做日语形态分析给模型输出做接地校验

效果展示

在线体验:https://lyra.niuniu869.com (已开放注册,无需邀请码,建议戴耳机)

体验路径:注册 → 搜「夜に駆ける」→ 点播放 → 歌词逐字亮起 → 点任意一个词 → 看语境释义 → 点「循环这句」→ 看「地图」里的星图

项目链接(可选)

踩坑记录(可选)

1. 音源里混着「看起来正常」的残片,会静默毁掉体验。
有一首歌的音频缓存只有 81KB,但歌曲时长是 303 秒。用户点开能播,播到第 5 秒就静默卡死,
界面没有任何报错 —— 这比直接报错糟糕得多,用户会以为是自己网络的问题,傻等下去。
最初我判断是「下载中断」,加了 Content-Length 校验,结果重新下载还是 81KB
而且完整通过了校验 —— 说明上游返回的就是个 5 秒试听片段,不是传输中断。
最终按「时长 × 最低码率」做完整性下限校验(303 秒的歌至少该有 1.2MB),
并把这类曲目标记后从推荐和搜索里隐藏,再加一个每天跑的复检任务,
等上游音源恢复了自动放回列表。

2. 歌词源会返回「简体转写版」的日语。
《残酷な天使のテーゼ》48 行歌词里有 10 行是简体字形:「少年よ神になれ」(应为「神話」)、
」(应为「蒼い風」)。对一个日语学习产品来说,这是在教用户写错字。
这里踩了两个坑:一是以为换个 CJK 字体能解决 —— 不能,「话 U+8BDD」和「話 U+8A71」是
两个不同的 Unicode 码位,不是同一个字的两种字形,字体只能改字形不能改码位;
二是想过交给模型去改 —— 但字形映射有唯一正确答案,查表 100% 准确、可单元测试、零延迟,
而模型有幻觉风险,更可能顺手改动字数或断句,那会直接打断逐字高亮赖以对齐的时间轴。
最后用确定性映射表解决,并且只对已判定为日语的歌词生效 ——
第一版我把它放在通用的歌词解析函数里,结果中文歌的歌词也被「日化」了(「真歌词」→「真歌詞」),
全量测试当场挂了 3 个才发现。

3. 定时任务可以「安静地坏」。
复检任务写完、单元测试全绿、部署上线。但我按 cron 的真实方式跑了一次,
日志立刻报 'NoneType' object has no attribute 'headers' —— 我传了个 None 当会话对象。
如果没跑那一次,这个任务会每天准时执行、正常退出、日志看着一切正常,
而复检功能从第一天起就是死的,没有任何人会发现。
根本原因是单元测试把那个函数整个 mock 掉了,永远测不到真实调用契约。
单测绿不等于功能活。

4. 上线前找了三个不同画像的用户各用两天,问题几乎全是我自己想不到的。
一个日语零基础的上班族、一个 N3 水平的学生、一个只认罗马音的动漫迷。
三个人在同一个地方卡住:注册后弹出的口味问卷里的歌手他们一个都不认识。
零基础用户的原话是:「而且没有 YOASOBI —— 我就是冲它来的。」
查下来发现歌手列表是按「缓存里出现最多」排的,选出来的是系统内部的高频项,
而不是用户认得的名字。N3 用户则一眼看出片假名被逐字注了平假名读音(レ(れ)モ(も)ン(ん)),
指出这是纯视觉噪音 —— 会读片假名的人本来就会读平假名。
这些都不是能靠自己盯着代码看出来的。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions