【langgraph 从入门到精通graphApi 篇】LangGraph 多 Schema 状态(MultiSchema)详解:一张图讲清「输入 / 输出 / 全局 / 私有」

📅 2026/7/19 21:05:35
【langgraph 从入门到精通graphApi 篇】LangGraph 多 Schema 状态(MultiSchema)详解:一张图讲清「输入 / 输出 / 全局 / 私有」
文章目录LangGraph 多 Schema 状态MultiSchema详解一张图讲清「输入 / 输出 / 全局 / 私有」代码一、这个 Demo 解决什么问题二、四态全景先建立直觉三、核心代码逐段解析1) 输入状态调用方只给 question2) 输出状态最终只回 answer3) 全局状态输入 输出 私有4) 节点写答案也写私有字段5) 组装图三参数定乾坤6) 运行 验证四、运行结果五、知识点总结可单独摘成笔记六、适用场景 最佳实践七、易踩的坑八、一句话总结附完整源码LangGraph 多 Schema 状态MultiSchema详解一张图讲清「输入 / 输出 / 全局 / 私有」对应源码src/langgraph/apidemo/demo_in_out_MultSchema.py适用版本langgraph 1.2.x难度★★☆☆☆适合刚接触 LangGraph 状态机制的同学代码#!/usr/bin/env python# -*- coding: utf-8 -*- Date : 2026/7/19 File : demo_in_out_MultSchema.py Author : liwei68 Description : LangGraph 多 Schema 状态MultiSchema演示 一张图演示四种状态输入 / 输出 / 全局 / 私有 纯逻辑、不调模型运行即见效果。 fromtypingimportTypedDictfromlanggraph.graphimportStateGraph,START,END# ════════════════════════════════════════════════════════════════# 1) 输入状态 InputState —— 调用方「只能 / 只需」提供的字段# graph.invoke() 时只接受这里的键多给会报错# ════════════════════════════════════════════════════════════════classInputState(TypedDict):question:str# 调用方只需给出问题# ════════════════════════════════════════════════════════════════# 2) 输出状态 OutputState —— 最终「只返回」给调用方的字段# graph.invoke() 的返回值只保留这里的键其余被过滤掉# ════════════════════════════════════════════════════════════════classOutputState(TypedDict):answer:str# 调用方只关心答案# ════════════════════════════════════════════════════════════════# 3) 全局状态 OverallState —— 节点之间共享的「完整」状态# 它是 InputState OutputState 的超集并补充若干私有字段。# 图的运行状态以它为准但对外边界由 input/output 两个 schema 限定。# ════════════════════════════════════════════════════════════════classOverallState(InputState,OutputState):# ↓↓↓ 私有字段 Private节点内部使用既不在输入也不在输出里 ↓↓↓thinking_steps:list[str]# 中间推理过程私有不对外暴露token_cost:int# 内部记账 / 成本私有不对外暴露# ──────────────────────────────────────────────────────────────# 节点接收全局状态写入答案 私有字段# ──────────────────────────────────────────────────────────────defreason_node(state:OverallState)-dict:print(f[reason] 收到输入 question {state[question]!r})# 中间推理写入私有字段仅图内部可见steps[① 解析问题,② 检索知识,③ 生成答案]print(f[reason] 私有中间步骤(仅内部可见):{steps})print(f[reason] 私有成本记账 token_cost 128)return{# —— 输出字段会出现在最终结果 ——answer:f关于「{state[question]}」的答案是42,# —— 私有字段不会出现在最终结果 ——thinking_steps:steps,token_cost:128,}# ════════════════════════════════════════════════════════════════# 组装图用 OverallState 作为全局 schema# 用 input_schema / output_schema 收口对外的输入与输出边界# ════════════════════════════════════════════════════════════════builderStateGraph(OverallState,input_schemaInputState,output_schemaOutputState,)builder.add_node(reason,reason_node)builder.add_edge(START,reason)builder.add_edge(reason,END)graphbuilder.compile()# ════════════════════════════════════════════════════════════════# 运行演示# ════════════════════════════════════════════════════════════════print(*64)print(① 输入调用方只提供 InputState 的字段question)print(*64)resultgraph.invoke({question:生命、宇宙和一切的终极答案是什么})print(\n*64)print(② 输出只返回 OutputState 的字段answer)print( 私有字段 thinking_steps / token_cost 已被过滤不会出现在结果)print(*64)print(最终结果:,result)print(结果包含的键:,list(result.keys()))# ── 验证四种状态的行为 ───────────────────────────────────────────assertthinking_stepsnotinresult,❌ 私有字段不应出现在输出里asserttoken_costnotinresult,❌ 私有字段不应出现在输出里assertresult{answer:关于「生命、宇宙和一切的终极答案是什么」的答案是42},❌ 输出应与 OutputState 一致print(\n✅ 验证通过)print( · 输入 InputState : 调用方只需给 question)print( · 全局 OverallState : 图内部共享 question answer 私有字段)print( · 私有 Private : thinking_steps / token_cost 仅内部使用)print( · 输出 OutputState : 调用方只拿回 answer私有状态被隐藏)一、这个 Demo 解决什么问题写一个 LangGraph 图时我们常会遇到一个矛盾对外调用方我只想让你传question也只想拿回answer对内节点之间我需要在多个节点里共享一堆中间产物——检索结果、推理步骤、token 消耗、调试日志……这些绝不能泄露给调用方。如果用单一 State硬扛要么调用方能瞎填一堆内部字段要么返回值里塞满调用方不 care 的垃圾数据。MultiSchema多状态 Schema就是 LangGraph 给出的标准解法用三个 schema 把「对外边界」和「对内共享」彻底分开。本 Demo 在一个最小例子里把四种状态全部跑通。二、四态全景先建立直觉状态代码里的类角色比喻谁看得见输入 InputInputState访客登记表——只登记姓名调用方填图读输出 OutputOutputState交付单——只写最终成果图写调用方拿全局 GlobalOverallState内部工作台——所有工具都在上面节点之间共享私有 PrivateOverallState里的额外字段内部草稿纸——写废了也不给人看只有节点自己一句话OverallState InputState OutputState 私有字段而图的「门禁」由input_schema/output_schema把守。三、核心代码逐段解析1) 输入状态调用方只给questionclassInputState(TypedDict):question:str这是graph.invoke()的合法入参白名单。调用方多传任何键都会直接报错——从入口就卡死非法输入。2) 输出状态最终只回answerclassOutputState(TypedDict):answer:str这是返回值的白名单。图跑完以后只有OutputState里声明的字段会出现在result里其余字段包括私有字段全部被过滤掉。3) 全局状态输入 输出 私有classOverallState(InputState,OutputState):# ↓↓↓ 私有字段 Private节点内部使用既不在输入也不在输出里 ↓↓↓thinking_steps:list[str]# 中间推理过程私有不对外暴露token_cost:int# 内部记账 / 成本私有不对外暴露OverallState通过多重继承把输入、输出字段收进来再补上两个只有图内部才需要的私有字段。图的运行态以它为基准节点读写都基于这个「全量」结构。 关键点OverallState必须是InputState和OutputState的超集。这是 LangGraph 的硬性约束否则compile()会报错。4) 节点写答案也写私有字段defreason_node(state:OverallState)-dict:print(f[reason] 收到输入 question {state[question]!r})steps[① 解析问题,② 检索知识,③ 生成答案]print(f[reason] 私有中间步骤(仅内部可见):{steps})return{answer:f关于「{state[question]}」的答案是42,# 输出字段thinking_steps:steps,# 私有字段token_cost:128,# 私有字段}节点返回的是一份增量字典。注意它同时返回了answer输出字段和thinking_steps/token_cost私有字段——在节点内部它们一视同仁区别只在「出口过滤」。5) 组装图三参数定乾坤builderStateGraph(OverallState,# 全局状态节点间共享input_schemaInputState,# 输入边界invoke 只接受这些键output_schemaOutputState,# 输出边界返回值只留这些键)builder.add_node(reason,reason_node)builder.add_edge(START,reason)builder.add_edge(reason,END)graphbuilder.compile()StateGraph的三个参数state_schema/input_schema/output_schema就是 MultiSchema 的全部开关。6) 运行 验证resultgraph.invoke({question:生命、宇宙和一切的终极答案是什么})assertthinking_stepsnotinresult# 私有字段不应出现asserttoken_costnotinresult# 私有字段不应出现assertresult{answer:关于「生命、宇宙和一切的终极答案是什么」的答案是42}四、运行结果 ① 输入调用方只提供 InputState 的字段question [reason] 收到输入 question 生命、宇宙和一切的终极答案是什么 [reason] 私有中间步骤(仅内部可见): [① 解析问题, ② 检索知识, ③ 生成答案] [reason] 私有成本记账 token_cost 128 ② 输出只返回 OutputState 的字段answer 私有字段 thinking_steps / token_cost 已被过滤不会出现在结果 最终结果: {answer: 关于「生命、宇宙和一切的终极答案是什么」的答案是42} 结果包含的键: [answer] ✅ 验证通过可以看到节点内部明明写进了thinking_steps/token_cost但result里只剩answer。这就是私有状态被自动隐藏的证据。五、知识点总结可单独摘成笔记三个 schema 参数state_schema位置参数全局状态节点共享的工作台。input_schema关键字约束invoke的入参。output_schema关键字约束invoke的返回值。继承组合OverallState(InputState, OutputState)用多重继承拼出全量状态且必须是前后两者的超集。入口校验传入input_schema之外的键 → 直接报错非法输入挡在门外。出口过滤返回值只保留output_schema中的键私有字段自动剥离。私有字段没有特殊语法所谓「私有」只是「在OverallState里、但不在InputState/OutputState里」的字段靠 schema 边界自然实现无需额外标注。节点返回增量所有节点都返回 dict 增量框架按 schema 合并带Annotated[..., reducer]的字段会走 reducer 累加不带则覆盖。六、适用场景 最佳实践✅对外暴露 API/SDK图是内部引擎输入/输出要干净内部状态要藏住。✅子图subgraph子图对外只暴露窄接口内部随便折腾。✅多入口/多出口不同调用场景需要不同的输入子集或输出子集时分别定义 schema。 图很简单、状态就两三个字段时单一 State 反而更直白别为了用而用。七、易踩的坑OverallState不是超集只要输出/输入里有字段不在全局里compile()直接抛错。误以为私有字段会被持久化返回调用方永远拿不到想调试只能print或在节点里落盘。混淆input_schema与默认值非输入字段如answer在入参里缺失是正常的靠节点产出即可不必在invoke时补齐。reducer 与 multi-schema 不冲突私有列表字段一样可以加Annotated[list, add_messages]只是它不参与对外输入输出。八、一句话总结LangGraph 的多 Schema 用OverallState兜住全部内部状态再用input_schema/output_schema把「门」开成只进question、只出answer中间那些草稿纸私有字段谁也看不见。附完整源码完整代码见src/langgraph/apidemo/demo_in_out_MultSchema.py纯逻辑、不调模型直接python demo_in_out_MultSchema.py即可运行。