ESSAY·

Loop Engineering 与 Graph Engineering:循环与图编排

循环管理下一次行动,执行图管理任务之间的依赖。用状态、进展、停止条件和汇合规则判断何时需要增加编排,避免把模型的计划文本直接当作执行状态。

最近修订:2026.09.07

阅读导航 · 第 4 / 6 篇

循环管理下一次行动,执行图管理任务之间的依赖。用状态、进展、停止条件和汇合规则判断何时需要增加编排,避免把模型的计划文本直接当作执行状态。

先统一四个时间尺度

本文把 Step 作为一次模型决策及其关联工具处理,把 Turn 作为响应一次用户输入的工作段,把 Run 作为一次任务执行实例,把 Session 作为跨工作段保留的记录容器。这是教学口径,不是跨框架统一协议。

一个 Run 可能跨多个 Turn:先检索资料,等待用户补充,再创建工单。一次进程启动也可能只处理 Run 的一部分。理解这些关系后,才能讨论“重启是否恢复同一任务”,而不是只看同一个聊天窗口还在不在。

已有的 DeepSeek 状态篇和 Pi 内核篇提供了具体实现。跨框架迁移时,优先映射事件和生命周期,不要仅映射相似的类名。

状态应该描述事实,而不是情绪

“思考中”“正在努力”适合作为反馈文字,却不能驱动恢复。工程状态必须让下一步动作可判定。

状态已知事实允许的下一步
Ready有任务,尚未跨越执行边界准备候选动作
Waiting缺少必需信息、审批或外部结果保存现场,等待有效事件
Dispatching已准备派发,结果尚未确认接收结果或对账
Verifying已获得执行线索查询终态,核对任务契约
Succeeded所有必需验收通过交付证据
Failed / Cancelled / Exhausted失败、取消或预算结束交付原因和剩余事项

真实系统通常需要更细的失败类型;上表只建立主干。尤其是 Dispatching 不能简单转换成“失败后再试”,因为请求可能已经产生副作用。

先解决未确认副作用,再决定下一步。等待与对账由事件或有界调度触发,避免忙轮询。

继续条件应包含可检查的进展

模型提出新动作只是继续的一个信号。运行时还要检查:动作是否在权限内,输入是否齐全,是否仍有预算,是否有未处理的工具结果,以及这次继续能否推进目标。

下面是策略伪代码,不是特定 SDK 的可直接运行接口:

先处理已派发动作的结果或不确定状态
若收到取消:停止新派发,记录仍在途的副作用
若缺少必需信息:进入等待并给出具体问题
若验收通过:结束并附带证据
若达到预算或截止时间:保存现场并交还任务
否则:生成候选动作,经过策略检查后执行

“进展”可以是新证据、状态变化或一个更具体的失败原因。若连续得到同一错误、同一查询结果和同一动作,应触发重复检测或更小的重试上限。这个上限需要按任务校准;不存在适用于所有 Agent 的统一轮数。

等待、预算与取消需要各自的语义

等待不应该持续消耗模型调用。 缺少资料编号就明确问编号,等待审批就保留动作快照。恢复由有效事件触发,并重新检查当前策略,而不是每隔几秒询问模型“现在能做了吗”。

预算必须跨重启保留。 如果每次恢复都重新获得三次尝试,一段有界代码也会成为无限任务。生产预算可以包含费用、调用次数和截止时间;配套实验只实现持久化的派发尝试上限。失败和派发前中断也可能消耗一次尝试,目的是让最坏行为可界定。

取消只阻止尚未发生的动作。 已创建的工单不会因为用户点击取消而消失。如果请求在途,界面应显示“已停止后续动作,正在确认已派发请求”,而不是宣称所有修改已回滚。对已发生副作用的删除或补偿,是另一项需要明确授权的操作。

任务变更不能悄悄复用旧动作

用户把“核验 source-001”改成“核验 source-002”,这不是给旧工单换一个摘要即可。系统需要重新形成任务版本和动作,检查原操作是否已执行,并使旧审批失效。

