第十五周有两条线:知识图谱 × 大模型(GraphRAG、NL2Cypher)和 Graph Engineering(多 Agent 拓扑)。
作业走的是第二条:实现一个可以下发 subagent 的 agent,并行完成多项工作。
做完之后有个结论我觉得特别值得写下来:并行省的是时间,不是钱。
一、为什么要 KG × LLM
两边正好互补:
| LLM 的短板 | KG 的补充 |
|---|---|
| 幻觉——事实错误,缺乏可溯源依据 | 结构化三元组——显式表达实体与关系 |
| 结构推理弱——多跳查询、关系链不稳定 | 可溯源验证——路径查询、逻辑链完整 |
而反过来,LLM 也把 KG 的构建门槛打穿了:
传统做法 NER + RE,CRF / BiLSTM-CRF / 远程监督 标注贵、迁移难
LLM 做法 Few-shot 抽三元组,输出 JSON 零标注、跨域即用
知识抽取从「专项工程」变成了「API 调用」。
这句话是这一节的核心。以前建一个领域图谱要 6~18 个月,现在 LLM 能直接从文本抽 {h, r, t}。
二、GraphRAG:两个阶段
离线索引
原始文档 → 分块(400~600 tok) → LLM 抽实体与关系 → 图谱构建
→ Leiden 社区检测(分层聚类)→ 每个社区由 LLM 生成摘要
在线查询:两条路
| 干什么 | 流程 | |
|---|---|---|
| Local Search | 具体实体 / 关系 / 路径查询 | 实体链接 → 邻居扩展 → 子图提取 → LLM 合成 |
| Global Search | 全局主题 / 摘要 / 趋势 | 分发社区摘要 → Map 并行生成 → Reduce 聚合 |
成本与收益的账要算清楚:
索引成本比向量 RAG 高 5~10 倍(LLM 抽取调用量大),但全局主题问答准确率比 vanilla RAG 提升 30%+。
这跟 Week10 做 RAG 时的感受一致——没有免费的提升,只有值不值的交换。
三、选型:什么时候用图
| 知识图谱 / GraphRAG | 向量 RAG | 长上下文 LLM | |
|---|---|---|---|
| 构建成本 | 高(6~18 个月) | 低(小时级) | 极低 |
| 多跳推理 | 强(图路径) | 弱(相似召回) | 中(依赖 CoT) |
| 可解释性 | 高(路径可溯) | 低(向量距离) | 低(黑盒) |
| 实时更新 | 时序 KG 可增量 | 容易 | 需再训练 |
决策原则:
简单召回走向量,多跳或需审计走图;混合架构则按查询类型路由。
不是越复杂越好,要看查询类型选范式。
主流形态是三层协同:向量召回(语义入口)→ 图谱推理(多跳精确)→ LLM 生成(合成答案)。
四、Graph Engineering:结构化的是「系统本身」
这是这周概念上最重要的区分:
| 结构化的是 | 回答的是 | |
|---|---|---|
| GraphRAG / 知识图谱 | 数据(实体、关系、三元组) | 「系统知道什么」 |
| Graph Engineering | 系统本身(agent、路由、审批门的拓扑) | 「系统是谁」 |
两者可以在同一架构里共存,因为回答的是不同问题。
AI 工程五层栈(逐层包含):
L1 Prompt → L2 Context → L3 Harness → L4 Loop(单 agent 循环)→ L5 Graph(多 agent 拓扑)
从 Week13 讲的 Prompt ⊂ Context ⊂ Harness,到 Week14 的 Loop,再到这周的 Graph——这条线终于串起来了。
三种典型拓扑
1. Diamond Router 分发给多路并行 → Aggregate 聚合
2. Orchestrator-Workers Supervisor 派发给 W1/W2/W3 → 回收合成
3. Pipeline Step A → Step B → 人工门 → Step C,不通过则回退
核心工程原则我抄了下来:
代码定边、模型定节点内——路由是确定性的 Python,LLM 只在节点内部做决策。
不用图的三种情况同样重要:单目标单领域且停止条件明确、纯顺序任务(上图反而降性能)、拓扑简单静态。
核心价值不是加更多 agent,而是用结构分离来提升结果的确定性。
五、作业:老师的代码在哪
week15 的 Videos 目录主题是 graph 和 LLM,和 subagent 看起来不沾边。但同一目录下并列放了第二套代码:
week15 graph与LLM/
├── graphrag_financial_report/ ← 主线:GraphRAG
└── market_research_subagents/ ← ★ 作业要参考的是这套
架构是动态 Orchestrator-Workers——拓扑在运行时长出来:
- 主 agent 是 ReAct 循环,有 2 个工具:
web_search(简单问题)和dispatch_subagents(派发多个 subagent 并行) - 主 agent 根据 query 自主决定用哪个——不是固定拓扑
- subagent 也是 ReAct 循环,多个用
ThreadPoolExecutor并行
我只换了一处
老师的 subagent 走 Tavily 联网搜索,我没有 Tavily key,这条路走不通。
换成:subagent 就一个子课题写一段结论,并落盘成文件。
这不影响作业要验证的东西:
| 并行加速从哪来 | LLM API 调用的网络 IO——跟这个 IO 是搜索还是补全无关 |
| 反而多一个好处 | 产物是 N 个文件,能客观验证每个 subagent 真的各干各的 |
六、一个刻意的设计:只回结论,不回过程
# orchestrator._dispatch 结尾
parts.append(f"【{r.sid}】{r.topic}\n{body[:700]}\n") # 只回结论,截到 700 字这是上下文隔离。主 agent 的 context 不会被 N 份中间过程撑爆。
★ 这一点和并行与否无关,是 subagent 相对「主 agent 自己串行做 N 件事」的另一个好处,很容易被并行加速的光环盖过去。
七、跑通验证
问> 2024年中国新能源汽车市场调研:销量规模、竞争格局、政策趋势
主 agent 自主拆成 3 个子课题 → 并行派发
s1 [ 0.0s → 12.7s] 销量规模
s2 [ 0.0s → 45.6s] 竞争格局
s3 [ 0.0s → 21.9s] 政策趋势
墙钟 45.58s ≈ 最慢那个(45.6s),而不是三者之和 80.17s
三项验证:
| 验证 | 怎么验 | 结果 |
|---|---|---|
| 真的并行了 | 记每个 subagent 的起止时刻,算区间重叠 | 3/3 对重叠 |
| 各干各的 | 三份产物两两算字符集合 Jaccard | 0.24~0.30,互不相同 |
| 不是见问题就拆 | 问单一事实问题 | 没派发,1 轮直接答 |
★ 「真的并行了」这条不能只看
wall < sum。如果某个 subagent 秒退(比如报错),wall也会小于sum,但那不是并行带来的。区间重叠才是直接证据。
八、对比实验
分两部分测,因为要控的变量不一样。
路由:主 agent 自主决定
[OK] 2024年新能源汽车市场调研:销量、竞争、政策 期望派发 派了 3 个 43.5s 28,111 tok
[OK] 远程办公:成本影响、效率影响、管理难点 期望派发 派了 3 个 46.2s 19,289 tok
[OK] 什么是 ThreadPoolExecutor? 期望不派发 没派发 4.8s 1,205 tok
[OK] Python 里 list 和 tuple 有什么区别? 期望不派发 没派发 7.7s 1,602 tok
单一事实问题只花 1,2051,602 tok,多侧面问题花 19,28928,111 tok——不该派的时候不派,本身就是省钱。
计时 A/B:固定子课题,只切执行方式
模式 墙钟s 子agent之和s 加速比 重叠对数 总tok
并行 19.9 46.0 2.31 3.0 18,172
串行 42.2 42.2 1.00 0.0 17,880
-53% +2%
执行区间画出来一眼就看清了:
并行 s1 [ 0.0 → 7.2] s2 [ 0.0 → 18.5] s3 [ 0.0 → 15.3] → 3/3 重叠
串行 s1 [ 0.0 → 6.9] s2 [ 6.9 → 23.5] s3 [23.5 → 41.3] → 0/3 重叠
串行那行首尾相接,正是 for 循环的样子;并行三个都从 0.0 开始。串行模式下加速比恰好 1.00(wall == sum)——这是这个指标的自检。
★ 并行省的是时间,不是钱
墙钟 -53% ← 真实收益
总 token +2% ← 噪声范围内,没省
该调的 LLM 一次没少调,只是同时调而已。
加速比 2.31× 不等于成本降到 1/2.31。 这个容易误读,所以两个指标必须一起量。
为什么计时必须固定子课题
第一版让主 agent 自己拆,结果同一个问题跑两次,一次拆 3 个、一次只拆 1 个(temperature=0 也照样变)。
并行那次派 1 个、串行那次派 3 个的话,比出来的是「模型这次心情如何」,不是「并行有没有用」。
所以路由让主 agent 自主决定(单独测),计时绕过拆分用固定的 3 个子课题——唯一变量才真的只剩执行方式。
九、约束写进 prompt 不算数,得写进宿主
这次踩了两个同源的坑,都是「我在 prompt 里说了,以为模型会听」:
| 位置 | prompt 里写的 | 模型实际做的 | 宿主怎么兜 |
|---|---|---|---|
| subagent | 工具描述写了「每个子调研员只调用一次」 | 存了两遍 | if res.finding_file: return "[拒绝]" |
| 主 agent | (没写,我以为不会) | 拿到结果后又派了一轮 | if res.dispatched: return "[拒绝]" |
第二个更麻烦:它让计时统计失真——墙钟只记最后一次派发的,token 却是两次的总和。
★ 而且这两个 bug 叠在一起,第一个把第二个盖住了:我的检查是「文件数 == subagent 数」,看到 5 个文件先怀疑 subagent 存两遍(确实也有),修完发现还是 5 个,才往上一层查到主 agent 重复派发。
这周的收获
1. 「结构化数据」和「结构化系统」是两件事。
GraphRAG 回答「系统知道什么」,Graph Engineering 回答「系统是谁」。这个区分让我把前面几周的东西串起来了:Prompt → Context → Harness → Loop → Graph,每一层都在往外扩一圈「模型之外的东西」。
2. 并行的收益要说清楚是哪一种。
墙钟 -53%,token +2%。如果简历上只写「通过并行 subagent 将效率提升 2.31 倍」,面试官问一句「成本降了多少」我就答不上来。加速比和成本是两个维度,一起量才不会误导自己。
3. 验证「真的并行了」要看重叠区间,不是看总时长。
wall < sum 这个条件太弱了——一个 subagent 崩掉也能满足它。记起止时刻算区间重叠,才是直接证据。
4. prompt 里的约束是建议,宿主里的检查才是约束。
这条 Week9 讲约束解码时就出现过(「能用协议保证的,就不要靠提示词祈祷」),这周在多 Agent 上又撞了一次。模型不听话不是意外,是默认情况。
下周是大模型结构演进。