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.yaml 的 chunking 段(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 自己根据输入长度决定用单步还是分块,对外接口不变。"