LangGraph 实战 03:Workflows 与 Agents——六种工作流模式与智能体实战(附 6 个可运行示例)

目录

  • 一、本篇学习地图
  • 二、六种模式总览(先记这张表)
  • [三、练习 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 关键设计点

  • 反馈载体是 statefeedback 写进 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 心法五条(背下来)

  1. 先问:用 Workflow 还是 Agent? 能预定义路径就不用 Agent(更可控更便宜)
  2. Workflow 五模式的选择:链式(有先后依赖)→ 并行(独立子任务)→ 路由(语义分流)→ 编排(动态子任务)→ 评估循环(需迭代达标)
  3. 并发写同一个 state 字段 → 必须 Annotated reducer;各写各的不需要
  4. Send 是唯一能"运行时才知道招几个工"的 API------子任务动态生成就靠它
  5. Agent = 循环图 + 工具调用 + 配对回传;条件边值方向 = 判断出口,循环边 = 反馈传送带

11.3 学习进度

  • 实战进度:02 篇(Pregel 7 例)→ 03 篇(Thinking 6 例)→ 本 04 篇(Workflows/Agents 6 例)完成
  • 下一步建议:05 篇《Checkpointers 核心概念》精读实战,或 09 篇《Interrupts 与 HITL》(03 篇 thinking_06 已打地基)
相关推荐
狂炫冰美式19 分钟前
电脑合盖之后 Cursor 还在偷我电?看看为啥
前端·人工智能·后端
leeyi19 分钟前
微前端:14 个子应用是怎么做到不散架的——DeepFlux 前端工程拆解(第102篇)
前端·aigc·agent
程序员三明治20 分钟前
【体验毛坯房】Deep Harness 入门教程
java·人工智能·后端·大模型·llm·deepseek·dsh
zzzll111120 分钟前
深入理解 Java HashMap:从原理到实战
java·开发语言·哈希算法
猫猫不是喵喵.28 分钟前
Java开发高频面试题汇总(通俗易懂版)
java·开发语言·java高频面试
2603_9658966237 分钟前
JavaScript 对象零基础详解|属性、方法、遍历、增删改查
开发语言·javascript·ecmascript
我命由我1234538 分钟前
Android 设备的日志缓冲区被写满,logcat: Unexpected EOF!
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
xcl092543 分钟前
分销返利商城系统开发实战:返利算法与架构设计指南
java·spring boot
天道kabuto1 小时前
踩坑实录:JS Map迭代器为什么“用完即焚”?兼谈技术知识沉淀
前端