Caloogy 文章
English

用数学拓扑结构编排 AI 工作流

日期
2026 年 10 月 8 日
系列
量化 Agent
阅读
5 分钟
主题
Ai agentQuantWorkflowDag

一句话版本

一个量化投研流程里,多个 AI Agent 各管一段:取数、清洗、算因子、因果检验、回测、风控审查、写报告。谁先谁后、谁可以并行、哪里会卡住、哪里会出现"大家各说各的",都是结构问题。 数学提供了三层越来越抽象的工具:

  1. 有向无环图与拓扑排序:决定执行顺序和并行度,找出关键路径,检测死锁(成熟、可直接用);
  2. 持续同调(拓扑数据分析):分析多个 Agent 输出之间的"形状",发现共识群、离群者和结构性分歧(已有研究基础,用于 Agent 编排属于构想);
  3. 范畴论:把工作流看作可以组合的"态射",保证组合后的流程仍然类型正确(理论构想)。

免责与定位

本文仅为技术科普,不构成任何投资建议。第 1 层是成熟工程实践,第 2、3 层中用于 Agent 编排的部分属于构想,文中会标注。


第一层:有向无环图(成熟)

1.1 工作流就是一张依赖图

取数

清洗

因子A

因子B

因果检验

回测

风控审查

报告

数学上,这是一个有向无环图(DAG):节点是任务,箭头是"必须先完成"。"无环"保证任务能按某种顺序全部完成。

1.2 拓扑排序:谁先谁后、谁能并行

拓扑排序给出一个满足所有依赖的执行顺序。把它按"层"展开,同一层的任务互不依赖,可以交给不同的 Agent 并行执行。

关键路径是从开始到结束耗时最长的那条依赖链,它决定了整个流程的最短完成时间。要加速整个流程,只有缩短关键路径上的任务才有用。

环意味着死锁:Agent A 在等 B 的结果,B 又在等 A 的结果。

1.3 代码

Python 标准库的 graphlib 就能完成这些:

from graphlib import TopologicalSorter, CycleError

workflow = {   # 键 = 任务,值 = 它依赖的任务
    "取数":       set(),
    "清洗":       {"取数"},
    "因子A":      {"清洗"},
    "因子B":      {"清洗"},
    "因果检验":   {"因子A", "因子B"},
    "回测":       {"因子A", "因子B"},
    "风控审查":   {"回测", "因果检验"},
    "报告":       {"风控审查"},
}
cost = {"取数": 3, "清洗": 2, "因子A": 5, "因子B": 8, "因果检验": 4, "回测": 6, "风控审查": 2, "报告": 1}

ts = TopologicalSorter(workflow); ts.prepare()
layer = 0
while ts.is_active():
    ready = list(ts.get_ready())               # 这一批互不依赖,可以并行交给不同 Agent
    print(f"第 {layer} 层可并行:", ready)
    ts.done(*ready); layer += 1

order = list(TopologicalSorter(workflow).static_order())
finish, prev = {}, {}
for t in order:
    deps = workflow[t]
    start = max((finish[d] for d in deps), default=0)
    prev[t] = max(deps, key=lambda d: finish[d]) if deps else None
    finish[t] = start + cost[t]
path, t = [], "报告"
while t: path.append(t); t = prev[t]
print("关键路径:", " → ".join(reversed(path)), " 总耗时:", finish["报告"])

try:
    list(TopologicalSorter({"A": {"B"}, "B": {"A"}}).static_order())
except CycleError as e:
    print("发现环(死锁):", e.args[1])

输出:

第 0 层可并行: ['取数']
第 1 层可并行: ['清洗']
第 2 层可并行: ['因子A', '因子B']
第 3 层可并行: ['因果检验', '回测']
第 4 层可并行: ['风控审查']
第 5 层可并行: ['报告']
关键路径: 取数 → 清洗 → 因子B → 回测 → 风控审查 → 报告  总耗时: 22
发现环(死锁): ['A', 'B', 'A']

