节点是图的基本计算单元。本章讲清节点的完整输入输出契约它能接收哪些参数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 人工审核。
企业数字化 ERP 产品动态
相关推荐
RabbitMQ和RocketMQ ^^ 《榴芒客服系统》是我们工作室开发的在线客服系统,欢迎下载试用: 《榴芒客服系统》https://blog.csdn.net/look4liming/article/details/164755808
1、开发语言: RabbitMQ是Erlang语言开发的; RocketMQ是Java语言开发的。
2、… · 2026/9/24 17:26:36
安科瑞助力新型电力负荷管理系统建设:政策驱动下的企业微电网智慧升级方案 随着国家能源局明确鼓励工业企业、工业园区建设智能微电网,新型电力负荷管理系统成为电力保供与新能源消纳的关键抓手。江苏省率先出台《新型电力负荷管理系统数据接入规范》,为虚拟电厂、智能微电网等新型经营主体的数据接入与协同运营提供了技术遵循。… · 2026/9/24 17:26:23
Hi8000 BOOST 升压恒压驱动芯片|智芯半导体 聚能芯一级代理 概述Hi8000 作为一款性能卓越的 BOOST 升压恒压控制驱动芯片,其外围电路设计简约。它广泛适用于输入电压范围在 2.7 - 40V 的升压恒压电源应用领域,具备低至 2.5V 的启动电压。该芯片能够依据负载的不同规模,自动在 PWM、PFM 和 BURST 模式之… · 2026/9/24 17:26:23
彻底卸载流氓软件:从识别、清理到卡顿优化全攻略 弄电脑这些年,我见过太多人因为"卸不干净"而重装系统,也有人愁眉苦脸地问"怎么我装了杀毒软件电脑还这么卡"——结果我过去一看,系统里躺着七八个全家桶软件,光启动项就有十几个,能不卡吗。今天这… · 2026/9/24 19:07:46
淘客返利APP核心拆解:联盟接口对接与结算系统设计 做了几年的电商导购类应用,这次把一个淘客返利APP从零到一完整做下来,最深的感受是:入口容易做,接口对接和结算系统才是真正的门槛。商品展示、搜索页这些谁都能堆出来,但订单能不能准确跟住、返利能不能算对、钱能不能… · 2026/9/24 19:07:46
蓝牙音响推荐2026:从编码到防水,按场景选不踩坑 聊蓝牙音响推荐,我一直有个观点:别只盯参数表,也别轻信品牌信仰。2026年这个时间点,蓝牙音响市场已经卷到了一个很有意思的阶段——入门款在做防水和高解析,中端款在拼编解码和单元结构,旗舰款则开始把智能… · 2026/9/24 19:07:46
2026年蓝牙音响选购指南:从场景到避坑,推荐这几款 写这篇蓝牙音响推荐之前,我特意翻了一遍过去一年多积累的试听笔记和用户反馈。蓝牙音响这个品类很有意思——门槛低到几十块就能出声,天花板又高到几千块你还觉得差点意思;参数表上大家都标得差不多,实际听感却能差出好几个档次。… · 2026/9/24 19:07:46
让你越来越累的不是工作,而是这三种人:识别与应对策略 你有没有过这种体会:一天下来,工作内容其实没那么难,也没怎么加班,但回到家整个人像被抽干了一样,瘫在沙发上别说干活,连手机都懒得刷。我过去一直以为是年纪大了、体力不行,后来认真复盘过好几… · 2026/9/24 19:07:46
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44