Skip to content

P2: 视频号解析侧单端点链无瞬态重试,TikHub 瞬时空结果直接终态失败 #34

Closed

Description

@zj1123581321

现象

2026-09-23 12:23:08,生产(fordeal media-resolver-api)解析微信视频号分享链接失败,一次瞬时的上游空结果直接把整个解析打成终态失败:

2026-09-23 12:23:08 | ERROR | app.services.video_resolver:resolve:318 | All providers failed to resolve video
2026-09-23 12:23:08 | ERROR | app.api.resolve:resolve_url:312 | Video resolution failed: All providers failed for platform 'wechat_channels', video_id ''. Errors: [tikhub: WechatChannels all endpoints failed for 'https://weixin.qq.com/sph/Aqf4lZmY93' [attempts=[{'endpoint': 'fetch_video_detail', 'decision': 'retryable'}]]]

调用方(VideoTranscriptAPI)把该错误归为终态「解析失败,稍后再试」,用户侧任务失败并收到告警通知。

复现与归因

  • 链接本身有效:2026-09-23 15:59 在生产容器内用同一 share_url 直调 POST /api/v1/wechat_channels/v2/fetch_video_detail(raw=false):code=200、data 节点齐全、object_type=0,_classify_wechat_channels 判 ok。即 12:23 的失败是上游(TikHub / 微信侧)瞬时空结果,不是链路或入参问题。
  • 孤发性:fordeal logs/error_*.log 统计,2026-09-02~09-22 视频号相关错误 0 次,09-23 仅此 1 次。低频瞬态,但一发生就是用户可见的任务失败。
  • 代码不对称:下载侧 TikHubProvider.fetch_wechat_channels_media(app/services/providers/tikhub.py)对同一 VideoNotFoundError 已有 3 次有限重试;而解析侧 resolve → _fetch_wechat_channels → _run_chain 是单端点单次调用,decision=retryable 直接 VideoNotFoundError 收场。视频号是 TikHub 单源、无 Cobalt 兜底,瞬态没有第二次机会。

关键代码位置:

  • app/services/providers/tikhub.py _run_chain:classify 判 retryable → 记 attempts → 链上没有下一端点 → 抛 VideoNotFoundError,全程无重试。
  • app/services/providers/tikhub.py _fetch_wechat_channels / WECHAT_CHANNELS_CHAIN:单端点链。
  • app/services/providers/tikhub.py fetch_wechat_channels_media:同错误已有 attempts = 3 的重试样板。

建议

解析侧对 retryable 判定做与下载侧对齐的有限重试(3 次 + 短退避),重试仍按 _run_chain 的 attempts 累计记录;耗尽后仍抛终态错误,不引入无限重试。可直接复用 fetch_wechat_channels_media 的重试样板,保持两侧行为一致。

验收条件

  1. 视频号解析路径对 decision=retryable 的响应做有限重试(3 次 + 退避),耗尽后仍以 VideoNotFoundError 终态收场;
  2. attempts 记录体现每次重试(能看出「重试了几次、每次判定」);
  3. 测试锁定:mock TikHub 返回瞬时空 data(前 2 次空、第 3 次正常)断言最终成功;全程空则断言终态失败且 attempts 含全部尝试次数;
  4. 不改变终态/非视频类错误的现有分类(_classify_wechat_channels 两态语义不变,除非与 P3: retryable 分类吞掉上游失败原因(空 data / object_type!=0 / 错误 body 三态不可区分) #33 一并处理)。

Activity

  1. zj1123581321 commented on Sep 23, 2026

    @zj1123581321
    CollaboratorAuthor

    已修复并合入 master(PR #38,合并提交 ffea47a)

    验收条件对照

    # 本 issue 的验收条件 结果
    1 解析路径对 decision=retryable 做有限重试(3 次 + 退避),耗尽后仍以 VideoNotFoundError 终态收场 已实现:重试轴进通用引擎 _run_chain(max_attempts_per_endpoint=3, retry_backoff=0.3)(其余 8 平台默认 1 = 现状不变)。耗尽仍抛 VideoNotFoundError,且不会因总预算被截断成 ProviderError(timed out):重试前要求「剩余预算 > 单端超时 + 退避」,装不下的下一次尝试根本不启动
    2 attempts 体现每次重试(几次、每次判定) 已实现:每次尝试一条记录,含 endpoint / decision / attempt 与归因字段
    3 测试锁定:前 2 次空、第 3 次正常 → 成功;全程空 → 终态失败且 attempts 含全部尝试 test_chain_transient_then_hit_records_three_attempts、test_chain_exhausted_raises_not_found_with_attempts、test_chain_budget_does_not_start_attempt_that_does_not_fit
    4 不改变终态/非视频类错误的现有分类 8 平台测试文件零改动、全绿;_call_endpoint 内部契约变更只改写抖音/小红书各一条用例(链上行为等价)

    附带修掉的同源问题

    下载侧 fetch_wechat_channels_media 里手搓的同形 3 次循环已删除,解析侧与下载侧从此共用同一实现——不下移会叠加成 3×3=9 次上游请求。

    实测

    • 合入后的 master 全量:uv run --extra dev python -m pytest tests/ -q → 439 passed。
    • 主脑独立复现(stub 上游):慢但可重试 → VideoNotFoundError 且只调 1 次(预算装不下不再启动);重试后挂起 → ProviderError 含 timed out;预算装不下下一次 → VideoNotFoundError 调 1 次;重试耗尽 → 3 次尝试 + VideoNotFoundError。

    边界说明

    归因(attempts 的 reason 字段)采用有界承诺:域内(fixtures 记录的形态 + 文档化的 4xx/5xx JSON 错误包)保证三类原因可辨;域外畸形形态只承诺「不泄漏凭据 / 不崩溃 / 分类只出 ok|retryable」,不承诺归因完备(域外形态待真实样本再增补,见 #39)。这条边界是本次 5 轮 gate 循环的直接结论,写进了 PR #38 描述。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions