LangGraph State / Config / Input / Output Schema 深度解析

LangGraph State / Config / Input / Output Schema 深度解析:为什么企业级 Agent 系统必须定义 Schema?

前言

在使用 LangGraph 开发工业级 Agent 时,随着系统复杂度的提升,我们经常会看到以下四个核心概念交织在一起:

  • State Schema(状态模式)
  • Configurable Schema / Context(运行时配置/上下文)
  • Input Schema(输入模式)
  • Output Schema(输出模式)

很多初学者会有疑问:"大模型本身就是处理松散 Dictionaries 的专家,为什么 LangGraph 要设计这么多繁琐的 Schema?直接传一个裸 dict 不行吗?"

事实上,这四个 Schema 并不是冗余的代码教条,而是 LangGraph 核心数据流设计的基础设施。它们共同构建了 Agent 在多节点协作、并发处理、状态保持与多租户隔离时的安全边界。本文将为你深度拆解这四种 Schema 的协作奥秘与落地实践。


一、 现代 Agent 的数据流模型

在 LangGraph 中,Graph(图)的本质是一个基于**通道(Channels)**的分布式状态机。数据在图中的流转路径如下:
#mermaid-svg-Sjvd70YakZj2duYR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Sjvd70YakZj2duYR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Sjvd70YakZj2duYR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Sjvd70YakZj2duYR .error-icon{fill:#552222;}#mermaid-svg-Sjvd70YakZj2duYR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Sjvd70YakZj2duYR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Sjvd70YakZj2duYR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Sjvd70YakZj2duYR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Sjvd70YakZj2duYR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Sjvd70YakZj2duYR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Sjvd70YakZj2duYR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Sjvd70YakZj2duYR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Sjvd70YakZj2duYR .marker.cross{stroke:#333333;}#mermaid-svg-Sjvd70YakZj2duYR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Sjvd70YakZj2duYR p{margin:0;}#mermaid-svg-Sjvd70YakZj2duYR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Sjvd70YakZj2duYR .cluster-label text{fill:#333;}#mermaid-svg-Sjvd70YakZj2duYR .cluster-label span{color:#333;}#mermaid-svg-Sjvd70YakZj2duYR .cluster-label span p{background-color:transparent;}#mermaid-svg-Sjvd70YakZj2duYR .label text,#mermaid-svg-Sjvd70YakZj2duYR span{fill:#333;color:#333;}#mermaid-svg-Sjvd70YakZj2duYR .node rect,#mermaid-svg-Sjvd70YakZj2duYR .node circle,#mermaid-svg-Sjvd70YakZj2duYR .node ellipse,#mermaid-svg-Sjvd70YakZj2duYR .node polygon,#mermaid-svg-Sjvd70YakZj2duYR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Sjvd70YakZj2duYR .rough-node .label text,#mermaid-svg-Sjvd70YakZj2duYR .node .label text,#mermaid-svg-Sjvd70YakZj2duYR .image-shape .label,#mermaid-svg-Sjvd70YakZj2duYR .icon-shape .label{text-anchor:middle;}#mermaid-svg-Sjvd70YakZj2duYR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Sjvd70YakZj2duYR .rough-node .label,#mermaid-svg-Sjvd70YakZj2duYR .node .label,#mermaid-svg-Sjvd70YakZj2duYR .image-shape .label,#mermaid-svg-Sjvd70YakZj2duYR .icon-shape .label{text-align:center;}#mermaid-svg-Sjvd70YakZj2duYR .node.clickable{cursor:pointer;}#mermaid-svg-Sjvd70YakZj2duYR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Sjvd70YakZj2duYR .arrowheadPath{fill:#333333;}#mermaid-svg-Sjvd70YakZj2duYR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Sjvd70YakZj2duYR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Sjvd70YakZj2duYR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Sjvd70YakZj2duYR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Sjvd70YakZj2duYR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Sjvd70YakZj2duYR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Sjvd70YakZj2duYR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Sjvd70YakZj2duYR .cluster text{fill:#333;}#mermaid-svg-Sjvd70YakZj2duYR .cluster span{color:#333;}#mermaid-svg-Sjvd70YakZj2duYR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Sjvd70YakZj2duYR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Sjvd70YakZj2duYR rect.text{fill:none;stroke-width:0;}#mermaid-svg-Sjvd70YakZj2duYR .icon-shape,#mermaid-svg-Sjvd70YakZj2duYR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Sjvd70YakZj2duYR .icon-shape p,#mermaid-svg-Sjvd70YakZj2duYR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Sjvd70YakZj2duYR .icon-shape .label rect,#mermaid-svg-Sjvd70YakZj2duYR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Sjvd70YakZj2duYR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Sjvd70YakZj2duYR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Sjvd70YakZj2duYR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 写入/初始化
2. 只读注入
3. 更新部分通道
4. 更新部分通道
5. 裁剪过滤
External Input
Shared State
Configurable Context
Node A
Node B
External Output

