第4章 节点 Node 详解

发布时间:2026/9/24 17:26:40

第4章 节点 Node 详解 节点是图的基本计算单元。本章讲清节点的完整输入输出契约它能接收哪些参数state / config / runtime、返回值必须遵循什么规则增量更新再介绍两个生产级能力——节点缓存CachePolicy与节点重试RetryPolicy以及 START / END 这两个特殊节点的本质。学完本章你写的节点就能达到生产代码的标准依赖可注入、输出可预期、失败可重试、重复调用可缓存。4.1 节点的输入输出节点输入三个可选注入参数在 LangGraph 中节点都是 Python 函数可以同步也可以异步它们可以接受以下参数state图的状态代表具体的业务数据config一个RunnableConfig对象包含 thread_id 之类的配置信息调用图时也可以传递用户自定义的其他配置runtime一个Runtime对象包含运行时 context可自定义 context调用图时传入以及其他信息如 store、stream_writer 等。以上参数会在运行过程中自动被 LangGraph 运行时注入——你不需要也不能手动传它们。由于是按关键字传参config和runtime的参数位置可以调换。三个参数的分工可以用一句话区分state 管业务数据config 管调用配置runtime 管运行时服务。依赖注入完整示例下面的客服节点演示了三个参数的典型用法从runtime.context获取注入的 LLM 与数据库依赖从config获取用户身份用runtime.stream_writer向图外推送进度importtimefromtypingimportTypedDict,Any,Listfromlangchain_core.runnablesimportRunnableConfigfromlanggraph.graphimportStateGraph,START,ENDfromlanggraph.runtimeimportRuntime# --- 模拟外部依赖 ---classMockLLM:definvoke(self,prompt:str):returnfAI Generated: Answer for {prompt}classMockDatabase:defget_user_info(self,user_id:str):return{id:user_id,role:vipifvipinuser_idelsestandard}classCustomerSupportState(TypedDict):query:str# 用户问题response:str# 客服回复log:List[str]# 处理日志defnode_customer_service(state:CustomerSupportState,config:RunnableConfig,runtime:Runtime)-dict: 客服节点展示如何从 config 中获取配置、 从 runtime.context 中获取注入的依赖LLM、DB并用 runtime 推送进度。 user_querystate[query]# 1、从 runtime 当中获取 context 对象依赖注入llmruntime.context[llm]dbruntime.context[db]# 2、从 config 当中获取调用方传入的配置configurableconfig.get(configurable,{})user_idconfigurable.get(user_id,guest)print(f\n[Node] 开始处理User ID:{user_id})# 3、验证依赖是否存在ifnotllmornotdb:return{response:System Error: Dependencies not injected!,log:[Error]}# 4、使用 db 对象查看用户角色user_infodb.get_user_info(user_id)user_tieruser_info.get(role)print(f[Node] 从 DB 获取用户角色:{user_tier})# 5、使用 runtime.stream_writer 向图外输出自定义数据writerruntime.stream_writer writer({status:thinking,message:f正在调用 LLM 为{user_tier}用户生成回复...})time.sleep(0.5)# 6、根据用户角色构建不同的 Prompt并模拟 LLM 调用promptfUser({user_tier}) asks:{user_query}llm_responsellm.invoke(prompt)return{response:llm_response,log:[fProcessed by{llm.__class__.__name__}]}defrun_demo():builderStateGraph(CustomerSupportState)builder.add_node(customer_service,node_customer_service)builder.add_edge(START,customer_service)builder.add_edge(customer_service,END)graphbuilder.compile()# 初始化外部依赖my_llmMockLLM()my_dbMockDatabase()# user_id 属于配置信息放入 configconfig{configurable:{user_id:vip_user_999,}}# LLM、DB 属于运行时依赖放入 context随 invoke 注入context{llm:my_llm,db:my_db}resultgraph.invoke({query:如何升级会员},configconfig,contextcontext)print(result)if__name____main__:run_demo()逐段解析runtime.context依赖注入节点内部没有任何MockLLM()/MockDatabase()的实例化代码——依赖完全由调用方在graph.invoke(..., context...)处注入。好处显而易见单测时注入 Mock 对象生产时注入真实客户端节点代码一行不改config[configurable]调用配置user_id是「每次调用可能不同」的信息放 config它与 context 的分界是——context 放服务/对象config 放数值型配置runtime.stream_writer进度外送writer({...})发出的数据不出现在状态里、不影响图的执行只出现在stream_modecustom的流中第 5 章详解。这是节点向外界汇报「我正在干嘛」的正规通道比在节点里 print 干净得多返回值依然是增量 dict只写response和log不碰query。节点输出必须返回增量节点的输出为当前节点对状态的增量更新而不能将接收到的整个状态实例返回出去。LangGraph 底层会把所有节点输出的状态都作为增量状态尝试与当前的全局状态做一次合并操作。如果把整个状态都输出会有两类问题对于没有配置任何 reducer 的状态键LangGraph 底层无法完成合并会抛出异常对于配置了 reducer 的状态键如果节点输出了不属于该节点更新的状态会导致数据错乱比如重复累加。错误示范返回整个 statefromtypingimportTypedDictfromlanggraph.constantsimportSTART,ENDfromlanggraph.graphimportStateGraphclassMyState(TypedDict):query:strfile_result:strweb_result:strfinal_answer:strdefquery_web(state:MyState)-dict:做网络搜索返回搜索结果# ★ 错误演示直接修改并 return 整个 statequerystate[query]state[web_result]f{query}的网络搜索结果returnstatedefquery_file(state:MyState)-dict:做文件搜索返回搜索结果querystate[query]state[file_result]f{query}的文件搜索结果returnstatedefanswer(state:MyState)-dict:返回最终的答案web_resultstate[web_result]file_resultstate[file_result]final_answerfLLM基于{web_result}{file_result}的最终结果state[final_answer]final_answerreturnstate graphStateGraph(state_schemaMyState)graph.add_node(answer)graph.add_node(query_web)graph.add_node(query_file)graph.add_edge(START,query_web)graph.add_edge(START,query_file)graph.add_edge(query_web,answer)graph.add_edge(query_file,answer)compiled_graphgraph.compile()rescompiled_graph.invoke({query:什么是LangGraph})print(res[final_answer])错误分析query_web和query_file并行执行两个函数都拿到{query: ..., 其余为 None}的初始状态快照各自往自己手里的 state 写入结果后返回了整个 state。合并时灾难发生了query_web返回的完整 state 里file_result是它没有权利写的字段值还是 Nonequery_file返回的完整 state 里web_result同理默认覆盖 reducer 下两个「完整状态」互相覆盖——后到者把先到者写入的键抹回 None于是answer节点读到的web_result或file_result可能是 None拼出的final_answer出现 “None” 字样甚至直接 KeyError。正确写法每个节点只返回自己负责的键defquery_web(state:MyState)-dict:return{web_result:f{state[query]}的网络搜索结果}defquery_file(state:MyState)-dict:return{file_result:f{state[query]}的文件搜索结果}defanswer(state:MyState)-dict:return{final_answer:fLLM基于{state[web_result]}{state[file_result]}的最终结果}一句话原则节点是「领任务、交产出」的工人只交自己那条生产线的产出别把整车间都搬回来。4.2 特殊节点START 与 ENDSTART和END是 LangGraph 当中的特殊节点START代表「将用户输入发送到图中」的节点。引用它的主要目的是确定应首先调用哪些节点从 START 引出的边就是入口边END代表终止节点。当想表示哪些边在完成后没有动作时会引用这个节点指向 END 的边意味着「到这就结束」。它们的本质只是两个特殊字符串源码节选ENDsys.intern(__end__)The last (maybe virtual) node in graph-style Pregel.STARTsys.intern(__start__)The first (maybe virtual) node in graph-style Pregel.解析sys.intern把字符串放入全局驻留表保证全进程里START是同一个对象——比较时可以直接用is既省内存又加速称其为「virtual node虚拟节点」很准确它们不代表任何计算只是通道系统里的标记锚点——START 的「执行」就是把用户输入写进入口通道END 的「到达」就是让所有通道不再产生新更新START 也是一个会写检查点的节点在图执行完 START 之后会先创建一个初始 checkpoint再进入第一个超步。这就是get_state_history里第一个快照values为初始输入的来源。4.3 节点缓存CachePolicyLangGraph 支持基于节点输入对节点进行缓存对于配置了缓存的节点且缓存结果未过期时以相同的输入再次调用节点可直接从缓存读取结果不再执行节点计算。使用缓存分两步编译图时指定缓存存储后端langgraph.cache包提供内存缓存InMemoryCache、Redis 缓存、Sqlite 缓存后端构造实例后在 compile 时传入为节点指定缓存策略CachePolicy可配置key_func根据节点输入生成缓存键默认是对输入做 pickle 后的哈希值ttl缓存生存时间秒不指定则永不过期。示例代码importtimefromtyping_extensionsimportTypedDictfromlanggraph.graphimportStateGraphfromlanggraph.cache.memoryimportInMemoryCachefromlanggraph.typesimportCachePolicyfromlanggraph.constantsimportSTARTfromlanggraph.checkpoint.memoryimportInMemorySaverclassState(TypedDict):x:intresult:intbuilderStateGraph(State)checkpointerInMemorySaver()# 1、定义节点模拟耗时计算defexpensive_node(state:State)-dict[str,int]:print(expensive_node 被调用)time.sleep(5)# 模拟 5 秒的重计算print(expensive_node 计算完成)return{result:state[x]*2}# 2、添加节点时配置缓存策略10 秒过期缓存键用默认生成方式builder.add_node(expensive_node,expensive_node,cache_policyCachePolicy(ttl10))builder.add_edge(START,expensive_node)# 3、编译时传入缓存器也可用 RedisCache 等graphbuilder.compile(cacheInMemoryCache(),checkpointercheckpointer)# 4、第一次调用x5真实执行耗时 5 秒print(graph.invoke({x:5},config{configurable:{thread_id:1}}))# 5、第二次调用输入相同 → 直接命中缓存几乎瞬时返回不打印被调用print(graph.invoke({x:5},config{configurable:{thread_id:2}}))逐段解析命中判定的粒度是「节点输入」第二次调用换了 thread_id2但节点输入状态相同x5照样命中——缓存与 thread 无关只看输入ttl1010 秒内相同输入直接返回缓存超过 10 秒后缓存过期重新计算。适合「数据有时效但不要求强实时」的场景如汇率、推荐结果key_func定制默认对整个输入做哈希。如果输入里包含时间戳、request_id 这类每次都变的字段默认策略会永远 miss需要自定义key_func剔除易变字段典型场景图里有个「调用外部 LLM 改写 query」的节点相同 query 反复进来时缓存能省掉真金白银的 token 费用注意事项节点内部若依赖状态之外的外部变量如全局计数器、当前时间缓存结果可能「过时」——被缓存节点的逻辑必须是输入决定输出的纯函数才有正确性保证。4.4 节点重试RetryPolicy很多场景下节点由于客观原因限制执行过程并不稳定——调用 API、查询数据库、调用 LLM都可能出现瞬时故障。LangGraph 允许为节点配置自定义重试策略。为节点添加重试策略需在add_node中设置retry_policy参数。它接受一个RetryPolicy对象两个关键属性max_attempts总尝试次数上限含首次retry_on对哪些异常类型进行重试。示例代码importrandomfromtypingimportDict,Anyfromtyping_extensionsimportTypedDictfromlanggraph.graphimportStateGraph,START,ENDfromlanggraph.typesimportRetryPolicyclassState(TypedDict):result:str# 用全局变量跟踪尝试次数attempt_counter0defunstable_api_call(state:State)-Dict[str,Any]:模拟一个不稳定的 API 调用前两次失败第三次成功globalattempt_counter attempt_counter1print(f尝试调用API这是第{attempt_counter}次尝试)ifattempt_counter3:raiseException(f模拟API调用失败 (尝试{attempt_counter}))else:return{result:fAPI调用成功经过{attempt_counter}次尝试}defvalue_error_call(state:State)-Dict[str,Any]:模拟抛出 ValueError 的节点默认策略不会重试它print(调用会抛出 ValueError 的节点)raiseValueError(模拟 ValueError 异常)defrun_demo():globalattempt_counter# ---- 演示 1默认重试策略可重试的异常 ----print(1. 使用默认重试策略对瞬时异常重试:)attempt_counter0builder1StateGraph(State)builder1.add_node(unstable_call,unstable_api_call,retry_policyRetryPolicy(max_attempts5),# 最多尝试 5 次)builder1.add_edge(START,unstable_call)builder1.add_edge(unstable_call,END)graph1builder1.compile()try:resultgraph1.invoke({result:})print(f最终结果:{result}\n)exceptExceptionase:print(f最终失败:{type(e).__name__}:{e}\n)# ---- 演示 2默认策略不会重试的异常类型 ----print(2. 测试不会重试的异常类型:)builder3StateGraph(State)builder3.add_node(value_error_call,value_error_call,retry_policyRetryPolicy(max_attempts3),)builder3.add_edge(START,value_error_call)builder3.add_edge(value_error_call,END)graph3builder3.compile()try:resultgraph3.invoke({result:})print(f最终结果:{result}\n)exceptExceptionase:print(f最终失败:{type(e).__name__}:{e}\n)if__name____main__:run_demo()逐段解析演示 1 的运行轨迹unstable_api_call第 1、2 次抛普通Exception重试机制自动重跑节点第 3 次成功返回。控制台会看到三次「尝试调用API」最终拿到结果——图层面没有任何报错瞬时故障被透明地消化掉了默认策略的重试黑名单默认策略对绝大多数异常重试但对以下异常不重试它们通常意味着代码本身有 bug重试没有意义ValueError, TypeError, ArithmeticError, ImportError, LookupError, NameError, SyntaxError, RuntimeError, ReferenceError, StopIteration, StopAsyncIteration, OSError演示 2 的运行轨迹value_error_call抛出ValueError虽然在黑名单里——即使max_attempts3也只执行一次就立即把异常抛给图的调用方。这就是「策略性放弃」参数校验错误重试一百次还是错retry_on定制如果你的业务判断「ValueError 也值得重试」比如上游服务用 ValueError 报过载可以显式传retry_onValueError覆盖默认黑名单与 checkpointer 的配合重试发生在节点内部超步尚未结束不会写入中间检查点——重试全部失败后本次 invoke 报错但之前超步的检查点仍在可以配合第 3 章的invoke(None)做故障恢复。两个机制一个管「瞬时抖动」一个管「持久故障」层次互补。缓存与重试的定位对比能力解决的问题命中/触发条件语义CachePolicy重复计算浪费节点输入相同且缓存未过期跳过执行直接取旧结果RetryPolicy瞬时故障节点抛出可重试异常同一超步内自动重跑节点两者可以同时配在同一个节点上先查缓存未命中再执行执行失败再重试。4.5 本章小结节点可注入三个参数state业务数据、config调用配置如 user_id、runtime运行时服务context 依赖注入 stream_writer 进度外送节点必须返回增量状态——返回整个 state 在并行场景下会互相覆盖是新手第一大坑START / END 本质是sys.intern的特殊字符串虚拟节点START 执行后即落第一个检查点CachePolicy(ttl...)基于节点输入缓存结果省掉重复的重计算/LLM 调用RetryPolicy(max_attempts..., retry_on...)对瞬时异常自动重试默认黑名单里的确定性异常不重试缓存管「重复」、重试管「抖动」、checkpointer 恢复管「持久故障」三者互补构成节点的生产级可靠性。下一章讲图与外界的交互流式输出的五种模式以及 interrupt 人工审核。
延伸阅读