配套实验选择更简单的规则:同一 Run 的目标、候选动作和预算固定,恢复时不允许偷偷改变。真实产品可以支持版本化修改,但必须记录新旧目标关系、旧操作的处理结果和新的授权依据。

用五个事件验证循环

准备一张状态转移表,至少演练:缺少参数、审批恢复、预算耗尽、派发前取消、派发后取消。每个场景都写清“下一次是否允许模型调用”和“下一次是否允许外部写入”。两者不一定相同。

例如,派发后取消仍可允许可信对账器读取实际工单,但不应让模型继续创建替代工单。这样的划分将产品体验与执行安全接在同一个事实模型上,也能接续已有的任务旅程设计与结果契约。

先选择最小可用的控制方式

方式适用条件需要付出的代价
固定工作流步骤已知,变化主要在输入维护分支与异常路径
固定主干加局部探索大部分步骤稳定,少数资料需要查找约束探索出口与预算
动态规划子任务数量和依赖要运行时发现检验拆分、循环和计划更新

例如“读取资料、准备工单、创建、验收”可以固定;“发现资料引用缺失后寻找原始来源”适合局部探索。不必为了使用 Agent,把原本确定的每一步都交给模型重新决定。

Anthropic 的工作流分类区分了预先定义的并行任务与运行时由协调者拆分的任务。动态拆分适合子任务无法预先列全的场景,但这不意味着所有工作都会因此受益。Building effective agents

把计划节点写成执行契约

一个可执行计划节点至少要表达输入、依赖、完成证据和失败出口。下面是教学结构,不是任何 SDK 的原生 Schema:

{
  "step_id": "verify-release-date",
  "plan_version": 3,
  "depends_on": ["resolve-project-identity"],
  "input_refs": ["source-001@v2"],
  "expected_evidence": ["primary-source-url", "publication-date"],
  "on_missing": "request-more-evidence",
  "write_allowed": false
}

任务状态可以区分待执行、进行中、等待、已验证、已失效与已取消。模型说“核验完成”只能提交候选结果;是否进入“已验证”,由节点的验收契约决定。

计划生成后也要检查基本结构:依赖是否存在、是否有环、每一步能否取得所需输入、工具是否在允许范围内,以及拆分是否仍然服务原目标。一个无法执行的计划应在派发前被发现。

重规划应由事实触发

值得重规划的事件包括:发现项目身份错误、必需数据不存在、工具持续不可用、用户改变目标,或剩余预算无法支撑原路径。

一个查询暂时超时,通常先进入工具层的有界恢复。若每次超时都让模型重写全部计划,既浪费成本,也可能丢掉已经确认的证据。相反,身份变更会影响后续全部资料选择,应使相关节点失效,而不仅仅是补写一句提示。

更新计划时保留旧版本、触发证据和受影响节点。已发生的写入继续按副作用恢复契约处理,不能因为计划不再包含该步骤就当它没有发生。

用同一组任务比较三种策略

从资料核验中抽取简单完整、资料缺失、名称变化和证据冲突四类任务。固定模型、工具、数据快照和总预算,比较固定流程、局部探索与动态规划。

记录独立验收结果、完成时延、重规划次数、失效工作量和人工修正成本。多试次检查稳定性,不把一次幸运探索当成策略优势。若动态规划只在资料缺失切片有收益,就只在该切片启用。

本篇交付物是工作簿中的计划卡:一个依赖图、三个重规划触发条件,以及每种触发对既有证据和写入的处理规则。

总览:反馈的时间顺序与任务的依赖结构

先约定术语。本文把 Loop Engineering 用作“围绕状态、行动、反馈和验收设计运行循环”的工程称呼;把 Graph Engineering 用作“围绕节点、边、共享状态和调度设计 Agent 执行图”的工程称呼。这是便于讨论的工作定义,不是两个已经统一标准化的学科分类。这里讨论的 Graph 是执行图,知识图谱中的实体与关系属于另一种图。

