目录
- 一、本篇学习地图
- 二、六种模式总览(先记这张表)
- [三、练习 wf_01:Prompt Chaining(提示词链)](#三、练习 wf_01:Prompt Chaining(提示词链))
- [四、练习 wf_02:Parallelization(并行化)](#四、练习 wf_02:Parallelization(并行化))
- [五、练习 wf_03:Routing(路由)](#五、练习 wf_03:Routing(路由))
- [六、练习 wf_04:Orchestrator-Worker(编排器-工人)](#六、练习 wf_04:Orchestrator-Worker(编排器-工人))
- [七、练习 wf_05:Evaluator-Optimizer(评估器-优化器)](#七、练习 wf_05:Evaluator-Optimizer(评估器-优化器))
- [八、练习 wf_06:Agents(工具调用循环)⭐](#八、练习 wf_06:Agents(工具调用循环)⭐)
- [九、Workflows vs Agents 选型决策](#九、Workflows vs Agents 选型决策)
- [十、踩坑清单(本篇新增 4 坑)](#十、踩坑清单(本篇新增 4 坑))
- 十一、复盘总结
一、本篇学习地图
04 篇精读文档的核心是:一件事该用"工作流"还是"智能体"?工作流有 5 种模式,智能体是第 6 种。本篇把 6 种全部亲手写了一遍(DeepSeek 模型版)。
#mermaid-svg-f6ggaaAt9rNjWVNZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f6ggaaAt9rNjWVNZ .error-icon{fill:#552222;}#mermaid-svg-f6ggaaAt9rNjWVNZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f6ggaaAt9rNjWVNZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .marker.cross{stroke:#333333;}#mermaid-svg-f6ggaaAt9rNjWVNZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f6ggaaAt9rNjWVNZ p{margin:0;}#mermaid-svg-f6ggaaAt9rNjWVNZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .cluster-label text{fill:#333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .cluster-label span{color:#333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .cluster-label span p{background-color:transparent;}#mermaid-svg-f6ggaaAt9rNjWVNZ .label text,#mermaid-svg-f6ggaaAt9rNjWVNZ span{fill:#333;color:#333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .node rect,#mermaid-svg-f6ggaaAt9rNjWVNZ .node circle,#mermaid-svg-f6ggaaAt9rNjWVNZ .node ellipse,#mermaid-svg-f6ggaaAt9rNjWVNZ .node polygon,#mermaid-svg-f6ggaaAt9rNjWVNZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f6ggaaAt9rNjWVNZ .rough-node .label text,#mermaid-svg-f6ggaaAt9rNjWVNZ .node .label text,#mermaid-svg-f6ggaaAt9rNjWVNZ .image-shape .label,#mermaid-svg-f6ggaaAt9rNjWVNZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-f6ggaaAt9rNjWVNZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f6ggaaAt9rNjWVNZ .rough-node .label,#mermaid-svg-f6ggaaAt9rNjWVNZ .node .label,#mermaid-svg-f6ggaaAt9rNjWVNZ .image-shape .label,#mermaid-svg-f6ggaaAt9rNjWVNZ .icon-shape .label{text-align:center;}#mermaid-svg-f6ggaaAt9rNjWVNZ .node.clickable{cursor:pointer;}#mermaid-svg-f6ggaaAt9rNjWVNZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .arrowheadPath{fill:#333333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f6ggaaAt9rNjWVNZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f6ggaaAt9rNjWVNZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f6ggaaAt9rNjWVNZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f6ggaaAt9rNjWVNZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f6ggaaAt9rNjWVNZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f6ggaaAt9rNjWVNZ .cluster text{fill:#333;}#mermaid-svg-f6ggaaAt9rNjWVNZ .cluster span{color:#333;}#mermaid-svg-f6ggaaAt9rNjWVNZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f6ggaaAt9rNjWVNZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f6ggaaAt9rNjWVNZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-f6ggaaAt9rNjWVNZ .icon-shape,#mermaid-svg-f6ggaaAt9rNjWVNZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f6ggaaAt9rNjWVNZ .icon-shape p,#mermaid-svg-f6ggaaAt9rNjWVNZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f6ggaaAt9rNjWVNZ .icon-shape .label rect,#mermaid-svg-f6ggaaAt9rNjWVNZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f6ggaaAt9rNjWVNZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f6ggaaAt9rNjWVNZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f6ggaaAt9rNjWVNZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 智能体(动态路径)
工作流(预定义路径)
Send API
ReAct 式
wf_01 Prompt Chaining
提示词链
wf_02 Parallelization
并行化
wf_03 Routing
路由
wf_04 Orchestrator-Worker
编排器-工人
wf_05 Evaluator-Optimizer
评估器-优化器
wf_06 Agent
LLM 自主调用工具循环
动态派发
子任务数运行时才知道
LLM 决策循环
看结果→想下一步→调工具
二、六种模式总览(先记这张表)
| # | 模式 | 文件 | 核心思想 | 何时用 | 关键 API |
|---|---|---|---|---|---|
| 1 | Prompt Chaining | wf_01 | 每次 LLM 调用处理上一次的输出,可带门函数提前结束 | 明确定义、可分解的步骤 | add_conditional_edges + 门函数 |
| 2 | Parallelization | wf_02 | 多节点同从 START 出发,并行执行后汇聚 | 独立子任务提速 / 多标准评分 | 多 add_edge(START, node) + aggregator |
| 3 | Routing | wf_03 | LLM 结构化输出"决策",条件边分流 | 输入需要语义理解后分流 | with_structured_output + 条件边 |
| 4 | Orchestrator-Worker | wf_04 | 编排器拆任务 → Send 动态派工 → 合并结果 | 子任务无法预定义 | Send + Annotated[list, operator.add] |
| 5 | Evaluator-Optimizer | wf_05 | 生成器 + 评估器循环,不合格带反馈回炉 | 有明确标准但需迭代 | 循环条件边 + state 带反馈 |
| 6 | Agent | wf_06 | LLM 自主决定调工具,连续反馈循环 | 问题不可预测 | bind_tools + ToolMessage 循环 |
!NOTE
选型心法(文档原话) :能用 Workflow 就不要用 Agent------预定义路径更可靠、可审计、成本可控;只有任务无法预测时才让 LLM 动态决策。
三、练习 wf_01:Prompt Chaining(提示词链)
文件 :wf_01_prompt_chaining.py
3.1 目标
生成笑话三连:生成 → 检查笑点(门函数)→ 不合格才改进 → 润色。展示"上一次 LLM 输出 → 下一次 LLM 输入"的链式传递 + 提前退出的门。
3.2 核心结构
python
def check_punchline(state: State):
"""门函数:检查笑话是否有笑点"""
if "?" in state["joke"] or "!" in state["joke"]:
return "Pass"
return "Fail"
workflow.add_conditional_edges(
"generate_joke",
check_punchline,
{"Fail": "improve_joke", "Pass": END}, # 门返回值 → 目标节点映射
)
3.3 实测输出(Fail 路径------Oct 31 == Dec 25 三连进化)
初始笑话: 程序员为什么分不清万圣节和圣诞节?因为 Oct 31 == Dec 25(八进制 31=十进制 25)
改进笑话: 加了"万剩节/生蛋节""十在8错"双关
最终笑话: 加反转结局(== 写成 = 后天天过圣诞节)
3.4 踩坑:全角/半角标点(本轮最大的坑) ⭐
现象:中文笑话明明有问号/感叹号,门函数却返回 Fail,莫名其妙走改进链。
根因 :门函数检查的是半角 ?/!(U+003F/U+0021),而中文 LLM 输出的是全角 ?/!(U+FF1F/U+FF01)------Python 判断 "?" in "?" 是 False,两个完全不同的字符!
修复:同时检查两种标点:
python
if any(p in state["joke"] for p in ["?", "!", "?", "!"]):
!WARNING
通用教训 :凡做文本判断(门函数/路由/校验),标点必须全角半角都查。中文 LLM 输出几乎全是全角标点,只写英文标点的判断逻辑会悄悄判错。
四、练习 wf_02:Parallelization(并行化)
文件 :wf_02_parallelization.py
4.1 目标
三个 LLM(笑话/故事/小诗)同时干活,最后 aggregator 汇成"三件套"。
4.2 核心结构
python
# 三个节点都从 START 出发 → 并行 fan-out
builder.add_edge(START, "call_llm_1")
builder.add_edge(START, "call_llm_2")
builder.add_edge(START, "call_llm_3")
# 汇聚 fan-in
builder.add_edge("call_llm_1", "aggregator")
builder.add_edge("call_llm_2", "aggregator")
builder.add_edge("call_llm_3", "aggregator")
4.3 实测输出
关于「程序员」的三件套!
📖 故事:他删掉最后一行代码时,服务器终于安静下来...
😂 笑话:程序员最讨厌两件事:写注释,以及别人不写注释。
✍️ 小诗:代码千行夜未央,咖啡续命对屏忙。需求一日三更改,键帽纷飞心不慌。
4.4 执行时序(BSP 屏障再次登场)⭐
Step 1(一个 super-step!):
① Plan:三个节点都订阅 START → 全部选中
② Execution:三个 LLM 请求并行发出!
③ Update:joke/story/poem 三个通道同时落地
Step 2:aggregator 选中(屏障后!)→ 拼装
Step 3:无人订阅 → 停止
关键理解:
- 并行 ≠ 乱序。三个 LLM 谁先返回无所谓------全部完成后 aggregator 才触发(BSP Update 阶段统一落地,下一步才可见)
- 这里不需要 reducer:三个节点各写各的字段(不冲突);只有"同写一个字段"才需要合并(见 wf_04)
五、练习 wf_03:Routing(路由)
文件 :wf_03_routing.py
5.1 目标
LLM 判断用户要什么(笑话/诗/故事),路由到对应节点。决策者从"代码 if/else"升级为"LLM 语义理解"。
5.2 核心结构
python
class Route(BaseModel):
step: Literal["poem", "story", "joke"] = Field(None, description="只能从这三样选")
router = llm.with_structured_output(Route) # 结构化输出作路由决策
def llm_call_router(state: State):
decision = router.invoke(
[
SystemMessage(content="根据用户的请求,判断他想要的是故事、笑话还是诗。"),
HumanMessage(content=state["input"]),
]
)
return {"decision": decision.step}
def route_decision(state: State): # 条件边函数
return state["decision"] # 返回的字符串 → 映射查找
5.3 实测输出
【请求】给我讲个程序员的笑话 → 【路由到】joke → 程序猴子档笑话
【请求】写一首关于秋天的诗 → 【路由到】poem → 《秋日书》七律
【请求】写一个关于猫的短故事 → 【路由到】story → 《收件人:毛茸茸的暴君》
三次全对!注意细节:
- 用 Pydantic
BaseModel定义 schema(03 篇用过 TypedDict,两者都行) - 路由调用用
[SystemMessage, HumanMessage]消息列表,不用字符串------系统角色与用户输入分离
5.4 与 03 篇 Command 路由的对比 ⭐
| Command 路由(03 篇) | Routing(本篇) | |
|---|---|---|
| 谁决定 | 代码写死 if/elif | LLM 结构化输出决策 |
| 到达方式 | 节点返回 Command(goto=...) | 条件边函数读 state 决策 |
| 适用 | 规则明确可编程 | 开放语义分流 |
混合是常态:LLM 分类(语义)+ 代码路由(规则),如 03 篇邮件 agent。
六、练习 wf_04:Orchestrator-Worker(编排器-工人)
文件 :wf_04_orchestrator_worker.py
6.1 目标
LLM 编排器规划报告章节 → Send 动态派发 N 个工人 并行写 → 合并收集 → 合成器拼装。子任务数量运行时才知道------这是 Send 的本事。
6.2 核心结构
python
# ① 编排器:LLM 规划章节(结构化输出)
def orchestrator(state: State):
report_sections = planner.invoke([SystemMessage(...), HumanMessage(...)])
return {"sections": report_sections.sections}
# ② Send API:动态派发工人!
def assign_workers(state: State):
return [Send("worker", {"section": s}) for s in state["sections"]]
builder.add_conditional_edges("orchestrator", assign_workers, ["worker"])
# ③ reducer:并行写同一 key 必须合并!
class State(TypedDict):
completed_sections: Annotated[list, operator.add]
# ④ 工人:写自己的一章,追加进共享列表
def worker(state: WorkerState):
return {"completed_sections": [result.content]}
6.3 实测输出(8 章报告全量生成)
规划出的章节:
1. 引言:从大模型到 Agentic AI
2. 核心技术基础
3. Agent 架构与设计模式
...(共 8 章:引言/核心/架构/场景/挑战/安全/展望/结论)
最终报告:(8 章全部拼装成功,Markdown 标题 + --- 分隔,内容详实)
6.4 三个核心机制深度讲解 ⭐⭐
① Send 是"临时招工",add_edge 是"固定流水线":
add_edge = 固定工人:代码里写死几个工位
Send = 动态派发:运行时才知道招几个(这次 8 章=8 工人,下次 3 章=3 工人)
② Annotated[list, operator.add] 是"并发写同一字段"的救星:
8 个工人几乎同时往回写
completed_sections------没有 reducer 时后写的覆盖先写的,只剩 1 章!
operator.add对 list =list1 + list2= 追加合并 → 8 个工人全部累积。
③ 为什么 synthesizer 只被触发一次?
BSP 屏障!所有 worker 在同一个 super-step 并行执行,Update 阶段合并后,synthesizer 才被选中------看到的是合并完成的完整列表,不是 8 次触发。
七、练习 wf_05:Evaluator-Optimizer(评估器-优化器)
文件 :wf_05_evaluator_optimizer.py
7.1 目标
生成器出笑话 → 评估器评分 → 不合格给建议 → 生成器带反馈重写 → 循环至合格。
7.2 核心结构
python
def route_joke(state: State):
if state["funny_or_not"] == "funny":
return "Accepted"
else:
return "Rejected + Feedback"
builder.add_conditional_edges(
"llm_call_evaluator", route_joke,
{"Accepted": END, "Rejected + Feedback": "llm_call_generator"}, # 循环!
)
7.3 实测输出(一轮过关)
=== 最终笑话 ===
(二进制门牌号 2333 梗:面试官问缺点→沉迷二进制→"欢迎光临"在二进制里是 2333 号→被录用改门牌号)
7.4 关键设计点
- 反馈载体是 state :
feedback写进 state,generator 下一轮读它改进------"把可恢复信息存 state 循环回去"的通用模式(03 篇错误循环同一套路) - 循环必须有收敛出口 :只一个
funny→END出口。若 LLM 一直判不好笑会无限循环(03 篇 10007 次的坑)------生产环境加"最大轮数"保险
八、练习 wf_06:Agents(工具调用循环)⭐
文件 :wf_06_agent.py
8.1 目标
Agent 模式的骨架 :LLM 自主决定要不要调工具、调哪个、用什么参数,工具结果喂回继续推理------连续反馈循环。
8.2 核心结构
python
@tool
def add(a: int, b: int) -> int:
"""计算 a + b 的和"""
return a + b
llm_with_tools = llm.bind_tools(tools) # ① 给 LLM 发"工具说明书"
def llm_call(state: MessagesState):
return {"messages": [llm_with_tools.invoke([SystemMessage(...)] + state["messages"])]}
def tool_node(state: MessagesState):
result = []
for tool_call in state["messages"][-1].tool_calls:
tool = tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
return {"messages": result} # ② 执行 + 配对回传
def should_continue(state: MessagesState) -> Literal["tool_node", END]:
return "tool_node" if state["messages"][-1].tool_calls else END # ③ 闸门
builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
builder.add_edge("tool_node", "llm_call") # ④ 循环!
8.3 实测输出(ReAct 推理现场直播)
[human] 算一下 (3+4)×2 等于多少?请分步计算并解释
[LLM] 决定调用工具: add(3, 4) ← 第一步:先加
[工具结果] 7
[LLM] 决定调用工具: multiply(7, 2) ← 第二步:看到 7 了,乘 2
[工具结果] 14
[ai] 所以,(3+4)×2 的计算过程如下:... 最终结果:14 🎉 ← 不需要工具了 → END
8.4 四幕拆解(本课最密集的信息流)⭐⭐⭐
第一幕:消息接力。MessagesState 默认追加消息(add_messages reducer),LLM 每一步都能看到全部历史:
消息1 [human] 算 (3+4)×2
消息2 [ai 带 tool_calls] 调 add(3,4) → 执行 → 消息3
消息3 [tool] add 结果 7
消息4 [ai 带 tool_calls] 调 multiply(7,2) → 执行 → 消息5
消息5 [tool] multiply 结果 14
消息6 [ai] 直接回答 → 无 tool_calls → END
第二幕:bind_tools 的秘密 。把工具说明书 (名字/参数/描述)注入 LLM 请求。LLM 每轮两个选择:直接回答 or 输出 tool_calls:
python
{'name': 'add', # 选哪个工具
'args': {'a': 3, 'b': 4}, # 参数:LLM 从问题推断
'id': 'call_00_VqKEvpXJEmoy...'} # 身份证号(配对钥匙!)
第三幕:tool_node 执行 。按 name 找工具 → 执行 → 结果装 ToolMessage(content=结果, tool_call_id=id)。
第四幕:配对理解。LLM 下次看到:
- 自己发的请求(带 id 的 tool_call)
- 工具返回(带同一个 id 的 ToolMessage)
→ 就知道"3+4=7 算完了,接下来用 7 去乘 2"。这就是 ReAct:看结果 → 想下一步 → 调工具 → 再看结果 → ...直到不需要工具。
#mermaid-svg-JB8pZlV8GrWKaHTi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JB8pZlV8GrWKaHTi .error-icon{fill:#552222;}#mermaid-svg-JB8pZlV8GrWKaHTi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JB8pZlV8GrWKaHTi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JB8pZlV8GrWKaHTi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JB8pZlV8GrWKaHTi .marker.cross{stroke:#333333;}#mermaid-svg-JB8pZlV8GrWKaHTi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JB8pZlV8GrWKaHTi p{margin:0;}#mermaid-svg-JB8pZlV8GrWKaHTi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JB8pZlV8GrWKaHTi .cluster-label text{fill:#333;}#mermaid-svg-JB8pZlV8GrWKaHTi .cluster-label span{color:#333;}#mermaid-svg-JB8pZlV8GrWKaHTi .cluster-label span p{background-color:transparent;}#mermaid-svg-JB8pZlV8GrWKaHTi .label text,#mermaid-svg-JB8pZlV8GrWKaHTi span{fill:#333;color:#333;}#mermaid-svg-JB8pZlV8GrWKaHTi .node rect,#mermaid-svg-JB8pZlV8GrWKaHTi .node circle,#mermaid-svg-JB8pZlV8GrWKaHTi .node ellipse,#mermaid-svg-JB8pZlV8GrWKaHTi .node polygon,#mermaid-svg-JB8pZlV8GrWKaHTi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JB8pZlV8GrWKaHTi .rough-node .label text,#mermaid-svg-JB8pZlV8GrWKaHTi .node .label text,#mermaid-svg-JB8pZlV8GrWKaHTi .image-shape .label,#mermaid-svg-JB8pZlV8GrWKaHTi .icon-shape .label{text-anchor:middle;}#mermaid-svg-JB8pZlV8GrWKaHTi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JB8pZlV8GrWKaHTi .rough-node .label,#mermaid-svg-JB8pZlV8GrWKaHTi .node .label,#mermaid-svg-JB8pZlV8GrWKaHTi .image-shape .label,#mermaid-svg-JB8pZlV8GrWKaHTi .icon-shape .label{text-align:center;}#mermaid-svg-JB8pZlV8GrWKaHTi .node.clickable{cursor:pointer;}#mermaid-svg-JB8pZlV8GrWKaHTi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JB8pZlV8GrWKaHTi .arrowheadPath{fill:#333333;}#mermaid-svg-JB8pZlV8GrWKaHTi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JB8pZlV8GrWKaHTi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JB8pZlV8GrWKaHTi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JB8pZlV8GrWKaHTi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JB8pZlV8GrWKaHTi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JB8pZlV8GrWKaHTi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JB8pZlV8GrWKaHTi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JB8pZlV8GrWKaHTi .cluster text{fill:#333;}#mermaid-svg-JB8pZlV8GrWKaHTi .cluster span{color:#333;}#mermaid-svg-JB8pZlV8GrWKaHTi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JB8pZlV8GrWKaHTi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JB8pZlV8GrWKaHTi rect.text{fill:none;stroke-width:0;}#mermaid-svg-JB8pZlV8GrWKaHTi .icon-shape,#mermaid-svg-JB8pZlV8GrWKaHTi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JB8pZlV8GrWKaHTi .icon-shape p,#mermaid-svg-JB8pZlV8GrWKaHTi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JB8pZlV8GrWKaHTi .icon-shape .label rect,#mermaid-svg-JB8pZlV8GrWKaHTi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JB8pZlV8GrWKaHTi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JB8pZlV8GrWKaHTi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JB8pZlV8GrWKaHTi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ToolMessage(7)
ToolMessage(14)
用户提问
算 (3+4)×2
LLM 决定调用工具
add(3,4)
工具执行器
加法计算
LLM 看到 7
决定调用 multiply(7,2)
工具执行器
乘法计算
LLM:结果 14
无 tool_calls → END
| 概念 | 一句话 |
|---|---|
bind_tools |
给 LLM 发工具说明书,用不用它决定 |
tool_calls |
LLM 说"我要调 X(参数)"------带唯一 id 的请求 |
ToolMessage |
工具结果,必须带 tool_call_id 才能配对 |
should_continue |
闸门:有 tool_calls→继续循环;无→结束 |
#mermaid-svg-yMACWy2wC85ygGeq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yMACWy2wC85ygGeq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yMACWy2wC85ygGeq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yMACWy2wC85ygGeq .error-icon{fill:#552222;}#mermaid-svg-yMACWy2wC85ygGeq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yMACWy2wC85ygGeq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yMACWy2wC85ygGeq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yMACWy2wC85ygGeq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yMACWy2wC85ygGeq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yMACWy2wC85ygGeq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yMACWy2wC85ygGeq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yMACWy2wC85ygGeq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yMACWy2wC85ygGeq .marker.cross{stroke:#333333;}#mermaid-svg-yMACWy2wC85ygGeq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yMACWy2wC85ygGeq p{margin:0;}#mermaid-svg-yMACWy2wC85ygGeq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yMACWy2wC85ygGeq .cluster-label text{fill:#333;}#mermaid-svg-yMACWy2wC85ygGeq .cluster-label span{color:#333;}#mermaid-svg-yMACWy2wC85ygGeq .cluster-label span p{background-color:transparent;}#mermaid-svg-yMACWy2wC85ygGeq .label text,#mermaid-svg-yMACWy2wC85ygGeq span{fill:#333;color:#333;}#mermaid-svg-yMACWy2wC85ygGeq .node rect,#mermaid-svg-yMACWy2wC85ygGeq .node circle,#mermaid-svg-yMACWy2wC85ygGeq .node ellipse,#mermaid-svg-yMACWy2wC85ygGeq .node polygon,#mermaid-svg-yMACWy2wC85ygGeq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yMACWy2wC85ygGeq .rough-node .label text,#mermaid-svg-yMACWy2wC85ygGeq .node .label text,#mermaid-svg-yMACWy2wC85ygGeq .image-shape .label,#mermaid-svg-yMACWy2wC85ygGeq .icon-shape .label{text-anchor:middle;}#mermaid-svg-yMACWy2wC85ygGeq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yMACWy2wC85ygGeq .rough-node .label,#mermaid-svg-yMACWy2wC85ygGeq .node .label,#mermaid-svg-yMACWy2wC85ygGeq .image-shape .label,#mermaid-svg-yMACWy2wC85ygGeq .icon-shape .label{text-align:center;}#mermaid-svg-yMACWy2wC85ygGeq .node.clickable{cursor:pointer;}#mermaid-svg-yMACWy2wC85ygGeq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yMACWy2wC85ygGeq .arrowheadPath{fill:#333333;}#mermaid-svg-yMACWy2wC85ygGeq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yMACWy2wC85ygGeq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yMACWy2wC85ygGeq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yMACWy2wC85ygGeq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yMACWy2wC85ygGeq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yMACWy2wC85ygGeq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yMACWy2wC85ygGeq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yMACWy2wC85ygGeq .cluster text{fill:#333;}#mermaid-svg-yMACWy2wC85ygGeq .cluster span{color:#333;}#mermaid-svg-yMACWy2wC85ygGeq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yMACWy2wC85ygGeq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yMACWy2wC85ygGeq rect.text{fill:none;stroke-width:0;}#mermaid-svg-yMACWy2wC85ygGeq .icon-shape,#mermaid-svg-yMACWy2wC85ygGeq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yMACWy2wC85ygGeq .icon-shape p,#mermaid-svg-yMACWy2wC85ygGeq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yMACWy2wC85ygGeq .icon-shape .label rect,#mermaid-svg-yMACWy2wC85ygGeq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yMACWy2wC85ygGeq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yMACWy2wC85ygGeq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yMACWy2wC85ygGeq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Agent 循环(ReAct 四步)
有
无
① LLM 看消息历史
决定:回答 or 调用工具
有 tool_calls?
② 工具执行器
按 name 找工具 + 传参数
③ ToolMessage 回传
同一 tool_call_id 配对
④ 直接回答 → END
8.5 与 03 篇 thinking_03 的对比
thinking_03 的错误自纠循环 = 手动模拟 的"LLM 决定→执行→看结果";wf_06 是官方 Agent 结构(bind_tools + MessagesState + ToolMessage 配对)------前者理解原理,后者写生产。两者同一套思想。
#mermaid-svg-UxAyvHJMFn24dtCM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UxAyvHJMFn24dtCM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UxAyvHJMFn24dtCM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UxAyvHJMFn24dtCM .error-icon{fill:#552222;}#mermaid-svg-UxAyvHJMFn24dtCM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UxAyvHJMFn24dtCM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UxAyvHJMFn24dtCM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UxAyvHJMFn24dtCM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UxAyvHJMFn24dtCM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UxAyvHJMFn24dtCM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UxAyvHJMFn24dtCM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UxAyvHJMFn24dtCM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UxAyvHJMFn24dtCM .marker.cross{stroke:#333333;}#mermaid-svg-UxAyvHJMFn24dtCM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UxAyvHJMFn24dtCM p{margin:0;}#mermaid-svg-UxAyvHJMFn24dtCM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UxAyvHJMFn24dtCM .cluster-label text{fill:#333;}#mermaid-svg-UxAyvHJMFn24dtCM .cluster-label span{color:#333;}#mermaid-svg-UxAyvHJMFn24dtCM .cluster-label span p{background-color:transparent;}#mermaid-svg-UxAyvHJMFn24dtCM .label text,#mermaid-svg-UxAyvHJMFn24dtCM span{fill:#333;color:#333;}#mermaid-svg-UxAyvHJMFn24dtCM .node rect,#mermaid-svg-UxAyvHJMFn24dtCM .node circle,#mermaid-svg-UxAyvHJMFn24dtCM .node ellipse,#mermaid-svg-UxAyvHJMFn24dtCM .node polygon,#mermaid-svg-UxAyvHJMFn24dtCM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UxAyvHJMFn24dtCM .rough-node .label text,#mermaid-svg-UxAyvHJMFn24dtCM .node .label text,#mermaid-svg-UxAyvHJMFn24dtCM .image-shape .label,#mermaid-svg-UxAyvHJMFn24dtCM .icon-shape .label{text-anchor:middle;}#mermaid-svg-UxAyvHJMFn24dtCM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UxAyvHJMFn24dtCM .rough-node .label,#mermaid-svg-UxAyvHJMFn24dtCM .node .label,#mermaid-svg-UxAyvHJMFn24dtCM .image-shape .label,#mermaid-svg-UxAyvHJMFn24dtCM .icon-shape .label{text-align:center;}#mermaid-svg-UxAyvHJMFn24dtCM .node.clickable{cursor:pointer;}#mermaid-svg-UxAyvHJMFn24dtCM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UxAyvHJMFn24dtCM .arrowheadPath{fill:#333333;}#mermaid-svg-UxAyvHJMFn24dtCM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UxAyvHJMFn24dtCM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UxAyvHJMFn24dtCM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UxAyvHJMFn24dtCM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UxAyvHJMFn24dtCM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UxAyvHJMFn24dtCM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UxAyvHJMFn24dtCM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UxAyvHJMFn24dtCM .cluster text{fill:#333;}#mermaid-svg-UxAyvHJMFn24dtCM .cluster span{color:#333;}#mermaid-svg-UxAyvHJMFn24dtCM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UxAyvHJMFn24dtCM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UxAyvHJMFn24dtCM rect.text{fill:none;stroke-width:0;}#mermaid-svg-UxAyvHJMFn24dtCM .icon-shape,#mermaid-svg-UxAyvHJMFn24dtCM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UxAyvHJMFn24dtCM .icon-shape p,#mermaid-svg-UxAyvHJMFn24dtCM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UxAyvHJMFn24dtCM .icon-shape .label rect,#mermaid-svg-UxAyvHJMFn24dtCM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UxAyvHJMFn24dtCM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UxAyvHJMFn24dtCM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UxAyvHJMFn24dtCM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是,路径明确
否,需探索
问题是否可预测?
选择 Workflow
(预定义路径)
选择 Agent
(动态决策)
链式:有先后依赖
并行:独立子任务
路由:语义分流
编排:动态子任务
评估循环:需迭代
bind_tools + 工具循环
限定工具集与行为准则
九、Workflows vs Agents 选型决策
| 维度 | Workflows | Agents |
|---|---|---|
| 执行路径 | 预定义代码路径 | 动态,自行定义 |
| 控制权 | 开发者完全掌控 | LLM 自主决策 |
| 适用 | 任务明确、可分解、可验证 | 问题与方案不可预测 |
| 自主性 | 低 | 高(可限定工具集与行为准则) |
| 可靠性 | 高(可审计) | 低(不可预测) |
| 成本 | 可控 | 略高(多轮 LLM) |
!IMPORTANT
选型建议 :Workflow 优先!先画流程------能确定的分支就用图;只有"LLM 自己决定用啥工具/走啥路径"才有 agent 的必要。Anthropic 官方指南就是这个观点。
持久化补充(04 篇文档要点):
- Checkpointer = 短期线程记忆(对话连续性 / HITL / time travel / 容错)
- Store = 长期跨线程记忆(用户偏好 / 事实 / 共享知识)
- 大多数应用两者都用;Agent Server 自动处理持久化基础设施
十、踩坑清单(本篇新增 4 坑)
| # | 坑 | 现象 | 解决 |
|---|---|---|---|
| 1 | 全角/半角标点(本轮最坑) | 门函数判 Fail,明明笑话有 ? |
any(p in joke for p in ["?", "!", "?", "!"]) |
| 2 | with_structured_output 必禁 thinking | 400: Thinking mode does not support tool_choice | extra_body={"thinking": {"type": "disabled"}}(复用 03 篇坑) |
| 3 | 并行写同 key 需 reducer | 多个 worker 写 completed_sections 只剩 1 章 |
Annotated[list, operator.add] |
| 4 | 循环图无出口(wf_05 隐患) | 评估器一直判不合格 → 无限循环 | 加"最大轮数"保险(state 计数) |
十一、复盘总结
11.1 六模式对应关系
| 模式 | 一句话收获 | 关键机制 |
|---|---|---|
| Prompt Chaining | 链式 + 门函数 | add_conditional_edges 字典映射 |
| Parallelization | 并行 fan-out + 汇聚 | 多 START 边 + BSP 屏障 |
| Routing | LLM 语义分流 | 结构化输出 + 条件边 |
| Orchestrator-Worker | 动态派工 + 并行合并 | Send + Annotated reducer |
| Evaluator-Optimizer | 迭代到合格 | 反馈存 state 循环 |
| Agent | LLM 自主工具循环 | bind_tools + ToolMessage 配对 |
11.2 心法五条(背下来)
- 先问:用 Workflow 还是 Agent? 能预定义路径就不用 Agent(更可控更便宜)
- Workflow 五模式的选择:链式(有先后依赖)→ 并行(独立子任务)→ 路由(语义分流)→ 编排(动态子任务)→ 评估循环(需迭代达标)
- 并发写同一个 state 字段 → 必须 Annotated reducer;各写各的不需要
- Send 是唯一能"运行时才知道招几个工"的 API------子任务动态生成就靠它
- Agent = 循环图 + 工具调用 + 配对回传;条件边值方向 = 判断出口,循环边 = 反馈传送带
11.3 学习进度
- 实战进度:02 篇(Pregel 7 例)→ 03 篇(Thinking 6 例)→ 本 04 篇(Workflows/Agents 6 例)完成
- 下一步建议:05 篇《Checkpointers 核心概念》精读实战,或 09 篇《Interrupts 与 HITL》(03 篇 thinking_06 已打地基)