简单来说:输入数据初始化全局状态 →\rightarrow→ 各节点读取状态并返回增量补丁 →\rightarrow→ 状态机依据 Schema 规则合并(Reducer)状态 →\rightarrow→ 最终过滤出对外输出。


二、 State Schema:图的全局动态内存

1. 什么是 State Schema

State Schema 定义了整个 Graph 的共享状态空间。它是图中所有 Node(节点)共同维系的"单一事实来源(Single Source of Truth)"。

2. 代码实现与自动合并(Reducer)

python 复制代码
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages

# 企业级实践中,通常结合 Annotated 与 Reducer 函数来控制状态如何合并
class AgentState(TypedDict):
    question: str
    intent: str
    # 使用 add_messages 机制自动追加消息流,而不是覆盖
    messages: Annotated[list, add_messages]
    internal_logs: list[str]

3. 为什么必须定义 State Schema?

  • 规避隐式拼写 Bug :在一个包含数十个节点的复杂 Agent 中,如果没有强类型约束,某个节点误将 state["intent"] 写成了 state["inteent"],动态类型语言(Python)不会在写入时报错,但下游依赖 intent 字段的节点将由于拿不到数据而发生灾难性崩溃。
  • 定义合并行为(Reducers) :Schema 不仅约束了字段类型,还声明了数据冲突时的合并策略。例如,messages 字段通过 add_messages 声明了它是 Append-only(追加模式),而 intent 则是 Overwrite(覆盖模式)。

三、 Configurable Schema (Context):隔离的运行时上下文

1. 什么是运行时配置/上下文

在开发多租户(Multi-tenant)或需要动态权限校验的企业 Agent 时,有些数据是节点只读 的,且绝对不应该在节点运行过程中被大模型修改。例如:当前请求的用户 ID、商户租户 ID、接口 API Key、当前语言环境(Locale)等。

在 LangGraph 中,这类只读的运行时上下文通过 RunnableConfigconfigurable 属性 来承载,而不是污染全局 State

2. 代码实现规范

python 复制代码
from pydantic import BaseModel, Field
from langchain_core.runnables import RunnableConfig

# 1. 定义可配置的上下文 Schema
class RuntimeContext(BaseModel):
    user_id: str = Field(description="当前请求的用户唯一标识")
    tenant_id: str = Field(description="多租户隔离的商户ID")
    language: str = Field(default="zh-CN")

# 2. 在 Node 中通过标准 RunnableConfig 读取
def generation_node(state: AgentState, config: RunnableConfig):
    # 安全地从封装好的 config 中提取上下文,节点无法反向篡改这些值
    configurable = RuntimeContext(**config.get("configurable", {}))
    
    user_lang = configurable.language
    # 依据外部传入的语言环境,动态调整 Prompt
    return {"messages": [("assistant", f"Current language context: {user_lang}")]}

四、 Input 与 Output Schema:对外的 API 契约

在 LangGraph 早期版本中,图的输入、输出、状态使用的是同一个 Schema。但这在生产环境中暴露出巨大的工程隐患。最新版 LangGraph 强化了 Input SchemaOutput Schema 的分离。

为什么必须把 Input / Output 与 State 分开?

这可以完美对齐经典软件工程中 DTO(数据传输对象)Domain Model(领域模型) 隔离的设计思想:

  • Input Schema(外部可见的输入) :对客户端而言,调用 Agent 只需要传入 {"question": "北京天气"}。客户端不需要、也不应该知道系统内部还有 intentretrieved_docs 等中间变量。
  • Output Schema(外部可见的输出) :系统内部为了让大模型链式思考,会在 State 中记录大量的 internal_logs(如 CoT 思考链、原始 Tool 响应、Prompt 调试信息)。这些数据不仅体积庞大,还可能包含敏感信息。通过 Output Schema,我们可以强制图在最后一步进行字段裁剪 ,只给客户端返回 {"answer": "今天晴天"}

五、 四大 Schema 协同作战:企业级 Agent 标准设计模式

以下是一个标准的工业级多路由 Agent 骨架代码,完整展现了四种 Schema 的分工与联动:

python 复制代码
from typing import TypedDict, Optional
from pydantic import BaseModel
from langgraph.graph import StateGraph, START, END
from langchain_core.runnables import RunnableConfig

# ==========================================
# 1. SCHEMA 定义层 (各司其职)
# ==========================================

# 外部输入:极其精简
class GraphInput(TypedDict):
    question: str

