#AI#AI Agent

Prompt 链与多步工作流编排

复杂任务一次 LLM 调用搞不定,要拆成多步链——但链怎么拆、中间结果怎么传、错误怎么恢复,是工程问题。本文讲清 prompt chaining 的几种模式(顺序/并行/路由/循环)、何时该链何时该用 Agent、中间状态管理,以及和单次调用的成本/质量权衡。

本文要点

  • 主题聚焦:AI、AI Agent
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2025年12月3日·更新于 2026年7月23日

"帮我研究 X,写个大纲,再写一篇 3000 字文章,最后润色。"——把这种任务塞进一次 LLM 调用,你大概率得到一份样样都平庸的东西:研究浅、大纲乱、正文散、润色等于没润色。复杂任务不是"prompt 写得够长"就能解决,而是要拆成多步链,让每一步只做一件事、只戴一顶帽子。

一、为什么单次调用不够用

单次调用的瓶颈有三个,且都不是"加 token"能解决的:

  1. 任务复杂度溢出。 一个 prompt 同时要求"检索+推理+写作+自检",每多一个目标,模型在每个目标上的注意力都被稀释。实测里,"一次写完"的输出几乎总是每项都 70 分。
  2. 上下文污染。 把研究材料、大纲、正文、修改意见全堆进一个上下文,模型容易抄前面的废话、忘后面的约束。长上下文 ≠ 长记忆,中间的信息会被"淹没"。
  3. 多角色冲突。 研究员要发散、编辑要收敛、校对要挑刺——这些指令互相打架,塞一个 prompt 里模型只能和稀泥。

拆链的本质是:把一个高方差任务,切成几个低方差子任务,每个子任务单独可测、单独可调、单独可换模型。

二、链的四种基本模式

所有 prompt chain 都是这四种模式的组合。

text
顺序 Sequential   A → B → C → D
并行 Fan-out      A → [B,C,D 并行] → merge
路由 Router       A → (条件) → B | C | D
循环 Loop         A → B → (满意?) ─ 否 ─→ B ...
                         └ 是 ─→ 输出

顺序链:最常见。上一步输出喂下一步,如"翻译→校对→排版"。适合步骤固定、前后依赖明确的流程。

并行链(fan-out):同一步派生多个独立子任务并行跑,再汇总。如"对 10 个章节各自生成摘要,再合并成总纲"。延迟低、吞吐高,但汇总会丢失细节,适合可合并的任务。

路由链:根据输入走不同分支。如"先分类:技术问题走专家 prompt,闲聊走轻量 prompt,违规走拒绝"。能省 token(简单任务不上大模型)、提质量(每分支专用 prompt)。

循环链:反复迭代直到满足退出条件。如"生成→自评→不够好→带着批评重新生成"。质量上限最高,但延迟和成本不可控,必须设硬上限(最多 N 轮)。

一个最小骨架:

ts
type Step<I, O> = (input: I) => Promise<O>;

// 顺序
const chain = async (topic: string) => {
  const research = await researchStep(topic);
  const outline  = await outlineStep(research);
  const draft    = await draftStep(outline);
  return polishStep(draft);
};

// 并行 fan-out + merge
const summarizeMany = async (chunks: string[]) => {
  const parts = await Promise.all(chunks.map(summarizeStep));
  return mergeStep(parts);
};

// 路由
const route = async (q: string) => {
  const kind = await classifyStep(q);          // 'tech' | 'chat' | 'block'
  return kind === 'tech'  ? expertStep(q)
       : kind === 'chat'  ? lightStep(q)
                          : refuseStep();
};

// 循环(带硬上限)
const refine = async (topic: string) => {
  let draft = await draftStep(topic);
  for (let i = 0; i < 3; i++) {
    const critique = await critiqueStep(draft);
    if (critique.score >= 8) break;
    draft = await reviseStep(draft, critique);
  }
  return draft;
};

三、链 vs Agent:别把链包装成 Agent

链和 Agent 经常被混为一谈,但它们的工程取舍完全相反:

  • 链是确定流程:每一步是什么、顺序如何、传什么,你在写代码时就定死了。模型只负责"把这一步做好",不负责"决定下一步干嘛"。可预测、可重放、好调试。
  • Agent 是自主决策:模型自己看情况决定调哪个工具、要不要再查一轮、什么时候收手。灵活,但不可预测、难调试、贵。

经验法则:流程越确定,越该用链;越开放探索,越该用 Agent。 "研究→大纲→起草→润色"是确定的,用链;"帮我查清楚这个 bug 的根因,需要查代码就查、需要跑用例就跑"是开放的,用 Agent。

很多人一上来就上 Agent,结果模型每轮都在"决定下一步干嘛"上花 token、还会绕远路。先用链把确定性流程钉死,只在真正需要决策的岔路口上引入 Agent,是性价比最高的设计。一个折中:链做骨架,某个节点内部是 Agent(如"研究"这一步让 Agent 自主检索),其余节点保持简单确定性。

四、中间状态:结构化传递,别串接自然语言

链最容易翻车的地方,是上一步输出怎么交给下一步。新手做法是"把 A 的自然语言输出直接拼进 B 的 prompt"——这等于在步骤之间加了一个高噪声通道。

ts
// ❌ 反例:自然语言串接,噪声大、不可解析
const outline = await llm(`基于这些研究写大纲:${researchText}`);
const draft   = await llm(`基于这个大纲写正文:${outline}`);

// ✅ 正例:结构化 schema 传递,B 只取需要的字段
type Research = { keyFacts: string[]; sources: {title:string;url:string}[]; gaps: string[] };
type Outline  = { sections: { heading: string; points: string[] }[] };

