每日听力课:逐字证据、每天最多准备一课,以及诚实的"未知"
语言: English | 简体中文
发布日期:2026-11-09
FLH 原本只处理我输入的问题。听力功能加入了第二个来源:法语视频加上我自己提供的转写稿,生成考试式听力练习;之后每天从播放列表拉取,最多自动准备一课,并可选通过 Telegram 通知。它分两次改动、两天完成。
我的工程贡献:把源材料与生成的练习分开,要求每条引用逐字匹配,把自动花费限制在每天一次尝试,并把超时或丢失的调用视为”未知”而非”失败”。
第一部分:以转写稿为依据的练习
Listening 视图保存视频链接、标题和可选的转写稿。材料、转写稿和练习是三张独立的只追加表。没有转写稿的链接会等待;Prepare exercise 返回 409,不调用 provider。
模型的输出必须以转写稿为依据:每段引用必须与某一行逐字一致,每个片段必须存在,不能出现源中没有的时间戳。越界片段、编造的时间、改写过的引用或 provider 错误,都会存为带原因的 failed 练习,转写稿保持逐字节不变。
答案、中英文理由、引用行和注释在 Reveal 前全部隐藏。引用的 0:16 链接到视频的 &t=16s。漏听的要点只有在点击 Save learning record 后,才通过已有的 capture 接口变成普通学习记录,进入与输入问题相同的可追溯流程。
第二部分:每日拉取,最多准备一次
每日拉取通过官方 YouTube Data API 读取播放列表,有严格边界:1–200 项、每次请求 50 项、每个响应 1 MiB,密钥放在请求头中。视频按 ID 去重,包括我手动保存的视频;新视频等待转写稿。不做语音识别,也不抓取网页。
开启 LISTENING_AUTO_PREPARE=daily 后,按 LISTENING_TIMEZONE 的本地日期每天最多准备一次,由部分唯一索引保证,而不只靠应用逻辑。Pull now 按钮和定时命令使用同一个服务,共享上限和运行租约:重叠的运行得到 409 busy,命令以 4 退出。通知只针对本次拉取准备好的那一课,并且只包含标题。
未知不等于失败
每次外部调用(OpenAI 或 Telegram)都在调用前插入尝试记录,结束时只更新一次。如果请求已写出但响应没有回来,结果是 unknown(由 httptrace.WroteRequest 判定),而不是 failed。
未知结果不会自动重试,因为它可能已经计费或已经送达。之后的拉取会跳过它。显式重试需要 confirm_unknown_retry,按钮上写着”可能再次计费”。丢失的响应先按 key 查询,只重发同一个 key。处理程序与客户端的取消解耦,关掉标签页不会丢弃已完成的结果。
验证
两部分都使用本地伪造的 YouTube、OpenAI 和 Telegram,配合真实 SQLite、真实处理程序和真实二进制运行,没有使用任何真实密钥、token 或通知。每日拉取的真实二进制冒烟测试 20 通过、0 失败。13 个后端变异和 6 个前端变异全部被发现;其中一个(旧转写稿上的未知尝试)起初漏掉,随后扩展了测试。make verify 通过,当时前端共 344 个测试。
局限
- YouTube、OpenAI 和 Telegram 的响应由伪造服务复现,没有与真实服务核对;真实模型生成的练习质量未测试。
- 转写稿由我提供,不与视频核对。
- 每日上限按尝试计算,失败或未知的尝试也会用掉当天额度。
- systemd 定时器只做了离线校验,没有安装。
- 听力功能是私有的:公开演示拒绝所有相关路由。
实现由 Claude Code 和 Codex 在我的指导、任务定义和审查下完成。
- 标题: 每日听力课:逐字证据、每天最多准备一课,以及诚实的"未知"
- 作者: Sirui Liu
- 创建于 : 2026-11-09 12:00:00
- 更新于 : 2026-11-09 12:00:00
- 链接: https://vanquisher.world/zh-CN/2026/11/09/flh-listening/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。