Files
115reelay/docs/architecture.md
T
DerrorsandClaude Opus 5.5 3700aff343 feat: 媒体库整理合并选目录与整理,插件按目录组织,清理旧版兼容
媒体库整理
- 「二级分类路由」并入「媒体库整理」:转存前选目录、转存后整理,共用开关与设置
- 目录方式:二级分类 / 按媒体类型 / 不分类;按媒体类型的文件夹名可改
- 二级分类规则随插件发布(category.yaml,MoviePilot 式顺序匹配,增加剧集兜底),管理台只读
- 两种目录方式都以流程图展示,按媒体类型的文件夹名在图上修改

插件结构
- 每个插件一个目录;内置插件的默认设置与校验在插件的 settings.py,内核按目录调用
- 插件动作 ui_actions + 通用接口 /api/extensions/<名>/actions/<动作>,插件不依赖 Web 框架
- 前端插件专用界面移到 web/src/plugins/<名>/,按 config_type 登记
- storage_provider 统一校验,内核不再写死媒体库整理

管理台
- 插件页「运行概览」、统计页内部标签、运行状态徽标、标题与副标题「网盘转存助手」
- 设置页两列卡片不留空、下拉框直接选中默认项、统计页 echarts 按需引入(约 1 MB → 490 KB)

清理
- 移除启动迁移与 .env 导入、旧配置写法与 routing、旧凭证文件、旧去重键与旧中断状态
- 数据库结构迁移重置为单一基线;不认识的顶层配置项直接报错
- 删除未使用的代码与组件;修复测试警告;CI 固定 ubuntu-24.04

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 11:51:17 +08:00

11 KiB
Raw Blame History

架构与可靠性设计

设计原则

  1. AI 提议,事实源校验,人工裁决,裁决结果反馈给 AI:AI 负责理解与提议——读消息、抽字段、给出存不存与存到哪的建议、在规则无法确定时给方案、把一句话偏好起草成规则;TMDB、分享清单与配置校验等事实源和确定性规则负责把关;拿不准的交给你裁决;你的批准、忽略与修正记成样本,回流到识别示例、偏好判断与自动审批建议里。调用 115、写目录、去重始终由确定性代码完成,转存后读回核验。
  2. 稳定内核 + 可插拔外围:主流程是固定骨架,能力都是挂在扩展点上的插件(见 插件开发指南)。
  3. 单一 Context 贯穿全程:一条消息带着 PipelineContext 流经所有阶段,每次状态变更都持久化,可追溯、可恢复、可干预。
  4. 失败隔离:通知和事件先落库再独立投递;转存前置检查和整理失败会阻止任务完成,但不会拖垮其他任务。

一条消息的处理流程

flowchart LR
    subgraph recv[接收]
        direction TB
        tg([Telegram 消息]) --> src[监听 Source] --> rcv[(落库 received)]
    end
    subgraph ident[识别与决策]
        direction TB
        bp[before_parse] --> parse[解析 Parser] --> ap["after_parse<br/>TMDB 补全 · 分享预读"] --> conf[确定性置信度] --> dec{决策 Decision}
    end
    subgraph store[转存与整理]
        direction TB
        bs["before_save<br/>选存放目录"] --> prep[准备清单] --> save[转存 Storage] --> verify[读回核验] --> as["after_save<br/>整理"]
    end
    recv --> ident
    ident -- save --> store
    ident -- ask --> pend[[pending_approval]]
    pend -- 批准 --> store
    pend -- 忽略 --> rejected([rejected])
    ident -- skip --> skipped([skipped])
    store --> done([done])

任务状态机

stateDiagram-v2
    direction LR
    [*] --> received
    received --> parsed
    parsed --> pending_approval: 需要确认
    parsed --> queued: 自动放行
    parsed --> skipped: 不收
    pending_approval --> queued: 批准
    pending_approval --> rejected: 忽略
    queued --> saving
    queued --> skipped: 内容已入库
    saving --> stored: 读回核验通过
    saving --> need_manual: 结果不明
    stored --> organizing
    organizing --> done
    organizing --> need_manual: 整理失败
    need_manual --> queued: 核验转存 / 重新整理 / 自动复查
    received --> failed: 解析失败
    queued --> failed: 准备或转存失败
    failed --> received: 重试
    failed --> queued: 重试
    failed --> stored: 重试
    done --> [*]