const research: Research = await researchStep(topic);     // 强制 JSON schema
const outline:  Outline  = await outlineStep(research);   // 只读 research.keyFacts
const draft              = await draftStep(outline);

结构化传递有三个直接好处:B 步 prompt 可以只裁剪需要的字段(别全量传)、中间结果可落库可重放(某步炸了不用从头跑)、每步可独立评测。

上下文裁剪是被低估的优化。不要把研究阶段产出的 5KB 原文一股脑塞进写作阶段——写作只需要 keyFacts,校对只需要 draft。每步只给"这一步真正需要的最小上下文",质量反而更高(模型不分心),token 也更省。

五、错误恢复:每一步都可能炸

链一旦有 N 步,整体失败概率是 1 - (1-p)^N——单步 2% 失败率,十步链就有 18% 的概率某步出问题。不做错误恢复的链上不了生产。

每一步要明确失败策略,四选一(或组合):

  • 重试:网络抖动、JSON 解析失败这类瞬时错误,指数退避重试 2–3 次。注意重试要换温度或加"上次错在哪"的反馈,否则同样的输入同样的错。
  • 降级:大模型挂了走小模型、JSON 解析失败走正则兜底、主链路挂了走缓存结果。降级要可接受质量下滑。
  • 跳过:非关键步骤(如"配图建议")失败就跳过,主流程继续。
  • 人工兜底:高价值/不可逆步骤(如最终发布)失败,进人工队列。
ts
const withRetry = async <T>(fn: () => Promise<T>, times = 3): Promise<T> => {
  let lastErr: unknown;
  for (let i = 0; i < times; i++) {
    try { return await fn(); }
    catch (e) { lastErr = e; await sleep(2 ** i * 500); }
  }
  throw lastErr;
};

// 幂等:中间结果落库,按 (taskId, stepName, inputHash) 去重
const runStep = async (taskId: string, step: string, input: unknown) => {
  const key = `${taskId}:${step}:${hash(input)}`;
  const cached = await db.get(key);
  if (cached) return cached;              // 重放时跳过已完成步
  const out = await withRetry(() => steps[step](input));
  await db.set(key, out);
  return out;
};

幂等是链的生命线。把每步的输入算 hash、输出落库,任何一步炸了重跑都能从断点续上,而不是从头烧 token。这比任何重试逻辑都值钱。

六、成本与延迟:链的隐形账单

链不是免费的。N 步链的延迟是各步之和(顺序时)、token 是各步之和,很容易比单次调用贵 3–5 倍。可控优化手段:

  1. 能并行就并行。 顺序链里但凡有"互相独立"的子任务,果断 fan-out,用 Promise.all。延迟从"串行之和"变"最慢一个"。
  2. 缓存中间结果。 同一输入的"研究""分类"结果可缓存复用,尤其路由链的分类步,命中率极高。
  3. 简单步用小模型。 路由分类、JSON 抽取、格式校验这类活,Haiku 级模型够用;只在推理/写作这种重活上用 Opus/Sonnet。混搭下来成本能砍一半以上。
  4. prompt caching。 链里每步的 system prompt 通常稳定,开 prompt caching 把前缀缓存住,重复调用省一大笔。

一句话:链的账要算总账——质量提升带来的收益(返工少、人工干预少)能不能盖过 token 和延迟的增加,盖得过就上链,盖不过就老老实实单次调用。

七、实战:一条"研究→大纲→起草→润色"内容生成链

把前面的东西捏到一起。这是我自己在用的内容生成链骨架:

ts
type Ctx = { topic: string; research?: Research; outline?: Outline;
             draft?: string; article?: string };

const generateArticle = async (topic: string): Promise<string> => {
  let ctx: Ctx = { topic };

  // 1. 研究:Agent 子步,自主检索(开放探索 → 用 Agent)
  ctx.research = await runStep(topic, 'research', topic);

  // 2. 大纲:确定性步,裁剪上下文,只传 keyFacts
  ctx.outline  = await runStep(topic, 'outline', ctx.research.keyFacts);

  // 3. 起草:循环链,自评+修订,最多 2 轮
  ctx.draft    = await runStep(topic, 'draft',
    { outline: ctx.outline, facts: ctx.research.keyFacts, maxRounds: 2 });

  // 4. 润色:小模型也能做,换便宜模型 + prompt caching
  ctx.article  = await runStep(topic, 'polish', ctx.draft);
  return ctx.article;
};

设计取舍一目了然:开放的研究步用 Agent、确定的写作步用链;每步结构化进出、落库可重放;循环带硬上限;润色降级到小模型。这条链跑出来的文章,质量稳定碾压"一次写完"的版本,因为每一步都只在一个明确目标上用力。

小结

  • 拆链的判据:任务有多个互相干扰的目标、上下文会互相污染、需要不同角色——满足任一就该考虑链。
  • 四种模式:顺序、并行、路由、循环,记住这四块积木,99% 的场景拼得出来。
  • 链 vs Agent:确定流程用链,开放探索用 Agent;先用链钉死骨架,只在岔路口上 Agent。
  • 中间状态:结构化 schema 传递,每步只给最小上下文,中间结果落库可重放。
  • 错误恢复靠幂等:(taskId, step, inputHash) 去重 + 重试/降级/跳过/人工,比任何重试逻辑都值钱。
  • 算总账:质量收益要盖过 token 和延迟成本;并行、缓存、小模型混搭是三件套。

金句:单次调用是许愿,链是分工,Agent 是授权。 把复杂任务从"一次赌对"变成"步步可控",你才真的拥有它,而不是被它的输出牵着走。

Related

相关推荐