# 项目：文档处理任务编排器

原文: https://yishto.com/learn/project-labs/lab-document-workflow-editor/

作者: [Yishto](https://yishto.com/about/)

用独立文档项目连接图校验、节点契约、任务快照和恢复，区分模型提取与有权限的外部操作。

首次发布: 2026-11-10
内容更新: 2026-11-10

文档处理任务编排器让用户连接读取、提取、校验和人工审阅等操作，并看到每一步的结果。第一版可以只处理一类合成文档：读取文本、提取记录、验证字段，最后由人决定是否导出。

可视化画布只是编辑入口。真正需要设计的是图的合法性、节点输入输出、持久执行状态和副作用权限。先建立这些契约，再考虑拖拽、连接线和更多节点。

## 把节点限定为可检查的能力

一个节点定义包含稳定标识、类型、配置、输入字段和输出字段。类型来自程序维护的允许列表，例如读取已有材料、提取指定字段、校验记录和请求人工审阅。文档中的文字不能变成新节点或扩大工具权限。

提取节点可以使用模型，但其输出仍是待校验的数据。校验节点由程序检查字段与业务条件。导出节点产生外部效果，需要明确目的地和执行授权。把这些职责分开，失败时才能知道该修改提示、数据规则还是执行策略。

第一版选择有向无环图（DAG），拒绝自环和循环依赖。循环工作流是另一个设计题，需要终止条件、迭代状态和预算；不要因为画布能画出环，就默认执行器知道何时停止。

## 保存不可变的执行快照

图的编辑版本和任务运行版本应分开。开始运行时冻结图版本、节点配置、输入材料版本和需要的适配器版本。用户之后修改画布，不改变已经执行一半的任务。

任务记录总体状态，节点运行记录保存尝试次数、开始结束时间、输入引用、输出引用与错误类别。大文档或产物放在受控存储中，状态表保存引用与归属。记录恢复所需的数据，日志不默认复制整份文档。

可恢复状态不只是聊天历史。程序必须知道哪些节点已经完成、哪些结果已持久化、哪些副作用尚未确认。成功结果与完成标志尽量协调提交；若它们跨存储系统，要设计暂存、确认和对账，而不是假设存在一个全局事务。

## 先验证图，再安排节点

保存图时检查节点 ID 唯一、依赖存在、节点类型允许、必填配置完整和端口类型兼容。开始任务前再检查权限及资源限制，避免用户编辑后的配置绕过此前的判断。

拓扑顺序保证依赖先执行，但不代表所有节点都应同时启动。可以并行处理独立节点，同时限制活动节点数、输入大小、总运行时间与模型预算。某个依赖失败时，下游应进入阻塞或跳过状态，不能用空对象冒充成功输出。

下面的状态关系可以作为第一版约定：等待节点在依赖成功后进入运行，运行节点可完成、失败或请求人工输入；取消请求影响尚未启动节点，已经在外部运行的任务需继续核实实际结果。状态名字要对应实现行为。

## 用一个小图验证依赖与环路

示例反复找到依赖已完成的节点，按 ID 排序后加入执行顺序。它先拒绝不存在的依赖；若还有待处理节点却找不到可执行节点，则拒绝循环。

```python
def order(graph):
    if any(dep not in graph for deps in graph.values() for dep in deps):
        raise ValueError("unknown dependency")
    done = []
    pending = set(graph)
    while pending:
        ready = sorted(node for node in pending if set(graph[node]) <= set(done))
        if not ready:
            raise ValueError("cycle")
        done.extend(ready)
        pending.difference_update(ready)
    return done
assert order({"parse": [], "review": ["parse"]}) == ["parse", "review"]
try:
    order({"a": ["b"], "b": ["a"]})
    raise AssertionError("cycle accepted")
except ValueError:
    pass
print(order({"parse": [], "review": ["parse"]}))
```

输出是 `['parse', 'review']`，互相依赖的两个节点被拒绝。这是离线顺序检查，没有执行节点、保存任务或调用模型。代码为便于阅读反复扫描待处理集合，没有展示适用于大图的线性拓扑实现；大型执行器应另行评估算法和资源成本。

## 对恢复和重试分别定义条件

恢复任务先读取冻结版本与已确认结果，不能把当前画布直接接到旧任务上。复用已完成的纯计算节点，需要输入、配置和相关版本相同；依赖外部最新数据的节点还需明确新鲜度约定。

重试限制次数和总预算，区分临时错误与永久输入错误。每次尝试保留单独记录，逻辑节点仍保持一个身份。重试提取可以重新计算，但重试导出、通知或写入外部系统可能重复产生效果，应使用逻辑操作键及可核实的外部结果。

超时可能发生在外部操作已经完成之后。此时先查询或对账，不能直接把它写成“没有执行”。人工审阅同样针对冻结输出；输出被重新计算后，不能无条件沿用旧批准。

## 选择编排库时保留职责

LangGraph 提供状态图和持久执行等运行时能力，Dify 的 Workflow 与 Chatflow 提供各自的编排与交互方式。可以使用它们作为适配层，但节点权限、文档归属、业务校验和恢复策略仍由应用明确负责。

第一版先用代码保存一个固定图，跑通读取、校验和人工审阅。随后加入任务状态查询及恢复，再让画布编辑同一种图格式。这样能分别检查编辑器和执行器，避免把拖拽效果误当成执行可靠性。

画布显示节点状态、输出预览和可恢复动作。预览只展示当前用户有权读取的材料，错误消息不含凭据；人工确认应显示实际目标和副作用，而不是一个没有上下文的“继续”按钮。

节点输入契约还应区分必需字段与可选字段。上游输出缺少必需字段时，在调度下游前给出契约错误；不能自动填空字符串后交给模型猜测。节点升级若改变字段含义，应新增版本并提供显式转换，旧任务继续使用原契约。人工补充的数据也经过同一校验，保存操作者与修改范围，避免恢复过程绕过执行入口的限制。

## 练习：在每个边界中断一次

构造未知依赖、循环、字段不兼容、无权限材料和下游失败。图校验错误应在启动前返回；执行错误应留下明确的节点与尝试记录，并使依赖节点停止。

再在提取完成后、完成标志写入前和外部导出响应返回前模拟中断。验收恢复是否使用同一图版本、是否复用正确结果、是否识别可能完成的外部操作。检查取消后尚未开始的节点不会启动，同时明确已经执行的效果如何核实或补偿。

## 资料来源与延伸阅读

- [Workflow and Chatflow](https://docs.dify.ai/en/cloud/use-dify/build/workflow-chatflow)
- [Stateful graph orchestration](https://docs.langchain.com/oss/python/langgraph/overview)
- [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/)
- [Model Context Protocol — Tools](https://modelcontextprotocol.io/specification/latest/server/tools)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [请求太多时：任务队列、缓存与背压](https://yishto.com/learn/service-data/jobs-caches-and-backpressure/)
- [文件、索引和记录需要不同的存储策略](https://yishto.com/learn/service-data/storage-search-and-data-models/)