视角核心问题主要设计对象应修复的典型故障
Loop得到反馈后,是否继续,怎样推进?观察、候选动作、进展判据、退出与等待重复搜索、反复改计划、过早完成、永不停止
Graph哪些工作可以开始,结果怎样交接?节点契约、依赖边、分支、汇合、状态更新漏执行前置步骤、分支覆盖、错用旧结果
Harness谁负责执行、保存事实和验证结果?上下文、权限、工具、持久状态、预算、验收恢复后重复写入、授权失效、成功无法查证

一个简单循环可以画成带回边的图;图中某个节点也可以运行一个局部循环。“用了 while”与“用了图框架”只是实现线索,无法直接说明系统有多少自主性或可靠性。

Anthropic 将预定义代码路径的工作流与由模型动态决定过程的 Agent 作了区分。这个区分提示我们:控制路径由谁决定,与路径用代码还是图表示,是两个问题。 固定图可以包含模型路由,普通函数也可以实现确定性工作流。Building effective agents

Loop Engineering:让每次继续都有理由

stateDiagram-v2
%% title: 状态机图
 state "观察当前事实" as Observe
 state "选择下一动作" as Decide
 state "执行动作" as Act
 state "核验结果与进展" as Check
 state "完成" as Done
 state "停止或交接" as Stop
 [*] --> Observe
 Observe --> Decide
 Decide --> Act: 条件满足且有预算
 Decide --> Stop: 缺条件或预算耗尽
 Act --> Check
 Check --> Done: 任务验收通过
 Check --> Observe: 有进展或有效新信息
 Check --> Stop: 无进展或无法恢复

Loop 状态图是本文工程设计:每次继续必须有进展或有效新信息,并受共享预算约束。工具执行成功仍需验收,反复重试本身不算进展。

Loop 解决的是反馈利用,而不仅是重复调用

设定一个教学任务:核验公告 A 的生效日,与知识库记录比较;发现冲突时形成提案,得到允许后创建一张待核验工单。知识库记录为 9 月 1 日,公告修订版写明 9 月 15 日,但 Agent 最先读到的摘要没有修订信息。

合理的循环应该发现证据不够,定位原公告,再把新证据用于比较。失败的循环则可能反复换措辞搜索,获得相同摘要,却把每次调用都当作进展。

循环中的“继续”来自当前事实与运行策略;拒绝、超时与结果未知都应作为结构化反馈回到状态处理。 在这个任务里,一次可检查的进展可以是取得修订版原文、确认某个字段存在冲突,或者定位到明确的访问障碍。模型说“我换个方法再试试”只是行动提议,还不能证明信息发生变化。

可以为每轮记录一个小型进展摘要:已满足的证据要求、剩余缺口、最近动作的标准化标识、结果的内容指纹、累计消耗。重复检测应同时考虑动作与结果;同一个查询在数据更新后可能有价值,不同措辞的查询也可能返回同一份无用材料。

循环出口与探索预算

在上述继续、等待、取消和终态之内,还需要约束局部探索:读到新证据可以继续;重复获得相同资料与缺口时,应切换有依据的策略或报告停滞。读取索引页可能只是准备工作,不要求每步立即提高答案分数,但累计探索必须受预算限制。

下文代码用“连续两轮没有新证据”演示停滞检测,这是测试策略,不是通用阈值。

区分三种不同的“再来一次”

工具重试处理暂时性故障,例如只读查询遭遇限流;Agent 再规划处理证据改变后需要重新选择动作;外层任务恢复处理中断后的未完成责任。三者不应各自无限重试,也不应在恢复时都获得一份全新预算。

在资料核验任务中,搜索服务暂时不可用,优先由明确负责的工具层有界退避。发现资料所属项目错误,才需要重新规划证据来源。创建工单后响应丢失,必须进入副作用恢复与对账,不能当成“搜索失败,换种方式再来”。

如果采用“生成 → 评估 → 修订”的优化循环,还要防止模型迎合自己的评分。每轮都写得更长、更自信,并不代表证据更完整。验收标准应固定在可信任务契约里,必要时通过受保护的程序检查或人工复核。取消、任务版本与预算仍遵循前面的同一组生命周期规则。