进入 failed 的任务从记录的阶段(retry_from)重试:临时错误自动退避重试(not_before,不占并发槽),分享失效、校验不通过这类重试也没用的只能人工重试。

  • 审批态落库:待确认任务持久化为 pending_approval,按钮 callback 只带任务 ID,进程重启后仍可点。
  • 三层去重:消息级(来源 + 消息 ID)、资源级(存储 Provider + 目标 CID + 分享码)、内容级(存储 Provider + 目标 CID + 标题 + 季 + 集)。剧集按集去重,同一分享链接追更新增的集仍会转存。
  • 确定性校验失败(集号无法识别、多季合集中有文件不带季号、同一集有多个文件、电影有多个正片候选)时先让 AI 给方案(ai.assist),方案经同一套规则校验通过才继续;否则在转存前停止,任务标记为不自动重试,修正后人工重试。

AI 与规则的分工

能力 AI 做什么 谁来把关
识别(providers/parser.py) 判类、抽字段与版本信息;参考同一来源最近的人工修正(ai.examples) strict schema + 本地校验;分享码只由代码提取;TMDB 回填规范元数据;置信度由可核对信号计算
偏好判断(decision: llm) 按一句话偏好与近期批准 / 忽略记录决定存不存、存到哪 只能否决或转人工,不能越过过滤规则、置信度与人工确认;选出的目录与手动指定同等对待
订阅放行(decision: subscription) —(订阅本身可由 AI 把一句话对应到 TMDB 条目,你确认后写入) 只认 TMDB 匹配的同一条目同一季;消息与读取分享都不能确认只涉及这一季(如跨多季的合集)时不放行,消息没写季号时把季记为订阅的那一季、转存只取该季;置信度、过滤规则、分享清单与去重照常生效
转人工前兜底(core/assist.py) 规则无法确定转存文件时给出季集映射(含多季合集里不带季号的文件)、多版本取舍、正片选择,以及消息只写了集数时选哪一季 交回同一套选集规则校验:不能与文件名冲突、不能丢掉某集全部文件、不能保留更差的版本、分段电影不能只选一段;不过仍转人工
一句话生成规则(core/compiler.py) 把偏好起草成过滤、分类与版本偏好 按正式配置校验(不过时带错误重写一次),列出改动由你确认后写入;运行时只按规则判断
审批建议(core/inbox.py) 把事实写成一句理由 事实(已入库的集、版本对比、订阅、识别可靠性)与建议动作都由规则决定
新来源接入、来源质量 起草来源规则;读统计与被拒样例给出调整建议 草案按正式配置校验并用样例试跑;建议的屏蔽词在批准过的消息里出现就丢弃;改动由你确认
失败解释、日报、控制 Bot 对话 把原因、统计写成话;理解一句话指令并调用查询工具 建议操作由规则决定;数字由代码统计;写操作只生成待确认计划,点「执行」后才调用服务层

人工裁决(批准、忽略、修正)统一记在 feedback 表(正文去掉链接与访问码),用于识别示例、偏好参考、按来源的通过率与「改为自动转存」建议,以及本地评测(python -m reelay.eval,样本只留在本地 data/eval/)。每次 AI 请求记入 llm_calls(用途、模型、耗时、token 与是否通过校验,保留 90 天),管理台「统计」可查看。所有 AI 调用失败时都按规则降级或转人工,不阻断主流程。

识别

  • 调用 LLM 前先做确定性预过滤(无 115 链接、黑名单、缺 must_have),相同正文命中缓存,瞬时错误退避重试,非法输出重试一次;仍失败则降级 regex 并强制人工确认。
  • 分享码与访问码只由确定性代码从正文和超链接中提取,不采信 LLM。
  • 置信度由标题/类型/年份/TMDB 匹配等可核对信号计算(reelay/core/confidence.py),LLM 自报值仅保存在 raw_fields.parser_confidence。
  • 发布版本信息(字幕、音轨、HDR、片源、编码、打包形式)由 LLM 一并抽取,规则从正文补上 LLM 漏掉的明确标记(reelay/core/release.py)。
  • TMDB 按标题相似度与年份打分,最佳候选不可信时不改标题并转人工。