(同一层内的任务顺序可能因运行而不同。)

1.4 工程意义

结论 用法
第 2、3 层可以并行 启动两个 Agent 同时计算因子 A 和 B
关键路径经过因子 B 优化因子 A 没有用;要加速,得优化因子 B 或回测
静态检测环 在执行前就发现 Agent 之间的循环依赖

风控审查被设计成"回测"和"因果检验"的共同后继:结构上保证任何结果在进入报告之前都必须经过风控。这是把安全规则写进拓扑结构本身,而不是依赖 Agent 记得去做(参见《Agent交易系统的安全边界》)。


第二层:持续同调——看见 Agent 输出的"形状"(研究基础 + 构想)

2.1 问题

五个 Agent 分别独立分析同一份数据,给出结论。它们是:

只看平均意见会掩盖结构。拓扑数据分析(TDA)中的持续同调正好用来描述数据的"形状"在不同尺度下如何变化。

2.2 直觉

把每个 Agent 的输出(比如对一组股票的打分向量,或结论文本的嵌入向量)看作空间中的一个点。逐渐增大一个距离阈值 ε\varepsilon,把距离小于 ε\varepsilon 的点连起来:

记录每个团"出生"和"消亡"的 ε\varepsilon,就得到持续条形码。0 维持续同调描述的是"连通分支"(团);1 维描述的是"环"(比如 Agent 之间循环的、无法收敛的意见)。

2.3 代码:0 维持续同调

import numpy as np
rng = np.random.default_rng(0)
emb = np.vstack([rng.normal(0, 0.3, (5, 2)), rng.normal(3, 0.3, (5, 2)), [[8, 8]]])  # 两组共识 + 一个离群 Agent
n = len(emb); parent = list(range(n))
def find(i):
    while parent[i] != i: parent[i] = parent[parent[i]]; i = parent[i]
    return i
edges = sorted((np.linalg.norm(emb[i] - emb[j]), i, j) for i in range(n) for j in range(i + 1, n))
deaths = []
for d, i, j in edges:
    a, b = find(i), find(j)
    if a != b: parent[a] = b; deaths.append(round(float(d), 2))
print("连通分支消亡的距离(持续条形码):", deaths)

输出:

连通分支消亡的距离(持续条形码): [0.11, 0.17, 0.24, 0.25, 0.32, 0.36, 0.42, 0.43, 3.27, 6.76]

2.4 怎么读

在投研流程中,这可以变成一条规则:条形码里出现长寿命的分支时,不要直接平均,而是把分歧交给人或"仲裁 Agent"(参见《解构主义与任务流》 中"分歧是有价值的信号")。

2.5 已有研究基础


第三层:范畴论——工作流的可组合性(理论构想)

3.1 直觉

范畴论研究的是"东西"(对象)和"东西之间的变换"(态射),以及变换如何组合。

范畴论 工作流
对象 数据类型:原始行情、清洗后的面板、因子值、回测结果、风控意见
态射 一个 Agent 或一个步骤:把一种数据变成另一种
组合 前一个 Agent 的输出类型必须等于后一个的输入类型
结合律 (A 然后 B) 然后 C = A 然后 (B 然后 C):分组方式不影响结果
恒等态射 "什么都不做"的步骤

3.2 构想中的用途

3.3 诚实的评估

范畴论在这里主要提供的是思考框架和设计约束,而不是新的算法。在规模不大的工作流中,类型系统和 DAG 已经足够;当工作流变得非常大、由 Agent 动态生成时,形式化的组合规则可能更有价值。


总结

层 工具 成熟度 用途
1 DAG、拓扑排序、关键路径 成熟 执行顺序、并行、瓶颈、死锁检测、把安全规则写进结构
2 持续同调 研究基础 + 构想 发现 Agent 意见中的派别和离群者
3 范畴论 理论构想 工作流的类型安全与可组合性