想象你是一家侦探事务所的所长。客户只留下一句话:“张三为什么昨天没上班?”你派第一名侦探去查——他跑回来报告:“张三请假了。”这个答案太浅,客户想知道请假背后的原因。于是第二名侦探带着新任务“张三为什么请假”再次出发,查到“他母亲住院了”。第三棒继续追“母亲在哪家医院、现在情况如何”,三段调查拼成一条完整逻辑链,客户终于满意点头。这就是多跳检索(Multi-hop Retrieval)的缩影:每一次检索只是一棒,推理员读完线索后写下新任务卡,把接力棒连同已收集的证据交给下一棒,直到证据链闭合才收工。在 RAG 系统里,“多跳”就是让大模型和检索器反复配合,把逻辑推理一步步走完。
掌握多跳检索,你就能让 RAG 从“答一句”进化到“追一串逻辑”,在金融研报、法务尽调、技术问答等高要求场景里做出能真正追溯因果的 AI 应用。
可视化演示
展示源代码从词法分析生成Token序列,再到语法分析构建抽象语法树(AST)的完整过程,支持切换阶段查看。
本节配有交互动效(进入视口后自动加载,约 3KB)。
词法分析阶段 扫描源代码字符流,识别Token类型(关键字/标识符/字面量/运算符) 每个Token记录类型、值和位置信息 char stream \u2192 scanner \u2192 tokens 词法分析将字符流转换为Token序列
多跳检索概念
侦探所里最吃功夫的不是“跑得快”,而是“知道该往哪儿跑”。单跳检索就像只派一名侦探出去,他查到“张三请假”就回来交差。可是“请假”只是表象,客户要的是请假背后的原因。单跳的局限正在于此:它只能回答一次检索就能命中的浅层问题,一旦答案藏在两三层逻辑之后,单次检索就会断线。
所谓多跳检索,是把“检索”和“推理”交替进行的过程:第一跳用原始查询检索文档,模型读完文档后发现信息不足,就生成一个新查询;第二跳用新查询再检索,再推理……反复迭代,直到拼出完整答案。每一跳都站在上一跳的肩膀上,这正是“接力递进”的含义。你可以用下面的链条来记忆它的运行骨架:
现实中,多步推理的价值不在于跳数炫技,而在于它让 RAG 能处理“答案分散在多个文档、且中间有逻辑依赖”的问题。单跳只能答点,多跳才能连成线。侦探接力队之所以有效,是因为每棒都有明确的新任务,而不是把同一份卷宗反复翻来翻去。
- 初始查询 $q_1$用户抛出的原始问题,比如“张三为什么没上班”。
- 第一次检索 $d_1$检索器根据 $q_1$ 找回第一批文档,相当于侦探拿到的第一份线索。
- 推理出新查询 $q_2$模型读完 $d_1$ 发现线索不够,生成新查询“张三请假的原因是什么”。
- 循环递推 $\cdots$重复“检索→推理”循环,每轮都向答案逼近一步。
- 第 $N$ 跳文档 $d_N$最终到达足以回答问题的证据,交给答案生成器收尾。
查询生成与分解
所长接到的大案子,从来不会一股脑儿抛给一个侦探。他会先把案件拆成几个小任务:“张三的考勤记录在人事部”“请假审批单在行政部”“请假原因可能写在家属联系档案里”。每个侦探只需完成一个小任务,后一个任务往往要等前一个任务的结果。查询生成与分解,做的正是这件事。
多跳检索的第一步,是把用户的复杂问题拆成一组有先后依赖的子问题。比如“公司今年利润下降的原因是什么”,可以拆成“利润怎么计算的”“今年利润相比去年差多少”“造成差异的环节有哪些”。这三个子问题存在天然先后关系:先弄清口径,再找差异,最后归因。模型通过提示词完成拆分,得到子查询的集合:
拆解之后还要“动态改写”:如果检索结果偏离预期,模型要能调整子问题的措辞,让下一跳更精准。这种迭代生成不是一次性写死清单,而是边查边改。就像侦探发现人事部的考勤记录不完整,立刻改写任务单,转去查门禁刷卡记录。查询生成的目标从来不是“拆得多”,而是“拆得准”——每个子问题都要能让检索器找到新信息,否则就是无效的重复劳动。
- 总问题 $Q$用户输入的原始复杂问题,对应案件的全貌。
- 子查询集合 $\{q_1, q_2, \dots, q_n\}$拆解后的一组子问题,按逻辑依赖排序。
- 第 $i$ 个子查询 $q_i$第 i 步要去检索的问题,必须能承接前面的结果。
- 生成规则 $\text{LLM}(Q, d_{i-1})$模型读原始问题 $Q$ 和上一跳文档 $d_{i-1}$ 来生成当前子查询。
提示词设计技巧
接力赛里每位选手起跑前都会拿到一张任务卡,上面写清楚本棒要跑的路线、交接区和注意事项。多跳检索的提示词,就是发给大模型的任务卡。任务卡写得好,模型才知道自己是“检索规划员”而不是普通问答机器人。
一张合格的多跳提示词包含四个要素:系统指令(你是谁、要做什么)、历史证据(前面几棒已经找到的线索)、当前查询(本棒要回答的子问题)、格式约束(输出几行、什么结构)。把这些要素拼起来,就是一个标准模板:
上下文注入是成败关键。如果不把前几跳的证据带进提示词,模型每次都在“失忆”状态下重新出发,接力棒就传不下去。格式约束同样重要:让模型输出“一行一个子问题”或 JSON 结构,程序才能稳定解析。少数场景还可以加一两个示例,引导模型模仿“上一跳提到某公司收购了某团队,接着查被收购团队的财务数据”这类推理节奏。任务卡越清晰,接力越顺畅。
- 第 $k$ 跳提示词 $P_k$发给大模型的完整指令文本。
- 系统指令 $\text{System}$固定角色设定,例如“你是检索规划员,请拆解问题并判断证据是否足够”。
- 历史证据 $E_{1k-1}$:前 $k-1$ 跳检索到的摘要与结论,作为上下文注入。
- 当前查询 $q_k$本跳需要完成的子问题。
- 下一跳查询 $q_{k+1}$模型根据整体上下文拟定的新检索目标。
检索终止条件
侦探办案不能无限查下去。如果每份线索都再引出一个新问题,案件就会永远查不完。所长必须定下结案标准:什么时候算查够了?多跳检索同样需要明确的终止条件,否则要么浪费算力,要么陷入死循环。
最常见的终止条件有三个。第一是置信度阈值:如果本跳检索结果的相关性最高分都低于某个阈值,说明检索器已经找不到有用信息,不如收工。第二是答案可判性:把已有证据交给大模型,让它自评“这些证据够不够回答原问题”,如果模型说够了,就可以停止。第三是资源上限:设定最多跳数或最大耗时,达到上限强制终止,防止预算超支。
结案判断要“综合执法”,不能只看单一信号。得分高不代表逻辑闭环,得分低也不一定立刻停——有时证据链就差最后一块拼图。更稳妥的做法是给模型一个“自评机会”:让它阅读已收集的证据,如果说“无法回答”,就多跑一跳;如果说“可以回答”,就立刻收工。侦探所的铁律是:结案不是查得越多越好,而是证据闭环的那一刻刚刚好。
- 检索得分 $\text{score}(d_k)$第 $k$ 跳返回文档中最高的一跳得分。
- 置信度阈值 $\tau$预先设定的分数下限,例如 0.3,低于它视为证据不可靠。
- 逻辑或 $\vee$两个终止条件满足其一即可停止。
- 最大跳数 $N_{\max}$系统允许的最大检索轮次,防止无限追踪。
- 停止 $\text{stop}$触发终止后进入答案生成阶段。
证据链跨跳传递
接力队最怕什么?交接时掉棒。侦探在第二棒已经查到的“母亲住院”,如果第三棒侦探不知道,就只能重新查一遍。多跳检索的“交接棒”就是证据链——每一跳检索到的中间结果必须被妥善保存、整理、传递,最终交给答案生成器。
构建证据链要处理三件事。第一是中间结果缓存:每跳检索到的文档片段、子查询、置信度都要记录下来,既用于后续跳数的上下文,也用于最终答案溯源。第二是上下文压缩:把所有原文一股脑塞给模型会撑爆上下文窗口,必须对片段做截断或摘要,只留关键信息。第三是链式引用:每条证据标注“来自哪一次检索、原文档是什么”,方便最终答案附上引用。数学上,证据链是一个不断累积的集合:
压缩与引用是证据链的灵魂。如果不压缩,三跳检索的原始文档可能超过十万字,提示词根本装不下;如果不做链式引用,最终答案就成了“无源之水”,用户难以验证。好的证据链就像侦探的案件笔记本:每一页记着线索来源、关键细节和可靠程度,最后一棒不仅能凭它作答,还能把每一环都交代得清清楚楚。
- 累计证据集 $E_k$前 $k$ 跳收集到的全部证据。
- 上一跳证据集 $E_{k-1}$已经处理好的历史证据。
- 并集运算 $\cup$把新证据追加到历史证据中,保留原有信息。
- 单条证据 $e_k$由文档 $d_k$、摘要片段 $\text{snippet}_k$、得分 $\text{score}_k$ 组成的三元组。
深度控制防循环
侦探最怕遇到“鬼打墙”:查着查着发现自己在反复查同一个线索,新查询和上一跳几乎一模一样。常见的场景是——模型生成子查询“张三的病历在哪”,检索不到信息后改写为“张三的病例记录”,再次失败后又改写为“张三的病历档案”,三跳都在原地打转。这就是多跳检索的“死循环”。
防死循环靠三道保险。第一是最大跳数:无论情况如何,超过 $N_{\max}$ 跳就强制停止,这是最硬的兜底。第二是循环检测:把每跳查询向量化,计算与历史查询的相似度,如果新一轮查询和上一轮几乎相同,立即判定进入循环。第三是回退策略:检测到循环后,不直接失败,而是回退到上一跳,尝试用不同的角度重写查询,或者放弃当前子问题转向其他证据。
最大跳数解决“能跑多远”的问题,循环检测解决“是否在绕圈”的问题,两者缺一不可。只设最大跳数,仍然会把算力浪费在重复检索上;只做循环检测,没有硬上限兜底,极端情况下仍然可能跳个没完。合理的深度控制就像给接力赛设置了“最多三棒”的规则,同时在场边安排裁判盯着有没有人绕圈——既保证效率,又保证安全。
- 查询相似度 $\text{sim}(q_k, q_{k-1})$用向量余弦相似度衡量两跳查询的重复程度。
- 循环阈值 $\theta_{\text{loop}}$经验值,例如 0.95,超过它判定为循环。
- 回溯或终止回退到上一跳重新规划查询,或直接停止该分支。
典型应用场景
侦探所最赚钱的业务永远是“复杂案子”。多跳检索在真实世界大显身手,也是因为它能处理那些答案不在一处、需要逻辑串联的问题。
第一个典型场景是因果关系追溯。比如企业想查“这个月退货率为什么翻倍”,单跳检索只能找到“退货率 8%”这个事实,要找到原因得继续查“哪些商品退货最多”“质检报告怎么说”“物流投诉是否增加”“供应链最近有什么变动”。每一跳都在因果链上往前推一步。第二个场景是跨文档事实查找:研报里的结论分散在财务摘要、管理层讨论、行业对比三份文件里,必须逐文档检索再拼接,才能形成完整答案。第三个场景是时间线推理:客服要复盘“用户从第一次咨询到投诉到底经历了什么”,需要按时间顺序检索多轮对话记录,重建事件先后顺序。这类任务的共同点是:单次检索只能拿到碎片,只有多跳串联才能还原全貌。
侦探所的名声,靠的从来不是跑得快,而是能把散落各处的线索连成一条让人信服的逻辑线。多跳检索也一样:当你的 RAG 应用面对的不再是“某年某月某日发生了什么”这样的事实型问题,而是“为什么会这样、中间经历了什么、最后的结论能否自洽”这样的推理型问题,多跳检索就不再是锦上添花,而是必需的引擎。
- 原问题 $Q$用户最初提出的复杂问题。
- 全部证据 $E_1, E_2, \dots, E_N$所有跳次累积的证据链,按检索顺序排列。
- 综合生成 $\text{LLM}(Q, E_1, \dots, E_N)$模型把问题与全部证据一起阅读,生成带逻辑的最终答案。
AI 应用场景
企业知识库因果关系追溯
阶段:查询生成与迭代检索
解决的问题:传统企业知识库只能做关键词匹配,用户问“为什么本月客户流失率上升”,系统返回一堆相关文档却无法串联因果关系。多跳检索把问题自动拆成“流失率数据是多少”“哪些客户流失”“流失前的共同行为是什么”“客服记录里有没有投诉先例”,逐跳检索、逐步归因,最终输出带证据链的因果结论。
具体案例:某电商客服系统接入多跳检索后,面对“为什么支付成功率下降了”的提问,系统第一跳查到支付成功率 92%,第二跳定位到“信用卡渠道失败率骤增”,第三跳追溯原因是某银行网关升级未通知。三段证据附在答案后,运营团队直接定位到根因。
多文档研报观点拼接
阶段:证据链构建与跨文档检索
解决的问题:分析师要回答“对比三份券商研报,它们对新能源车明年的预测有何分歧”,传统方案需要人工阅读三份长文档再手动归纳。多跳检索先定位每份研报的“明年预测”章节,再提取各自的预测数据和假设条件,最后拼接成对比表格答案并标注出处。
具体案例:某投研平台用多跳检索处理上百份研报,用户提问后系统自动完成“定位观点→抽取数据→交叉验证→输出对比结论”的四跳流程,报告生成时间从两小时缩短到三分钟,且每一条结论都带页码引用。
客服对话时间线推理
阶段:多跳检索与终止条件判断
解决的问题:客户投诉升级时,客服需要快速还原“用户从下单到售后抱怨”的完整经过。传统系统只能按时间拉出聊天记录,无法自动筛选关键节点。多跳检索以“问题事件”为线索逐步检索:第一跳找到首次咨询,第二跳找物流状态更新,第三跳找异常缘由,最后判断证据是否足够,生成时间线摘要。
具体案例:某电商客服系统部署多跳检索后,面对“请说明这个订单发生了什么”的请求,系统自动检索下单记录、支付记录、物流轨迹和三次客服会话,输出带时间戳的事件链摘要,客服平均处理时长缩短 40%。
Python 演示
# -*- coding: utf-8 -*-"""多跳检索迷你演示:拆解问题、逐跳检索、累积证据、最终作答"""from types import SimpleNamespace class MultiHopRetriever: def __init__(self, retriever, llm, max_hops=3, score_threshold=0.3): self.retriever = retriever # 检索接口 self.llm = llm # 大模型接口 self.max_hops = max_hops # 最多跳数 self.score_threshold = score_threshold # 得分阈值 self.evidence_list = [] # 累计证据链 def _decompose(self, query): prompt = ( "将问题拆成最多3个子问题,按逻辑依赖排序," "每行一个子问题:\n" + query ) resp = self.llm(prompt) return [line.strip() for line in resp.splitlines() if line.strip()] def _retrieve(self, sub_query): docs = self.retriever.retrieve(sub_query, top_k=2) self.evidence_list.append({"query": sub_query, "docs": docs}) return docs def answer(self, query): sub_queries = self._decompose(query)[:self.max_hops] context_parts = [] for hop, sub_q in enumerate(sub_queries): docs = self._retrieve(sub_q) if not docs or docs[0].score < self.score_threshold: break # 低置信度提前终止 snippet = "; ".join(d.text[:40] for d in docs) context_parts.append("[第%s跳] %s -> %s" % (hop + 1, sub_q, snippet)) check = self.llm("证据足够回答吗?%s\n%s" % (sub_q, snippet)) if "不够" in check: continue # 模型自评证据不足,继续下一跳 final_context = "\n".join(context_parts) return self.llm("综合证据回答:%s\n%s" % (query, final_context)) def fake_retriever(query, top_k=2): return [ SimpleNamespace(score=0.8, text="文档片段:" + query), SimpleNamespace(score=0.5, text="补充记录:" + query), ] def fake_llm(prompt): if "拆分成" in prompt: return ("谁负责公司利润核算?\n" "今年利润相比去年变化多少?\n" "变化的主要成因有哪些?") if "足够" in prompt: return "足够" return "利润下降主要源于原材料成本上升。" if __name__ == "__main__": retriever = SimpleNamespace(retrieve=fake_retriever) multi = MultiHopRetriever(retriever, fake_llm, max_hops=3) result = multi.answer("公司今年利润下降的原因是什么?") print(result) for i, ev in enumerate(multi.evidence_list): print("证据链 %d: %s" % (i + 1, ev["query"]))
sub_queries = self._decompose(query)[:self.max_hops]先拆解子问题,再用max_hops截断,防止超过最大跳数。docs = self._retrieve(sub_q)检索当前子查询,并同步把子查询和结果追加进evidence_list证据链。if not docs or docs[0].score < self.score_threshold:置信度阈值判断:没有文档或最高分太低就提前终止检索。context_parts.append("[第%s跳] %s -> %s" % (hop + 1, sub_q, snippet))把当前跳的关键信息压缩进上下文,供最终回答使用。check = self.llm("证据足够回答吗?%s\n%s" % (sub_q, snippet))让模型自评证据是否充分,模拟“答案可判性”终止条件。return self.llm("综合证据回答:%s\n%s" % (query, final_context))所有跳数完成后,用完整证据链回答原问题。
常见误区
误区多跳检索当然是跳得越多越稳,每多跳一次就能多拿到一份证据,答案肯定会更完整、更准确,把最大跳数设大一点总没错。
正解跳数增加会带来两个副作用:一是错误累积,前面任何一跳检索偏差都会像滚雪球一样影响后续推理;二是上下文噪音变多,压缩摘要可能丢失最重要的信息。多跳的价值是“恰好够用”而非“越多越好”,应该在证据能闭合时立刻终止。就像接力赛不是跑者越多越快,掉棒风险反而随人数上升。
为何常见:初学者把“检索次数”误当成“努力程度”,认为更卖力等于更好,忽略了系统误差和信息衰减的问题。
误区既然要把大问题拆成小问题,那我可以让模型一次性拆出所有子问题,然后用并发检索一把梭,速度还更快,结果应该也一样吧?
正解多跳检索的“跳”意味着后一步依赖前一步的结果。很多子问题是链式依赖的,第二跳的查询必须根据第一跳的文档生成,预先写死的子问题清单无法根据中间证据动态调整。并发执行适合并列型问题,不适合因果链型问题。先同时查完所有分支,再合并答案,本质是“两段式”而不是“多跳式”。
为何常见:把“拆解”看作一个纯静态动作,忽略了多跳的核心价值——“用检索结果改写下一步查询”的动态迭代。
误区死循环无非是跳数失控,只要设一个 `max_hops=5`,到第 5 跳强制停下来,循环问题自然就解决了。
正解最大跳数只是在最坏情况下“踩刹车”,并没有阻止循环本身。在 5 跳的限制内,系统可能已经做了三次完全相同的检索,白白浪费算力和时间。真正的防循环要靠查询相似度检测:每跳生成查询后,与历史查询做对比,发现重复立即终止或回退。最大跳数是安全带,循环检测才是导航仪。
为何常见:把“终止”和“防循环”混为一谈,忽略了循环的本质是“查询内容”的重复而非“跳数”的超限。
误区检索器给每个文档打了分,低于阈值的就终止,高于阈值就继续。一个数字就能决定一切,何必让模型自己判断?
正解检索得分只反映“文档与查询的字面相关度”,不反映“这些证据是否足够回答原问题”。常见情况是每跳得分都很高,但证据始终缺关键一环,模型仍无法作答。反之,得分不高但几份证据互补,可能已经足够。因此要结合“答案可判性”判断:让模型自评能否基于现有证据作答。侦探结案不光看线索新不新,还要看逻辑扣不扣得上。
为何常见:过度信任检索器的相关性分数,把“相关”和“充分”划了等号。
误区每跳查到的文档都是线索,全都要保留,原封不动传给下一跳和最终回答,这样信息最全,答案最稳。
正解上下文窗口是有限资源。三跳每次检索两篇文章,每篇 2000 字,累积就是 12000 字,很快会撑爆窗口。更严重的是,大量无关细节会稀释关键信息,模型注意力被分散。正确做法是截断、摘要、去重,每次只传“本跳新增的关键片段”和“上跳推理出的临时结论”。侦探记笔记只记要点,不会把整本案卷抄一遍。
为何常见:直觉上觉得“信息越全越好”,忽略了注意力模型对冗余信息敏感、以及上下文长度的硬性限制。
面试题
Q1. 多跳检索中“跳”指什么?
- A.一次检索加一轮推理← 正确答案
- B.一次网络请求的数包
- C.模型隐藏层的参数量
- D.检索结果的得分高低
答案:A。多跳检索的“跳”是“检索→推理”的循环单位,每跳完成一次检索并用结果生成下一轮查询。B 是网络概念,C 是模型结构概念,D 是评分指标,都与“跳”的定义无关。
Q2. 单跳检索最适合回答哪类问题?
- A.三体作者的出生年份← 正确答案
- B.年报利润下滑的原因链
- C.三篇论文的观点对比
- D.事故与裁员的因果关联
答案:A。单跳检索一次就能命中的是事实型问题,比如“作者是哪年出生”。B、C、D 都需要跨多个文档或追踪因果链,单次检索只能得到碎片,必须多跳才能答全。
Q3. 查询分解时,子问题之间最常见的关系是什么?
- A.完全独立可并行执行
- B.后一查依赖前一查结果← 正确答案
- C.从同一关键词重复查询
- D.顺序随机由检索器决定
答案:B。多跳检索的子问题通常呈链式依赖,后一跳根据前一跳的证据改写生成。如果完全独立,用一次性并行检索即可,不需要多跳。C 是重复查询,D 不符合设计逻辑。
Q4. 以下哪些属于有效的检索终止条件?
- A.检索结果最高得分低于阈值← 正确答案
- B.每跳结束后随机休眠一秒
- C.模型自评认为证据足以作答← 正确答案
- D.检索结果出现英文内容即终止
答案:AC。A 是“置信度阈值”终止条件,C 是“答案可判性”终止条件,两者都在主流多跳框架中使用。B 只是人为延时,与检索质量无关;D 以语言作为终止依据没有逻辑支撑。
Q5. 证据链传递时,为避免上下文爆炸,最合适的做法是什么?
- A.将每跳全文拼入提示词
- B.中间证据先摘要后拼接← 正确答案
- C.只保留最后一跳原文
- D.全部证据存入外部库
答案:B。每跳全文拼入会迅速撑爆上下文,只保留最后一跳又会丢失前面的线索。正确做法是对中间证据做截断、摘要与去重,再拼接进提示词。D 只是存储方案,并没有解决传递时的上下文膨胀。
Q6. 下列哪些属于多跳检索的适用场景?
- A.单文档事实型问答
- B.跨文档事实拼接← 正确答案
- C.事件时间线还原← 正确答案
- D.因果链条的追溯← 正确答案
答案:BCD。跨文档拼接需要多次检索不同来源,时间线还原需要逐段检索事件记录,因果追溯需要沿因果链逐层深挖,三者都是典型多跳场景。A 是单文档事实题,一次检索即可,不需要多跳。
Q7. 提示词设计中“格式约束”的主要作用是什么?
- A.让子查询结构稳定易解析← 正确答案
- B.让检索响应速度更快
- C.让模型的输出更随机
- D.让文档索引更紧凑
答案:A。格式约束让模型按固定结构(如“每行一个子问题”或 JSON)输出,工程侧才能稳定切分,流水线才不会解析失败。B 与检索器性能无关,C 与随机性相反,D 是索引层的事。
Q8. 当第 k 跳查询与第 k−1 跳查询几乎一致时,最合理的处理是什么?
- A.继续多搜几跳看情况
- B.触发循环检测终止分支← 正确答案
- C.调低阈值重新检索
- D.清空证据链重新开始
答案:B。相邻两跳查询高度相似说明检索在原地打转,继续搜索只会浪费算力,应触发循环检测并终止或回溯。调低阈值可能引入更多噪声,清空证据链属于过度反应。
Q9. 关于多跳检索与普通 RAG 的核心区别,表述正确的是?
- A.依赖更大的外部语料库
- B.用检索结果改写查询词← 正确答案
- C.必须换更大的参数模型
- D.可完全关闭检索模块
答案:B。普通 RAG 是“检索一次→生成答案”,查询词固定不变;多跳检索把检索结果喂回模型,动态改写下一轮查询,这是本质区别。语料库大小和模型参数量都不是判定多跳的关键标准。
Q10. 关于查询分解中“依赖关系”的正确理解有哪些?
- A.后查依赖前查的答案← 正确答案
- B.子查询无关联可并发
- C.顺序由问题逻辑决定← 正确答案
- D.每子查询同词原文
答案:AC。依赖关系意味着后一个子问题需要前一个子问题的检索结果才能正确生成,顺序由问题本身的因果或流程逻辑决定。B 把链式依赖误判为并行关系,D 要求每个子查询重复相同原文,完全错误。