更多相关文章

2026/9/24 17:26:40

RabbitMQ和RocketMQ

^^ 《榴芒客服系统》是我们工作室开发的在线客服系统,欢迎下载试用: 《榴芒客服系统》https://blog.csdn.net/look4liming/article/details/164755808 1、开发语言: RabbitMQ是Erlang语言开发的; RocketMQ是Java语言开发的。 2、…

2026/9/24 17:26:40

RocketMQ知识点

^^ 《榴芒客服系统》是我们工作室开发的在线客服系统,欢迎下载试用: 《榴芒客服系统》https://blog.csdn.net/look4liming/article/details/164755808 RocketMQ是队列式的消息中间件。由Producer、Consumer、Broker、NameServer组成。Productor创建消息…

2026/9/24 18:21:44

Spring Boot后端项目部署实战:从解压到联调的全流程指南

简介:期刊出版数字化要求后端系统高效组织数据与业务逻辑。这份资源正是一套面向初中级开发者的期刊管理后端实现,适合用来学习API设计、数据库建模与权限控制。资源围绕期刊、文章、作者、审稿人等核心实体,以Python提供app入口、rpc远程调用…

2026/9/24 18:21:44

AI创业公司云平台选型指南:算力、成本与防锁定策略

这两年我经常被VC朋友问同一个问题:手上投了十几家AI公司,每家都在问云平台怎么选,能不能直接给个清单?说实话,这个问题没有标准答案,但问的人多了,我发现大家踩过的坑高度重合。今天这篇就从技…

