跳转至

ADR-0005 · 长输入用分块流水线,且由 Skill 自行决定

  • 状态:accepted
  • 日期:2026-08-01

背景(Context)

用户直接提出了这个问题:

"如果我的语音发给你的很长,或者 coffee chat 里面发给你的很长的话,还是建议使用一步编排调用吗?还是多步编排呢?"

这是个真问题。典型输入长度差异极大:

输入 量级
"字节量化岗,8月底截止,想投" 几十字
一份 LinkedIn JD 全文 2–5 千字
一场 40 分钟 coffee chat 录音转写 1–2 万字
一篇经验帖长文 几千至上万字

如果统一用"一次调用返回一个巨型 JSON":

  • 长输入会撞上下文上限
  • 输出 JSON 越大,格式出错概率越高,而一处语法错整个结果全废
  • 失败只能整体重来,白烧一次长上下文的钱
  • 模型在超长上下文里会漏掉中段信息(lost in the middle)

反过来,如果统一用多步来回编排,短输入(占绝大多数)会平白多几次往返,又慢又贵。

决策(Decision)

两条策略并存,由 Skill 内部按输入长度自行选择,对外接口保持不变。

输入 → 归一化(STT/视觉/正文抽取) → len(text) 与 chunking.max_chars 比较
   ├─ 不超限 → 单步:一次调用返回完整结构
   └─ 超限   → 分块流水线:
                切段(fixed 定长+重叠 | topic 先让 AI 划主题) 
                → 逐段调用(同一 output_schema)
                → 按 dedupe key 合并去重(冲突取 confidence 高者)
                → 可选一次 reduce 调用生成全局摘要

策略写在 skill.yamlchunking 段(strategy / max_chars / overlap_chars / reduce),脚本式 skill 可用 ctx.chunk() 自行控制。

每个 chunk 一条 ingest_run 记录,因此可以 POST /v1/ingest/{id}/retry?chunk_index=3 只重试失败的那一段

理由与代价(Consequences)

得到

  • 短输入保持最快最省(一次调用)
  • 长输入不受上下文限制,且局部失败局部重试
  • 用户和前端完全无感:始终只是 POST /v1/ingest
  • 每段独立校验,一段格式错不影响其余段

代价

  • 合并去重逻辑需要认真写(同一个人在多段被提到、同一家公司多次出现)
  • 分块可能切断跨段语义(用重叠窗口 + 可选 reduce 调用缓解)
  • 长输入总 token 略高于理想的单次调用(重叠部分重复计费)

备选方案(Alternatives)

方案 为何不选
一律单步 长 coffee chat 直接爆上下文;巨型 JSON 一处错全崩
一律多步 Agent 循环 短输入(绝大多数)多付几倍延迟与成本;行为不可预测,难测试
在路由层按长度分派到不同 skill 同一件事拆成两个技能,用户要维护两份 prompt,违反"技能=一件事"
靠模型自带的长上下文(128k+)硬吃 依赖特定供应商;lost-in-the-middle 导致中段信息丢失;单次失败成本极高

备注

这条决策明确了用户提出的原则:"skill 自己根据输入长度决定用单步还是分块,对外接口不变。"