Blink 渲染数据流
1. 我一开始应该怎么想 Agent?
不是先想:
1 | 怎么接 LLM? |
而是先想:
1 | 如果没有 LLM, |
比如查订单:
1 | 用户要查订单 |
所以我的理解是:
先把确定性的业务流程设计出来,再看哪些地方真的需要 LLM。
可以先记:
1 | 确定性的事情 → 程序 |
2. State 到底是什么?
我的理解:
State 就是“这个任务现在进行到什么情况了”。
例如:
1 | { |
它里面保存:
1 | 我是谁? |
3. candidate 和 verified 为什么要分开?
这个我现在理解成:
1 | 用户说的 / LLM 提取的 |
比如用户说:
1 | 订单号 A100 |
只能先放:
1 | candidate.orderId = "A100" |
不能直接认为:
1 | A100 一定是真的订单 |
必须 Tool 查过之后,才能进入:
1 | verified.order |
所以:
LLM 给我的只是“候选信息”,Tool 验证之后才是“业务事实”。
4. status 和 step 有什么区别?
我现在理解:
1 | status |
比如:
1 | { |
意思就是:
现在不能继续,因为缺订单号;当前位置是在等订单号。
5. State 是谁改的?
不是让 LLM 随便改。
应该是:
1 | State + Event |
比如:
1 | 用户补充订单号 |
程序决定:
1 | waiting_user |
所以:
LLM 可以帮助理解用户,但是最终怎么改 State 是程序决定的。
6. Tool 是什么?
我的理解:
Tool 就是一个真实业务能力。
比如:
1 | getOrder({ |
Tool 不应该拿整个 State:
1 | getOrder(state) |
而应该只拿它需要的数据:
1 | getOrder({ |
所以:
Tool 不需要知道整个 Agent,它只需要完成一个具体业务操作。
7. Runner 是什么?
以前手写的时候:
1 | while (state.status === "running") { |
我的理解:
Runner 就是自动推进任务的人。
它一直做:
1 | 看现在 step 是什么 |
直到:
1 | waiting_user |
8. applyMessage() 和 run() 有什么区别?
这个可以这样理解:
1 | applyMessage() |
例如:
1 | Agent:请提供订单号 |
9. handleMessage() 到底是干嘛的?
我现在理解它不是具体业务逻辑。
它更像:
一次用户请求进来之后的总协调入口。
大概:
1 | 用户 Message |
所以:
1 | handleMessage |
10. 为什么需要 Task Registry?
因为以后不可能只有查订单。
可能有:
1 | order_query |
所以可以注册:
1 | taskRegistry = { |
我的理解:
顶层只负责找到对应业务,具体业务自己知道怎么运行。
11. Session 和 Task State 是一回事吗?
不是。
我的理解:
1 | Session |
例如:
1 | { |
意思:
1 | 当前正在做 T002 |
12. Router / Resolver 是干嘛的?
它解决的是:
用户这句话,到底想干什么?
比如得到:
1 | { |
或者:
1 | { |
关键点:
Resolver 只负责判断,不负责真正修改业务 State。
13. 为什么 LLM 判断完不能直接执行?
因为 LLM 输出只能算:
1 | Candidate Action |
还要经过:
1 | Schema Validation |
例如 LLM 说:
1 | resume T999 |
程序还要检查:
1 | T999 是不是当前真的可以 resume? |
所以:
LLM 提建议,程序做最终裁决。
14. 为什么给 LLM 的 Context 要尽量少?
因为 Resolver 只是判断:
1 | 这个用户现在想干什么? |
它可能只需要:
1 | { |
没必要把整个数据库给它。
所以:
每一层只拿完成自己工作需要的数据。
15. 多任务怎么切换?
例如:
1 | 正在查订单 T001 |
可以:
1 | T001 → paused |
以后:
1 | resume T001 |
继续之前的位置。
关键:
pause 不能把原来的执行位置丢掉。
16. 参数验证是不是只有一次?
不是。
我现在把它分成三层:
1 | 1. 有没有? |
例如:
1 | null |
17. error 不等于 failed
这个很重要。
例如:
1 | 订单号格式错了 |
有 error,但是:
1 | 用户还能重新输入 |
所以应该可能是:
1 | waiting_user |
而不是:
1 | failed |
我现在区分:
1 | Validation Error |
18. Retry 应该放在哪里?
Tool 做一次操作。
比如:
1 | getOrder() |
至于:
1 | 超时多久 |
应该是 Tool 的执行策略。
所以:
1 | Tool |
19. ToolResult 为什么要统一?
因为第三方 API 格式可能全都不同。
Runner 不应该懂:
1 | Shopify 返回什么 |
统一变成:
1 | { |
或者:
1 | { |
或者:
1 | { |
所以:
ToolResult 是 Tool 和 Runner 之间的统一协议。
20. Response 为什么和 State 分开?
因为:
1 | State |
不能把整个内部 State 都直接返回用户。
所以每个业务可以:
1 | buildResponse(state) |
现在我已经有三个协议:
1 | Resolver → Executor |
21. Checkpoint 是什么?
我的理解非常简单:
State 是现场,Checkpoint 是存档。
Agent 不是一直跑着。
而是:
1 | 请求进来 |
下次用户再来:
1 | 加载上次存档 |
22. 为什么有 Checkpoint 还需要 Idempotency?
因为可能发生:
1 | createRefund 已经成功 |
恢复以后又执行:
1 | createRefund |
可能退款两次。
所以写操作要有稳定的:
1 | idempotencyKey |
可以理解:
1 | Checkpoint |
23. version 是解决什么?
解决并发。
两个请求同时:
1 | load version 3 |
都修改,再保存,就可能互相覆盖。
所以保存的时候检查:
1 | 数据库现在还是不是 version 3? |
如果不是:
1 | 说明别人已经改过了 |
所以:
1 | version |
24. 到这里,为什么开始学 Graph?
因为我们手写 Runtime 已经变成:
1 | State |
会发现:
本质上我已经在自己实现一个 Graph Runtime。
所以框架只是把这些东西正式抽象出来。
25. 手写 Runtime 和 Graph 怎么对应?
我现在这样记:
1 | 以前 Graph |
26. Node 到底是什么?
Node 就是:
流程里的一个执行站点。
以前:
1 | if (step === "check_order") { |
现在:
1 | checkOrderNode(state) |
所以我可以理解成:
Node ≈ 原来某个 step 里面真正执行的那段代码。
27. Node 和 Tool 一样吗?
不一样。
1 | Tool |
例如:
1 | checkOrderNode |
Node 还负责:
1 | 解释 ToolResult |
28. Edge 是什么?
很简单:
一个 Node 做完以后,下一个 Node 去哪。
固定下一步:
1 | A → B |
就是普通 Edge。
29. Conditional Edge 是什么?
如果下一步需要判断:
1 | check_order |
就是 Conditional Edge。
所以:
1 | 固定下一步 → Edge |
30. Graph 是什么?
Graph 可以理解成:
把整个业务流程显式画出来。
以前流程藏在:
1 | while + if + step |
现在直接:
1 | START |
31. 每个 State 都有自己的 Graph 吗?
不是。
我的理解:
1 | Graph |
比如:
1 | OrderQueryGraph |
三个任务可以共用同一张 Graph。
32. 有 Graph 以后还需要 step 吗?
如果 step 只是告诉 Runner:
1 | 下一段代码执行什么 |
通常不需要了。
因为 Graph Runtime 已经知道:
1 | 现在在哪个 Node |
但是如果:
1 | approvalStatus = pending |
这是业务事实,就还是应该存在 State。
所以:
1 | 技术执行位置 |
33. Node 为什么可以只 return 一部分 State?
Node 可以:
1 | return { |
不用:
1 | return { |
因为 Graph Runtime 会根据 State 的 merge rule 合并。
所以执行过程:
1 | State |
34. Reducer 是什么?
Reducer 就是:
新值和旧值到底怎么合并?
例如:
1 | order |
以前我自己写:
1 | candidate: { |
其实就是在手写 merge rule。
35. START / END 是什么?
1 | START |
它们通常不是实际业务 Node。
36. Interrupt 和 END 一样吗?
不一样。
1 | END |
比如:
1 | 缺退款原因 |
用户之后补充:
1 | 商品坏了 |
再:
1 | Resume |
继续。
所以:
1 | waiting_user |
37. Graph 的 Checkpoint 多保存了什么?
以前主要保存:
1 | 业务 State |
Graph 还需要知道:
1 | 业务 State |
所以恢复以后不需要重新:
1 | START |
而是从暂停的位置继续。
38. Graph Run / Task ID 是什么?
同一张 Graph 可以跑很多实例:
1 | refundGraph |
所以必须知道:
我现在恢复的是哪一次运行?
不同框架可能叫:
1 | taskId |
本质一样:
一次具体 Graph 运行实例的 ID。
39. Session 和 Graph Run ID 又是什么关系?
我现在这样理解:
1 | Session |
比如:
1 | Session S001 |
所以:
1 | Session |
40. Subgraph 是什么?
就是:
Graph 里面还能调用 Graph。
例如:
1 | Agent Graph |
这里:
1 | OrderQueryGraph |
都是 Subgraph。
41. Supervisor Graph 是什么?
可以理解:
顶层总调度。
它负责:
1 | 用户想干什么? |
例如:
1 | Supervisor |
这其实就是以前:
1 | handleMessage |
的 Graph 版本。
42. Parent Graph 和 Subgraph 要共用全部 State 吗?
不一定,而且通常不应该。
顶层可能只需要:
1 | userId |
退款 Graph 自己需要:
1 | orderId |
所以:
每个 Graph 只维护自己关心的数据。
43. Subgraph 怎么把结果返回父 Graph?
像函数一样:
1 | Parent |
父 Graph 不需要知道 Subgraph 所有内部细节。
44. Subgraph 中途 Interrupt 怎么办?
如果 Parent 正在等 Subgraph:
1 | Parent |
那么整个这次运行也只能先停。
Checkpoint 要记:
1 | Parent 在哪 |
用户回来以后:
1 | Resume |
45. Node 里面一定是 Tool 吗?
不是。
Node 可以分成:
1 | 普通代码 Node |
例如:
1 | 格式验证 |
所以:
Node 是执行位置,里面用什么能力是另外一件事。
46. Agent Loop 是什么?
它的结构:
1 | Agent Node |
所以:
1 | Agent |
可以理解:
1 | Agent Node |
47. Fixed Graph 和 Agent Loop 怎么选?
我的理解:
流程确定
例如退款:
1 | 查订单 |
用:
1 | Fixed Graph |
因为程序已经知道流程。
下一步不确定
例如:
1 | 帮我分析这个客户最近为什么一直投诉 |
可能需要:
1 | 先查订单? |
可以用:
1 | Agent Loop |
48. 最后我现在形成的整体理解
Agent 不是:
1 | LLM + 一堆 Tools |
这么简单。
一个比较完整的 Agent,其实是:
1 | 用户消息 |
而最重要的设计原则一直没有变:
1 | 能让程序确定的 |
我目前已经从:
1 | “Agent 就是 LLM 调工具” |
走到了:
1 | “Agent 本质上是一个 |
然后 Graph Framework 做的事情,就是把我之前手写的:
1 | State |
变成更正式的:
1 | State |