2026/9/24 18:21:44

Win11网线直连传大文件:“输入网络凭据”问题全解析

1. 为什么网线直连才是最稳的文件传输方式先说个场景:两台电脑都需要互传大量文件,一个大活儿是几十 GB 的设计稿、视频素材或者虚拟机镜像。用 U 盘倒腾来回拔插累得够呛,走微信、网盘传大文件要么限速要么压缩画质,内网 WiFi 传…

2026/9/24 18:21:44

从COCO到YOLO:雨雪路面数据集训练全流程与避坑指南

简介:雨雪天气路面状况识别是自动驾驶与智能交通中的常见难点,这份数据集专门面向结冰路面、雪地、下雨湿滑、干燥路面四类场景,图片均为原始拍摄图像,并使用COCO格式进行目标标记,可直接用于目标检测、语义分割等模型…

2026/9/24 18:16:44

Java火车票系统实战:解决超卖、事务隔离与订单唯一性

简介:这是一套面向Java初学者与数据库课程实践者的火车票售票系统完整源码,基于Java Swing界面与Access数据库(.mdb文件)实现,解决小型票务场景下的车次管理、余票查询、在线售票与退票等核心业务需求。资源共89个文件…

2026/9/23 12:07:00

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/23 12:06:55

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/24 0:00:21

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:21

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:21

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/22 16:34:32

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/22 20:01:30

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/22 13:25:41

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码