Graph Engineering:把交接条件变成执行结构

flowchart LR
%% title: 数据流图
 S["固定任务版本与分支集合"] --> A["来源 A 读取"]
 S --> B1["来源 B 定位"]
 B1 --> B2["来源 B 读取"]
 A -->|A 的结果与身份| J["按分支身份汇合"]
 B2 -->|B 的结果与身份| J
 J --> V["核验证据完整性与冲突"]
 V --> D["生成可审查提案"]

依赖图示意不等长分支。汇合等待当前任务版本下的指定分支终态,再检查结果能否用于交付;两条入边本身不自动表达这种业务条件。

从单份核验扩展到三份资料

现在要求核验 A、B、C 三份公告,分别形成带来源的结果,汇总所有冲突,再创建一张工单。核验彼此独立,可以并行;汇总依赖三份结果;如果本次写入需要审批,审批必须绑定最终提案。

把每份核验都放进一个大循环,会让“当前查谁、谁已完成、能否汇总”等状态不断混入模型上下文。显式执行图可以把这些调度条件交给程序。

汇总必须等到预期分支满足完成条件。定义三条入边不自动构成等待全部分支的屏障,需要核对框架调度语义。 LangGraph 的 Graph API 使用 State、Nodes、Edges 表达工作流,节点可以包含模型调用,也可以只是普通代码。它还通过 reducer 定义状态更新如何合并。这提供了一种具体实现方式;本文的业务契约并不依赖必须使用 LangGraph。Graph API 官方文档

节点边界应对应可验收、可恢复的工作

把节点命名为“研究员”“专家”“总结者”,并没有说明输入和产物。对本任务更有帮助的节点契约是:

节点必需输入产物与完成证据失败出口
核验来源source_id、资料版本、待核验字段字段判断、证据引用、尚未解决的缺口证据不足或来源不可用
汇总提案本次要求的所有来源结果保留冲突与来源的工单提案及摘要哈希缺分支、版本不一致或证据冲突待处理
确认许可操作者、具体动作、当前提案哈希与该动作及版本绑定的有效许可拒绝、撤销或等待
写入与对账有效许可、候选参数、持久操作键权威资源编号与实际状态结果未知、业务拒绝或参数冲突
验收原任务要求与实际资源字段一致性检查和交付引用未通过,进入明确修复流程

不是每个节点都需要一次模型调用,也不是每个节点都值得成为独立 Agent。来源读取与字段校验可能由普通函数完成;需要独立上下文或专业推理时,再考虑委派。多 Agent 的预算和产物责任见协作篇。

拆节点的一个实用问题是:如果这里中断,下次必须重做多少工作?一个节点包办三份检索、汇总、审批和写入,恢复时会很难说明哪些代码可能再执行。拆得过细又会增加状态读写、调度和调试成本。边界应对应清楚的输入输出、故障范围和验收责任。

边要表达条件,汇合要知道在等谁

“A 连到汇总”可能表示 A 完成就触发,也可能是屏障的一部分,必须核对框架语义。动态拆分任务尤其需要在派发前记录预期分支集合,例如 {A, B, C};只统计“收到了三个结果”,会把 A 的两次重放误认为额外完成了一份工作。

汇合节点应逐项检查分支标识、任务版本和结果状态。三份分支都结束,但 C 返回 needs_evidence,只能说明工作已返回,不能说明证据已充分。业务可以允许带缺口的部分报告,也可以要求补齐资料后才能写入,但这个选择必须在任务契约中明确。

分支超时之后,汇合不能无期限等下去;父任务应定义截止时间、部分交付策略与取消传播。用户修改待核验范围时,则需要更新任务版本和预期分支集合,使旧结果失效或重新核验。状态变化必须能被汇合节点识别,不能只追加一条聊天消息。

合并规则往往比画图更重要

让 A、B、C 都返回 result,可能产生覆盖或并发更新错误;把所有结果追加到列表里,又可能在重试后收进重复条目。更稳妥的教学方案是用“任务版本 + 来源编号”作为逻辑键,保存带来源版本的结果。

