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 关键设计点

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

  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 分钟前
MyBatis XML 开发实战:从配置到 CRUD 全解析
xml·java·mybatis
Jmyd01235 小时前
Web端3D营销展厅技术实现路径:从素材上云到线索回流的数据链路
前端·3d·性能优化·三维数字化
哦虎!5 小时前
【JavaEE】网络初识
java
半个落月5 小时前
LangChain Agent 中间件实战(上):生命周期、状态、短路与工具包
javascript·langchain
砚凝霜5 小时前
【软考信息安全】第二十二章 Web服务器安全分析与增强——Apache与IIS
服务器·前端·安全
for_ever_love__5 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
半个落月5 小时前
LangChain Agent 中间件实战(下):DeepAgents文件权限、长期记忆与对话摘要
javascript·langchain
我是人✓6 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
欣栀♡6 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
IT研究室7 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计