Node 怎么设计?每个节点都应该有明确职责
上一篇文章里我们讨论了 LangGraph 里的 State。一个重要结论是State 决定了节点之间如何传递信息也决定了流程如何路由和恢复。但有了 State 之后下一步就会遇到一个更实际的问题Node 应该怎么设计很多人一开始写 LangGraph会把 Node 理解成“随便放一个函数就行”。这样能跑但很快就会遇到维护问题。因为 Node 不是普通函数。它是图里的任务单元。如果职责不清整个图会变得很难读、很难测、很难复盘。这篇文章就讨论一个好的 LangGraph Node应该怎么设计Node 的本质是任务单元Node 可以理解为图中的一个处理步骤。它读取当前 State完成一件事再把更新写回 State。比如读取用户问题 ↓ 调用模型判断意图 ↓ 更新 intent 字段或者读取检索请求 ↓ 调用 Retriever ↓ 写入检索结果Node 的核心不是“代码短不短”而是“职责清不清楚”。如果一个 Node 既做检索又做判断又做格式化输出后面就很难维护。一个 Node 最好只做一类事情在工程上最稳的做法是一个 Node 只完成一种明确任务。比如意图判断节点 检索节点 重写问题节点 工具调用节点 结果整理节点 人工确认节点这样做有几个好处。第一便于理解。看节点名字就知道它大概在做什么。第二便于测试。你可以单独测这个节点输入什么、输出什么。第三便于替换。如果以后要换模型、换工具、换检索器只要改这个节点。第四便于调试。问题出在哪个步骤一眼能看出来。Node 的输入输出边界要清楚Node 不是随意修改 State 的地方。更好的方式是明确这个 Node 依赖哪些字段 这个 Node 会产出哪些字段 这个 Node 不应该乱碰哪些字段比如一个检索节点输入可能是user_query tenant_id permission_context输出可能是retrieval_results retrieval_scores sources这样节点边界就很清楚。如果所有节点都随便读写全局 State后面会很快失控。Node 不应该承担过多业务判断很多人会在 Node 里写很多 if else。比如先判断问题类型 再判断是否需要检索 再判断是否调用工具 再判断是否要人工确认如果一个 Node 里塞太多判断它就不再是任务单元而变成了流程垃圾场。更好的方式是一个 Node 做一个动作 分支判断交给 Edge 流程切换交给图结构例如分类节点 ↓ 条件边路由 ├─ 检索节点 ├─ 工具节点 └─ 人工确认节点这样结构更清楚。Node 应该命名清晰Node 名字最好一眼就能知道它在做什么。比如classify_intent rewrite_query retrieve_context rank_candidates generate_answer human_review finalize_output不要起特别泛的名字比如process handle run step1 node_a名字不清楚图就不清楚。Node 命名其实也是架构设计的一部分。Node 粒度怎么把握Node 太大不行太小也不行。太大的问题是职责混乱。太小的问题是图会碎得像一堆流水账。一个比较实用的判断标准是这个节点是否能被清楚描述成一个动作 这个动作是否有明确输入输出 这个动作是否值得单独测试如果答案是肯定的这个粒度通常就比较合适。比如“判断是否需要检索”是一个节点 “做检索”是一个节点 “判断检索结果是否足够”是一个节点 “生成答案”是一个节点这样的划分通常比较自然。Node 适合做确定性动作也适合做模型调用Node 不一定非得调用模型。它可以是很多类型的动作纯规则判断 数据清洗 模型推理 工具调用 结果整合 人工确认包装比如一个 Node 负责规则分类 一个 Node 负责调用 LLM 一个 Node 负责调用数据库 一个 Node 负责整理输出格式这说明 LangGraph 并不是只用来包模型。它更像是把“确定性逻辑”和“模型逻辑”组织在一个图里。Node 设计要考虑可观测性一个好 Node不只是能跑还要能查。所以 Node 最好能记录这些信息输入是什么 输出是什么 耗时多少 有没有报错 状态更新了什么 是否触发了分支这样后面排查问题时你可以知道问题发生在哪个节点。如果 Node 设计得很散日志也很散复盘会非常难。常见误区第一个误区是把 Node 当作随便的函数块。Node 不是越自由越好而是越明确越好。第二个误区是一个 Node 做太多事。职责越多后面越难改。第三个误区是 Node 没有清晰命名。名字不清楚图就没有结构感。第四个误区是 Node 直接乱改 State。这样会让图失去可预测性。第五个误区是只看正常路径不看失败路径。Node 的设计也要考虑失败、重试和兜底。总结LangGraph 里的 Node核心不是“能执行代码”而是“能承担一个清晰职责”。好的 Node 应该满足职责明确 输入输出清楚 粒度合适 命名清楚 便于测试 便于调试如果 State 是图的上下文那么 Node 就是图里的执行单元。下一篇文章可以继续讨论 Edge 和 Conditional Edge。因为有了清晰的节点之后下一步就是让流程知道执行完这个节点后应该去哪里。