相同逻辑键、相同结果可视为重放;相同键却出现不同结果,应保留冲突并进入修订决策。不要未经定义就采用“最后到达者胜出”,因为到达顺序通常不代表事实新旧或证据权威性。

如果调度器可能改变结果到达顺序,合并规则就要考虑顺序对结果是否有影响;如果允许重放,还要考虑重复更新是否改变状态。累加 Token 消耗与合并事实是两种不同口径:前者可能应计入真实发生的每次调用,后者不应把同一事实复制三遍。一个通用的列表追加函数不能替代这些业务判断。

执行图可以有环,任务依赖要说明环的用途

严格的任务依赖 DAG 不允许环,因为“先完成 A 才能开始 B,先完成 B 才能开始 A”无法启动。Agent 的执行图却可以有回边,例如证据不足时返回检索。这里的环表示迭代控制,并不一定是依赖定义错误。

有环也意味着图本身不会证明程序能结束。每条回边都需要退出条件、资源上限和停滞处理。框架的递归或步数限制通常只是调度层保护;例如 LangGraph 文档中的 recursion limit 计量 super-step,不能直接充当整项任务跨恢复的费用预算。Graph API:运行步数限制

组合与演变:固定主干,保留有边界的探索

对于这个案例,一个自然的组合是:外层 Graph 管理三个来源的依赖、汇合和最终写入;每个核验节点内部使用有界 Loop,决定下一步读取哪份原文、是否需要补充搜索,以及何时返回证据缺口。

这是一种按问题增长的建设顺序,而非整个行业必须经历的替代史:

任务变化可以增加的机制新增责任
材料齐全,步骤固定普通函数或固定流程输入与结果校验
需要根据反馈寻找缺失证据有界 Loop进展、停止与预算
多份任务有分支和依赖显式 Graph节点契约、汇合与合并
主干稳定,局部路径不确定Graph 包含局部 Loop局部退出、全局预算和结果交接
任务跨会话、审批或故障恢复持久化 Harness重放、授权复检与副作用对账

Graph 和 Loop 都不是 Harness 的替代物。前者描述结构,后者描述迭代行为,Harness 把它们接到上下文、执行许可、工具与事实存储。Prompt 与 Context 也继续发挥作用:局部核验需要清楚的判断规则和正确材料,外层汇总需要分支结果及其来源,不必复制所有子循环的聊天历史。

恢复边界落在节点之外

检查点只能恢复运行状态,业务副作用仍要对账。LangGraph 的中断节点重执行规则及可运行例子集中在LangGraph 运行时研究。本篇的组合约束是:审批先于写入,局部 Loop 的重复执行不能产生新业务操作键,旧图任务不能默认交给不兼容的新图。三种控制引擎如何承担这项责任,见架构选型。

实践:先验证循环与汇合的两个不变量

以下 Python 3 示例不调用模型、不连接外部服务,只检验两个局部规则:在本教学策略下,连续两轮没有新证据就返回停滞;汇合以分支身份判断完整性,拒绝旧任务版本和冲突重放。它不是完整运行器,也没有实现并发调度、持久恢复和工单写入。

def collect_evidence(observations, budget=4, stall_limit=2):
    seen, stalls, used = set(), 0, 0
    for batch in observations:
        if used >= budget:
            return "exhausted", seen, used
        used += 1
        new = set(batch) - seen
        seen.update(new)
        stalls = 0 if new else stalls + 1
        if stalls >= stall_limit:
            return "stalled", seen, used
    return "observations_ended", seen, used

def join_results(expected, updates, task_version):
    merged = {}
    for update in updates:
        source = update["source"]
        if update["task_version"] != task_version or source not in expected:
            raise ValueError("stale or unexpected branch")
        if source in merged and merged[source] != update:
            raise ValueError("conflicting replay")
        merged[source] = dict(update)
    ready = set(merged) == expected and all(
        item["status"] == "verified" for item in merged.values()
    )
    return ready, merged

