LangGraph 思维模式教程(二):拆解流程
本系列教程将带你深入理解 LangGraph 的思维方式,学会用 LangGraph 的方式构建智能体。
本章主题:Step 1 - 把业务流程拆解成离散的节点 阅读时间:约 20 分钟
开篇:从需求到流程
为什么要先拆解流程?
在开始写代码之前,最重要的一步是理解需求,拆解流程。
常见错误:
需求来了 → 直接写代码 → 边写边改 → 越来越乱
正确做法:
需求来了 → 理解需求 → 拆解流程 → 画流程图 → 再写代码
为什么这样更好?
1. 思路清晰:
- 先想清楚再动手
- 避免边写边改
2. 便于沟通:
- 流程图可以和产品、业务确认
- 确保理解一致
3. 易于修改:
- 发现问题在流程图上改
- 不影响代码
4. 便于分工:
- 流程拆解后可以并行开发
- 每个人负责几个节点
案例背景
假设产品团队找到你,说:
"我们需要一个 AI 客服,能自动处理客户邮件。要求:
- 读取客户邮件
- 按紧急程度和主题分类
- 搜索相关文档回答问题
- 起草回复
- 复杂问题转人工
- 必要时安排后续跟进"
听起来挺复杂?别急,我们一步步拆解。
这个需求的难点:
1. 多种情况:
- 问题、Bug、账单、功能请求...
- 每种情况处理方式不同
2. 条件分支:
- 紧急的要人工审核
- Bug 要创建工单
- 问题要搜索文档
3. 可能循环:
- 人工审核不通过要重新起草
- 可能需要多轮沟通
这正是 LangGraph 擅长的场景!
一、理解需求:典型场景分析
1.1 为什么要分析典型场景?
在拆解流程之前,先理解需求。最好的方法是分析典型场景。
为什么分析典型场景?
1. 理解业务:
- 看看实际会发生什么
- 理解每种情况的处理方式
2. 发现边界:
- 找出所有可能的情况
- 发现特殊情况和边界条件
3. 验证理解:
- 和产品、业务确认
- 确保理解正确
4. 指导设计:
- 场景直接映射到流程
- 每个场景对应一条路径
1.2 典型场景分析
让我们来看看这个案例的典型场景:
场景一:简单问题
客户邮件:"如何重置密码?"
期望流程:
1. 读取邮件
2. 分类:问题 / 低紧急度
3. 搜索文档:找到密码重置指南
4. 起草回复:包含重置步骤
5. 发送回复
特点:
- 流程简单
- 不需要人工审核
- 可以完全自动化
场景二:Bug 报告
客户邮件:"导出功能选择 PDF 时崩溃"
期望流程:
1. 读取邮件
2. 分类:Bug / 中紧急度
3. 创建 Bug 工单
4. 起草回复:告知已记录
5. 发送回复
特点:
- 需要创建工单
- 不需要搜索文档
- 可以完全自动化
场景三:紧急账单问题
客户邮件:"我被重复扣款了!"
期望流程:
1. 读取邮件
2. 分类:账单 / 高紧急度
3. 起草回复
4. 人工审核(紧急问题必须人工确认)
5. 发送回复
特点:
- 紧急问题
- 必须人工审核
- 不能完全自动化
场景四:功能请求
客户邮件:"能添加深色模式吗?"
期望流程:
1. 读取邮件
2. 分类:功能请求 / 低紧急度
3. 搜索文档:查看是否已有相关功能
4. 起草回复
5. 发送回复
特点:
- 需要搜索文档
- 不需要人工审核
- 可以完全自动化
场景五:复杂技术问题
客户邮件:"API 集成间歇性出现 504 错误"
期望流程:
1. 读取邮件
2. 分类:复杂 / 高紧急度
3. 起草回复(可能需要更多信息)
4. 人工审核(复杂问题需要人工介入)
5. 发送回复
特点:
- 复杂问题
- 需要人工审核
- 可能需要更多信息
1.3 从场景中提取规律
分析完场景,我们可以提取一些规律:
规律一:所有场景都有这些步骤
- 读取邮件
- 分类意图
- 起草回复
- 发送回复
规律二:根据分类有不同分支
- 问题、功能请求 → 搜索文档
- Bug → 创建工单
- 账单、复杂 → 直接起草
规律三:根据紧急程度决定是否人工审核
- 高紧急度 → 人工审核
- 低紧急度 → 直接发送
规律四:人工审核可能循环
- 审核不通过 → 重新起草 → 再次审核
二、拆解流程:从场景到节点
现在我们有了典型场景,可以开始拆解流程了。
2.1 识别关键步骤
从上面的场景,我们可以识别出这些关键步骤:
| 步骤 | 说明 | 出现频率 | 为什么需要 |
|---|---|---|---|
| 读取邮件 | 获取邮件内容 | 每次都有 | 需要邮件内容才能处理 |
| 分类意图 | 判断邮件类型和紧急度 | 每次都有 | 决定后续处理方式 |
| 搜索文档 | 查找相关知识 | 问题、功能请求 | 需要知识来回答 |
| Bug 跟踪 | 创建 Bug 工单 | Bug 报告 | 需要记录 Bug |
| 起草回复 | 生成回复内容 | 每次都有 | 需要回复内容 |
| 人工审核 | 等待人工确认 | 紧急、复杂 | 确保质量 |
| 发送回复 | 发送邮件 | 每次都有 | 完成处理 |
如何识别关键步骤?
方法一:从场景中提取
- 列出每个场景的步骤
- 找出共同步骤
- 找出特殊步骤
方法二:从需求中提取
- 需求中的每个"动词"可能是一个步骤
- "读取"、"分类"、"搜索"、"起草"、"发送"
方法三:从问题中提取
- 问自己:要完成这个任务,需要做什么?
- 按顺序列出所有必要的步骤
2.2 画出流程图
有了关键步骤,我们可以画出流程图:
┌─────────────────────────────────────────────────────────────┐
│ 邮件处理流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 读取邮件 │ │
│ │ Read Email │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 分类意图 │ ──────┬──────┬──────┬──────┐ │
│ │ Classify │ │ │ │ │ │
│ └─────────────┘ │ │ │ │ │
│ │ │ │ │ │
│ ┌──────────────┘ │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ │ │ │
│ ┌─────────────┐ ┌──────────┐ │ │ │
│ │ 文档搜索 │ │ Bug 跟踪 │ │ │ │
│ │ Doc Search │ │Bug Track │ │ │ │
│ └──────┬──────┘ └────┬─────┘ │ │ │
│ │ │ │ │ │
│ └─────────┬─────────┘ │ │ │
│ │ │ │ │
│ ▼ │ │ │
│ ┌─────────────┐ │ │ │
│ │ 起草回复 │ ◄────────┘ │ │
│ │Draft Reply │ │ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ┌─────────┴─────────┐ │ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌─────────────┐ ┌───────────┐ │ │
│ │ 人工审核 │ │ 发送回复 │ ◄──────┘ │
│ │Human Review │ │Send Reply │ │
│ └──────┬──────┘ └───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────┐ │
│ │ 发送回复 │ │
│ │Send Reply │ │
│ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.3 理解流程图
让我详细解释这个流程图的每个部分:
第一步:读取邮件
作用:
- 入口点,所有流程从这里开始
- 获取邮件内容、发送者信息等
输入:
- email_id:邮件 ID
输出:
- email_content:邮件内容
- sender_email:发送者邮箱
为什么是第一步?
- 需要邮件内容才能进行后续处理
- 所有场景都需要这一步
第二步:分类意图
作用:
- 使用 LLM 分析邮件
- 输出:意图类型 + 紧急程度
输入:
- email_content:邮件内容
输出:
- classification:
- intent:question / bug / billing / feature / complex
- urgency:low / medium / high / critical
- topic:主题
为什么是第二步?
- 需要邮件内容才能分类
- 分类结果决定后续处理方式
- **这是分支点**
第三步:根据分类分流
分类后有三条路:
| 分类结果 | 下一步 | 原因 |
|---|---|---|
| 问题、功能请求 | 文档搜索 | 需要查找相关知识来回答 |
| Bug | Bug 跟踪 | 需要创建工单记录 |
| 账单、复杂 | 直接起草回复 | 不需要搜索,直接处理 |
为什么这样分流?
问题、功能请求 → 文档搜索:
- 需要相关知识才能回答
- 例如:"如何重置密码?"需要密码重置指南
Bug → Bug 跟踪:
- 需要记录 Bug,分配给开发团队
- 例如:"导出 PDF 时崩溃"需要创建工单
账单、复杂 → 直接起草:
- 账单问题需要查询账单系统,不是文档
- 复杂问题可能需要更多信息,先起草再人工审核
第四步:搜索或跟踪
文档搜索:
输入:
- classification.topic:搜索主题
输出:
- search_results:搜索结果列表
Bug 跟踪:
输入:
- email_content:Bug 描述
输出:
- ticket_id:工单 ID
两者完成后都去"起草回复"。
第五步:起草回复
作用:
- 使用 LLM 生成回复
- 根据紧急程度决定是否需要人工审核
输入:
- email_content:邮件内容
- classification:分类结果
- search_results:搜索结果(如果有)
- ticket_id:工单 ID(如果有)
输出:
- response_text:回复内容
决策:
- 如果 urgency = high 或 critical → 人工审核
- 否则 → 直接发送
第六步:人工审核(可选)
作用:
- 紧急或复杂问题需要人工确认
- 审核通过后发送
输入:
- response_text:起草的回复
输出:
- approved:是否通过
- edited_response:修改后的回复(如果有修改)
可能的结果:
- 审核通过 → 发送回复
- 审核不通过 → 重新起草(循环)
第七步:发送回复
作用:
- 发送邮件
- 流程结束
输入:
- sender_email:收件人
- response_text:回复内容
输出:
- 无(流程结束)
三、节点设计原则
3.1 单一职责原则
每个节点应该只做一件事:
好的设计:
├── read_email:只读取邮件
├── classify_intent:只分类意图
├── search_documentation:只搜索文档
├── bug_tracking:只创建工单
├── draft_response:只起草回复
├── human_review:只等待人工审核
└── send_reply:只发送回复
不好的设计:
└── process_email:读取、分类、搜索、起草、发送全在一个函数
为什么单一职责很重要?
1. 可测试:
- 每个节点可以单独测试
- 测试用例简单
- 问题容易定位
示例:
def test_classify_intent():
state = {"email_content": "如何重置密码?"}
result = classify_intent(state)
assert result["classification"]["intent"] == "question"
2. 可复用:
- 节点可以在其他流程中复用
- 不需要重复代码
示例:
- classify_intent 可以用于邮件、聊天、工单等多种场景
- search_documentation 可以用于问答、建议等多种场景
3. 可调试:
- 出问题容易定位
- 日志清晰
示例:
ERROR in classify_intent
→ 问题在分类节点
→ 检查分类逻辑
4. 可维护:
- 修改一个节点不影响其他节点
- 新功能容易添加
示例:
- 想改进分类逻辑?只改 classify_intent
- 想增加新的搜索源?只改 search_documentation
3.2 节点粒度
节点应该多大?这是一个常见问题。
太细的例子
def read_email_header(state):
"""只读取邮件头部"""
return {"header": get_header()}
def read_email_body(state):
"""只读取邮件正文"""
return {"body": get_body()}
def read_email_attachments(state):
"""只读取邮件附件"""
return {"attachments": get_attachments()}
问题:
- 过度拆分,增加复杂度
- 每个步骤都太小,没有独立意义
- 增加了节点数量,但没增加价值
太粗的例子
def process_email(state):
"""处理邮件:读取、分类、搜索、起草、发送"""
email = read_email()
category = classify(email)
docs = search(category)
draft = generate(email, docs)
send(draft)
return {}
问题:
- 失去 LangGraph 的所有优势
- 无法单独测试每个步骤
- 无法复用
- 无法调试
合适的例子
def read_email(state):
"""读取完整邮件"""
email = get_full_email()
return {"email": email}
def classify_email(state):
"""分类邮件意图"""
email = state["email"]
category = classify(email)
return {"category": category}
def search_docs(state):
"""搜索相关文档"""
category = state["category"]
docs = search(category)
return {"docs": docs}
def draft_response(state):
"""起草回复"""
email = state["email"]
docs = state["docs"]
draft = generate(email, docs)
return {"draft": draft}
def send_email(state):
"""发送回复"""
draft = state["draft"]
send(draft)
return {}
原则:每个节点对应业务流程中的一个明确步骤
判断标准:
- 能否用一句话描述这个节点做什么?
- 这个节点是否有独立的业务意义?
- 这个节点是否可以单独测试?
3.3 输入输出清晰
每个节点应该有清晰的输入和输出:
def search_documentation(state: EmailState) -> dict:
"""
搜索相关文档
输入:
- state["classification"]["topic"]: 搜索主题
- state["classification"]["intent"]: 意图类型
输出:
- {"search_results": 搜索结果列表}
说明:
- 根据分类结果的主题搜索相关文档
- 返回最多 5 条相关文档
"""
topic = state["classification"]["topic"]
intent = state["classification"]["intent"]
# 根据意图调整搜索策略
if intent == "feature":
query = f"功能:{topic}"
else:
query = topic
results = knowledge_base.search(query, top_k=5)
return {"search_results": results}
为什么输入输出要清晰?
1. 文档作用:
- 看注释就知道节点做什么
- 不需要读代码
2. 类型检查:
- 可以用类型检查器验证
- 提前发现错误
3. 接口明确:
- 知道需要什么数据
- 知道会产生什么数据
4. 便于测试:
- 知道如何构造测试输入
- 知道如何验证输出
四、确定节点列表
4.1 节点列表
根据上面的分析,我们确定以下节点:
| 节点名称 | 功能 | 输入 | 输出 | 类型 |
|---|---|---|---|---|
read_email |
读取邮件 | email_id | email_content, sender_email | 数据节点 |
classify_intent |
分类意图 | email_content | classification | LLM 节点 |
search_documentation |
搜索文档 | classification | search_results | 数据节点 |
bug_tracking |
Bug 跟踪 | email_content | ticket_id | 动作节点 |
draft_response |
起草回复 | email_content, classification, search_results | response_text | LLM 节点 |
human_review |
人工审核 | response_text | approved, edited_response | 用户节点 |
send_reply |
发送回复 | sender_email, response_text | 无 | 动作节点 |
4.2 节点之间的跳转关系
| 当前节点 | 条件 | 下一个节点 | 说明 |
|---|---|---|---|
| START | - | read_email | 流程开始 |
| read_email | - | classify_intent | 读取后分类 |
| classify_intent | intent=question/feature | search_documentation | 需要搜索文档 |
| classify_intent | intent=bug | bug_tracking | 需要创建工单 |
| classify_intent | intent=billing/complex | draft_response | 直接起草 |
| search_documentation | - | draft_response | 搜索后起草 |
| bug_tracking | - | draft_response | 创建工单后起草 |
| draft_response | urgency=high/critical | human_review | 需要人工审核 |
| draft_response | urgency=low/medium | send_reply | 直接发送 |
| human_review | approved=True | send_reply | 审核通过 |
| human_review | approved=False | draft_response | 审核不通过,重新起草 |
| send_reply | - | END | 流程结束 |
4.3 跳转关系的可视化
START
│
▼
read_email
│
▼
classify_intent ─────┬──────────┬──────────┐
│ │ │ │
│ (question/ │ (bug) │(billing/ │
│ feature) │ │ complex) │
▼ ▼ │ │
search_documentation bug_tracking │
│ │ │ │
└────────┬─────────┘ │ │
│ │ │
▼ │ │
draft_reply ◄─────────────┘ │
│ │
┌─────┴─────┐ │
│ │ │
(urgent) (normal) │
│ │ │
▼ │ │
human_review │ │
│ │ │
┌───┴───┐ │ │
│ │ │ │
(pass) (fail) │ │
│ │ │ │
▼ └───────┼─────────────────────────┘
send_reply ◄─────┘
│
▼
END
五、本章小结
5.1 核心要点
1. 理解需求
方法:
- 分析典型场景
- 提取规律
- 和产品、业务确认
目的:
- 确保理解正确
- 发现边界情况
2. 识别步骤
方法:
- 从场景中提取
- 从需求中提取
- 从问题中提取
结果:
- 关键步骤列表
- 每个步骤的说明
3. 画出流程图
目的:
- 可视化流程
- 理清跳转关系
- 便于沟通
工具:
- 纸和笔
- 流程图工具
- ASCII 图
4. 确定节点
原则:
- 单一职责:每个节点只做一件事
- 合适粒度:不要太细也不要太粗
- 输入输出清晰
结果:
- 节点列表
- 跳转关系表
5.2 拆解流程的检查清单
在完成流程拆解后,问自己:
完整性检查:
□ 是否覆盖了所有典型场景?
□ 是否有遗漏的步骤?
□ 是否有遗漏的分支?
合理性检查:
□ 每个节点是否只做一件事?
□ 节点粒度是否合适?
□ 输入输出是否清晰?
一致性检查:
□ 节点之间的跳转关系是否清晰?
□ 是否有死循环?
□ 是否有无法到达的节点?
可行性检查:
□ 每个节点是否可以实现?
□ 是否有技术难点?
□ 是否需要外部依赖?
5.3 常见错误
错误一:跳过流程拆解,直接写代码
→ 结果:边写边改,越来越乱
错误二:节点粒度太细
→ 结果:过度复杂,没有价值
错误三:节点粒度太粗
→ 结果:失去 LangGraph 的优势
错误四:忽略边界情况
→ 结果:运行时出错
错误五:没有和产品确认
→ 结果:理解偏差,返工
5.4 下一章预告
下一章,我们将深入讲解 Step 2:识别节点类型。
我们会学习四种节点类型:
- LLM 节点:调用大模型
- 数据节点:查询数据
- 动作节点:执行操作
- 用户节点:等待人工输入
每种类型有不同的特点和实现方式,我们会在下一章详细讲解。
六、思考题
思考题一:增加节点
如果要增加一个"客户历史查询"节点,应该放在流程的哪个位置?
提示:
- 客户历史有什么用?
- 哪些场景需要客户历史?
- 放在哪个位置最合理?
思考题二:支持多轮对话
如果要支持"多轮对话"(客户可能回复),流程应该如何修改?
提示:
- 客户回复后应该怎么处理?
- 需要记住什么信息?
- 流程图应该如何修改?
思考题三:实际应用
想想你工作中的某个流程,试着:
- 分析典型场景
- 画出流程图
- 拆解成节点
- 确定跳转关系
系列导航
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)