# 外部输出:隐藏了内部思考细节与中间变量
class GraphOutput(TypedDict):
    answer: str

# 全局内部状态:包含中间件、召回文档、意图等
class GraphState(TypedDict):
    question: str
    intent: Optional[str]
    retrieved_docs: list[str]
    answer: Optional[str]

# 运行时上下文:环境与多租户权限配置
class GraphContext(BaseModel):
    user_id: str
    experiment_group: str  # A/B 测试分组

# ==========================================
# 2. NODE 节点实现层
# ==========================================

def retrieval_node(state: GraphState, config: RunnableConfig) -> dict:
    # 读取只读上下文进行 A/B 测试策略路由
    context = GraphContext(**config.get("configurable", {}))
    if context.experiment_group == "v2_embedding":
        docs = ["Retrieved from Vector DB v2"]
    else:
        docs = ["Retrieved from Vector DB v1"]
        
    return {"retrieved_docs": docs}

def generator_node(state: GraphState) -> dict:
    # 读取 state 中的 question 和 retrieved_docs,生成最终的 answer
    raw_question = state["question"]
    docs_context = ",".join(state["retrieved_docs"])
    
    return {"answer": f"基于文档【{docs_context}】,回答:{raw_question} 的结果为晴。"}

# ==========================================
# 3. GRAPH 编排层
# ==========================================

# 显式将 input, output, state 进行三权分立
workflow = StateGraph(
    state_schema=GraphState,
    input_schema=GraphInput,
    output_schema=GraphOutput
)

workflow.add_node("retrieval_agent", retrieval_node)
workflow.add_node("generator_agent", generator_node)

workflow.add_edge(START, "retrieval_agent")
workflow.add_edge("retrieval_agent", "generator_agent")
workflow.add_edge("generator_agent", END)

app = workflow.compile()

运行时调用机制

当外部客户端触发该 Agent 时的完整数据剪裁过程如下:

python 复制代码
# 客户端调用:严格按照 Input Schema 与 Config 传参
runtime_config = {"configurable": {"user_id": "usr_995", "experiment_group": "v2_embedding"}}
initial_input = {"question": "上海明天的天气"}

final_result = app.invoke(initial_input, config=runtime_config)

# 打印最终输出
print(final_result)
# 输出完全符合 GraphOutput 的契约,内部的 `retrieved_docs` 已经被自动裁剪:
# {'answer': '基于文档【Retrieved from Vector DB v2】,回答:上海明天的天气 的结果为晴。'}

六、 总结与核心架构思维导图

在 LangGraph 架构体系中,四种 Schema 构建了清晰的系统边界:

Schema 类型 面向对象 读写权限 核心白话解释
Input Schema 外部调用方 客户端只写 / 图初始化只读 用户带了什么东西进来
Configurable (Context) 基础设施/平台层 节点内绝对只读 系统提供了什么运行环境(安全与隔离)
State Schema 内部 Agent 节点 节点内可读、可写、可追加 Agent 链式运行过程中记住了什么
Output Schema 外部接收方 最终状态裁剪后输出 系统最终需要把什么呈现给用户

💡 专家建议

在写简单 Demo 时,你固然可以只定义一个 State 包揽所有字段。但只要项目进入生产环境,涉及到前后端解耦、A/B 测试、多租户安全隔离、灰度发布 等硬核指标时,遵循 Input + Config + State + Output 的四权分立架构设计,是确保 Agent 系统具有高健壮性、高可测试性与高可维护性的唯一解。

复制代码
相关推荐
茉莉玫瑰花茶3 天前
综合案例 - AI 智能租房助手 [ 4 ]
数据库·python·ai·langgraph
眠りたいです4 天前
LangChainv1:agent快速上手与中间件认识
人工智能·python·中间件·langchain·langgraph
张彦峰ZYF5 天前
LangGraph Tool Calling 入门:从 @tool 到完整调用链
人工智能·大模型·agent·langgraph·tool calling
骑士雄师5 天前
13.如何根据Rannable对象创建工具
langgraph
Irissgwe6 天前
十、LangGraph能力详解:工作流的常见模式
python·langchain·ai编程·工作流·langgraph
Irissgwe6 天前
十、LangGraph能力详解:LangGraph 的其他特性
python·ai·langchain·langgraph
张彦峰ZYF7 天前
LangGraph 条件边:让 AI Agent 学会“做选择”
人工智能·大模型·langgraph
装不满的克莱因瓶8 天前
学习 LCEL 表达式:降低 LLM 应用开发难度
人工智能·ai·langchain·agent·智能体·lcel·langgraph
Irissgwe9 天前
【无标题】
python·langchain·ai编程·langgraph