a = {"source": "A", "task_version": 1, "status": "verified"}
b = {"source": "B", "task_version": 1, "status": "verified"}
assert collect_evidence([["v2"], ["v2"], ["v2"]])[0] == "stalled"
assert collect_evidence([["v1"], ["v2"]], budget=1)[0] == "exhausted"
assert not join_results({"A", "B"}, [a, a], 1)[0]
assert join_results({"A", "B"}, [a, b, a], 1)[0]
assert join_results({"A", "B"}, [a, b], 1) == join_results({"A", "B"}, [b, a], 1)
assert not join_results({"A", "B"}, [a, {**b, "status": "needs_evidence"}], 1)[0]

for updates in ([{**a, "task_version": 0}], [a, {**a, "status": "needs_evidence"}]):
    try:
        join_results({"A", "B"}, updates, 1)
    except ValueError:
        pass
    else:
        raise AssertionError("invalid result was accepted")
print("PASS: loop bounds and branch join contracts")

这里的 verified 是测试输入,不是可以盲信的模型自评。真实系统需要先校验证据与节点产物,再形成相应状态。observations_ended 也只代表演示输入用完,不能作为任务成功信号。

这段代码可以验证重复结果、缺失分支和版本错误等具体反例。要比较 Loop 与 Graph 在真实任务中的效果,还需接入同一组模型、工具和材料;代码断言不能证明图编排提高了模型正确率。

接入真实运行系统后,再演练六种故障

故障注入预期处理必须检查的事实
检索连续返回相同摘要局部 Loop 有界退出或切换有依据的策略调用消耗与未解决缺口
A 的结果重复送达,B 尚未返回汇合继续等待 B按身份记录的预期与已收分支
用户修改任务后,旧 C 结果迟到不混入新版本提案任务版本与失效记录
一个分支因权限不足结束按契约等待或部分交付未取得的证据与实际授权边界
工单写入成功,但检查点未保存恢复后用原键对账实际工单数量与字段
审批等待期间提案发生变化旧许可不能直接授权新提案许可所绑定的动作及哈希

预算耗尽与取消也应向局部 Loop 和在途分支传播;最终写入与验收要预留资源。父任务不能让每个分支都自行读取同一个余额后各自花完。具体执行实验可以接续 Mini Harness 实战,其中单动作恢复机制是基础,本篇的并行图与局部探索需要另外实现。

评测要分别观察局部推进与全局交付

固定模型、工具和数据快照,比较单 Loop、固定 Graph、Graph 加局部 Loop 三种配置。任务样本覆盖材料完整、证据缺失、分支超时和写入结果未知;总预算包含重试、汇总与验收,不只计算首次模型调用。

Loop 层记录无进展调用比例、退出原因和取证成功率;Graph 层记录缺分支汇总、重复合并、旧版本混用和关键路径耗时;整项任务记录实际资源验收通过率、重复写入、总成本和人工介入。并行可能缩短等待时间,同时增加调用费用,两项指标都应保留。

先用固定工具桩验证调度与恢复,再用多个模型试次检验任务质量。没有实验数据时,可以解释机制为何可能有用,但不应编造“图比循环快多少”或“成功率提高多少”。评测设计可参考可重复的回归系统。

回到设计:让不确定性在有边界的位置发生

材料已经齐全时,固定流程足够;需要发现缺失证据时,为局部工作增加反馈循环;出现分支、依赖与恢复交接时,把它们写成执行图。无论选择哪种表示,仍要回答:什么算进展、何时允许下一步、结果由谁验收、中断之后哪些事实还能成立。

对本文的资料核验任务,最终设计可以浓缩为三份产物:一张局部循环退出表,一张标明预期分支和汇合条件的执行图,一份写入与恢复契约。Loop 让 Agent 根据反馈继续推进,Graph 让多步工作按条件交接,Harness 让整个过程对真实结果负责。

参考资料

资料核对日期:2026-09-05。术语划分、案例结构、故障表及示例代码为本文的工程归纳;框架行为以对应文档和实际安装版本为准。