转存与恢复边界

StorageProvider 分为 prepare(列出分享、确定文件清单、建立独占暂存目录)、save(发送一次转存)、reconcile(只读核验):

  • 分享清单是集数的事实来源:剔除样片/预告/花絮,按文件名识别季集,排除目标目录中已入库的集。规则无法确定时先让 AI 给方案,方案经同一套规则校验后写入清单(每个文件带季集),整理沿用清单里的季集。
  • 文件清单和 receiving 检查点必须在调用 115 写接口之前提交。115 没有可用的请求幂等键,发送前后中断时无法确认是否已执行,所以 receiving 恢复后只核验、绝不重发;空目录、部分落地、内容不符都进入 need_manual,由管理台"核验转存"重新读回;主进程也会在后台自动只读复查(5 分钟起、间隔加倍、最长 1 小时,24 小时后停止),覆盖 115 落地慢的情况,复查期间不发提醒,停止复查时才通知。
  • 落地文件按名称、数量、大小与可用 SHA1 完整核验,分别记录源文件与落地文件 ID。转存回执、去重记录与释放资源占位在同一事务内提交,之后整理可独立恢复。
  • 资源占位(分享与集)在准备阶段获取;集数确定后如需占用的集正被其他任务持有,会释放全部占位再排队,避免互相等待;发送前重新核对其他任务是否已入库同一内容。
  • 整理先持久化计划,再按文件 ID 执行建目录/改名/移动并读回核验;中断后按文件实际位置续跑。同名冲突按 plugins.organizer.conflict 处理,删除动作先记入扩展状态,重放不会重复删除。
  • SQLite 使用 WAL + FULL 同步;数据库结构以 PRAGMA user_version 版本化迁移,版本高于程序时拒绝打开。

通知、事件与告警

  • 通知与事件使用事务 Outbox:与任务状态在同一事务内写入,投递器独立重试(单次 30 秒超时,最多 8 次,支持 Telegram FloodWait),至少一次语义。已处理的审批和旧轮次通知不再推送。
  • 系统通知(故障告警与恢复、审批汇总、日报)同样走 Outbox,进程重启不丢。告警按类别去重:状态由正常变为异常时通知一次,恢复时发送恢复通知。
  • 115 cookie 缺失或失效时暂停转存与整理的领取(解析照常),任务回到原阶段等待,不消耗重试次数,凭证恢复后自动继续。
  • 消息源断开超过阈值告警;重连后按每个来源已接收的最大消息 ID 补拉停机期间的消息。

配置与快照

任务接收时保存审批、过滤、转发、路由、命名和扩展开关的配置快照:配置更新只影响新任务;"重新解析"使用当前策略,"重新整理"使用当前命名与扩展配置。目录别名在尚无转存检查点时解析为当前 CID,有检查点后绑定原 Provider、CID、清单与文件 ID。

进程模型

  • python -m reelay.main:消息源 + Worker + 控制 Bot,单实例(数据库旁的文件锁阻止两个 Worker 消费同一数据库)。
  • python -m reelay.web:管理 API 与前端,只读写任务状态,不启动 Worker。
  • python -m reelay.mcp_server:MCP 的 stdio 接入,与 Web 相同的服务层;Web 进程也在 /mcp 提供同一组工具(Streamable HTTP,凭 MCP 访问令牌)。工具的定义、参数校验与实现在 reelay/agent_tools.py,控制 Bot 的对话助手也从这里取工具,区别只在写操作何时执行:MCP 直接执行(客户端按工具标注确认),对话助手先列出来,用户点「执行」后才执行。

三者共享同一个 SQLite 数据库与配置文件;Web/MCP 修改配置后,Worker 约 1 秒内采用。