
部署总报错?你可能连LangGraph的"身份证"都没写对!一文拆解langgraph.json所有暗坑,让你的Agent从本地跑通到云端丝滑上线。这篇内容把平台配置文件的每个字段掰开了、揉碎了讲,看完你就能明白:为什么代码在本地跑得欢,一上云就摆烂——问题八成出在这个小小的JSON里。
#mermaid-svg-YA4fSgmvom0NDjhf{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-YA4fSgmvom0NDjhf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YA4fSgmvom0NDjhf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YA4fSgmvom0NDjhf .error-icon{fill:#552222;}#mermaid-svg-YA4fSgmvom0NDjhf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YA4fSgmvom0NDjhf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YA4fSgmvom0NDjhf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YA4fSgmvom0NDjhf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YA4fSgmvom0NDjhf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YA4fSgmvom0NDjhf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YA4fSgmvom0NDjhf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YA4fSgmvom0NDjhf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YA4fSgmvom0NDjhf .marker.cross{stroke:#333333;}#mermaid-svg-YA4fSgmvom0NDjhf svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YA4fSgmvom0NDjhf p{margin:0;}#mermaid-svg-YA4fSgmvom0NDjhf .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-YA4fSgmvom0NDjhf .cluster-label text{fill:#333;}#mermaid-svg-YA4fSgmvom0NDjhf .cluster-label span{color:#333;}#mermaid-svg-YA4fSgmvom0NDjhf .cluster-label span p{background-color:transparent;}#mermaid-svg-YA4fSgmvom0NDjhf .label text,#mermaid-svg-YA4fSgmvom0NDjhf span{fill:#333;color:#333;}#mermaid-svg-YA4fSgmvom0NDjhf .node rect,#mermaid-svg-YA4fSgmvom0NDjhf .node circle,#mermaid-svg-YA4fSgmvom0NDjhf .node ellipse,#mermaid-svg-YA4fSgmvom0NDjhf .node polygon,#mermaid-svg-YA4fSgmvom0NDjhf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YA4fSgmvom0NDjhf .rough-node .label text,#mermaid-svg-YA4fSgmvom0NDjhf .node .label text,#mermaid-svg-YA4fSgmvom0NDjhf .image-shape .label,#mermaid-svg-YA4fSgmvom0NDjhf .icon-shape .label{text-anchor:middle;}#mermaid-svg-YA4fSgmvom0NDjhf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YA4fSgmvom0NDjhf .rough-node .label,#mermaid-svg-YA4fSgmvom0NDjhf .node .label,#mermaid-svg-YA4fSgmvom0NDjhf .image-shape .label,#mermaid-svg-YA4fSgmvom0NDjhf .icon-shape .label{text-align:center;}#mermaid-svg-YA4fSgmvom0NDjhf .node.clickable{cursor:pointer;}#mermaid-svg-YA4fSgmvom0NDjhf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YA4fSgmvom0NDjhf .arrowheadPath{fill:#333333;}#mermaid-svg-YA4fSgmvom0NDjhf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YA4fSgmvom0NDjhf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YA4fSgmvom0NDjhf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YA4fSgmvom0NDjhf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YA4fSgmvom0NDjhf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YA4fSgmvom0NDjhf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YA4fSgmvom0NDjhf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YA4fSgmvom0NDjhf .cluster text{fill:#333;}#mermaid-svg-YA4fSgmvom0NDjhf .cluster span{color:#333;}#mermaid-svg-YA4fSgmvom0NDjhf 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-YA4fSgmvom0NDjhf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YA4fSgmvom0NDjhf rect.text{fill:none;stroke-width:0;}#mermaid-svg-YA4fSgmvom0NDjhf .icon-shape,#mermaid-svg-YA4fSgmvom0NDjhf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YA4fSgmvom0NDjhf .icon-shape p,#mermaid-svg-YA4fSgmvom0NDjhf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YA4fSgmvom0NDjhf .icon-shape .label rect,#mermaid-svg-YA4fSgmvom0NDjhf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YA4fSgmvom0NDjhf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YA4fSgmvom0NDjhf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YA4fSgmvom0NDjhf :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-YA4fSgmvom0NDjhf .root>*{fill:#ff9999!important;stroke:#cc0000!important;stroke-width:3px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .root span{fill:#ff9999!important;stroke:#cc0000!important;stroke-width:3px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .root tspan{fill:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .blue>*{fill:#99ccff!important;stroke:#0066cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .blue span{fill:#99ccff!important;stroke:#0066cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .blue tspan{fill:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .green>*{fill:#99ff99!important;stroke:#009900!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .green span{fill:#99ff99!important;stroke:#009900!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .green tspan{fill:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .yellow>*{fill:#ffff99!important;stroke:#cccc00!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .yellow span{fill:#ffff99!important;stroke:#cccc00!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .yellow tspan{fill:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .purple>*{fill:#cc99ff!important;stroke:#6600cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .purple span{fill:#cc99ff!important;stroke:#6600cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .purple tspan{fill:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .orange>*{fill:#ffcc99!important;stroke:#cc6600!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .orange span{fill:#ffcc99!important;stroke:#cc6600!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-YA4fSgmvom0NDjhf .orange tspan{fill:#000!important;}
langgraph.json配置文件全景图
graphs字段
dependencies字段
env字段
docker与node字段
http字段
避坑与最佳实践
Agent注册与入口
pip包与本地包
环境变量注入
自定义Dockerfile
FastAPI扩展
六脉神剑检查法
本文目录:
嗨,大家好呀,我是你的老朋友精通代码大仙。接下来我们一起学习 《LangChain核心技术与LLM项目实践》。
“饭要一口一口吃,代码要一行一行敲,但配置文件要是写错一行,前面一千行代码全白干。”
这话搁在LangGraph平台上,简直不能再贴切了。你是不是也这样?本地Jupyter里Agent跑得虎虎生风,多轮对话、工具调用、条件边跳转,逻辑丝滑得像德芙。结果信心满满地往LangGraph平台上一推,报错信息密密麻麻,看得人头皮发麻。回去查了半天业务代码,没问题啊!最后发现,原来是 langgraph.json 里一个路径写歪了,或者一个逗号多余了,或者依赖忘声明了。
别笑,这种"配置刺客"坑过的新手,能从北京排到广州。今天咱就把这个 langgraph.json 的底裤给扒了,把它每个字段的脾气、喜好、雷区,都聊个明明白白。争取让你看完这篇,以后写配置跟写Python一样顺手。
1. graphs字段:给你的Agent办一张"云端身份证"
点题
graphs 是整个 langgraph.json 的灵魂,没有它,平台根本不知道该把你的哪个Agent挂出去提供服务。它是一个字典,key是你给这个图起的名字(这个名字会直接影响你后续调用API时的路径),value是一个字符串,格式必须是 "模块导入路径:变量名"。这个变量名指向的,必须是一个已经编译好的 CompiledGraph 实例。
换句话说,这就是你的Agent在LangGraph云平台上的"身份证号码"。你代码里写得再天花乱坠,平台只认这个字段指明的入口。
#mermaid-svg-P9JSxEAjqVHzwOHE{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-P9JSxEAjqVHzwOHE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P9JSxEAjqVHzwOHE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P9JSxEAjqVHzwOHE .error-icon{fill:#552222;}#mermaid-svg-P9JSxEAjqVHzwOHE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P9JSxEAjqVHzwOHE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P9JSxEAjqVHzwOHE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P9JSxEAjqVHzwOHE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P9JSxEAjqVHzwOHE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P9JSxEAjqVHzwOHE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P9JSxEAjqVHzwOHE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P9JSxEAjqVHzwOHE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P9JSxEAjqVHzwOHE .marker.cross{stroke:#333333;}#mermaid-svg-P9JSxEAjqVHzwOHE svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P9JSxEAjqVHzwOHE p{margin:0;}#mermaid-svg-P9JSxEAjqVHzwOHE .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-P9JSxEAjqVHzwOHE .cluster-label text{fill:#333;}#mermaid-svg-P9JSxEAjqVHzwOHE .cluster-label span{color:#333;}#mermaid-svg-P9JSxEAjqVHzwOHE .cluster-label span p{background-color:transparent;}#mermaid-svg-P9JSxEAjqVHzwOHE .label text,#mermaid-svg-P9JSxEAjqVHzwOHE span{fill:#333;color:#333;}#mermaid-svg-P9JSxEAjqVHzwOHE .node rect,#mermaid-svg-P9JSxEAjqVHzwOHE .node circle,#mermaid-svg-P9JSxEAjqVHzwOHE .node ellipse,#mermaid-svg-P9JSxEAjqVHzwOHE .node polygon,#mermaid-svg-P9JSxEAjqVHzwOHE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P9JSxEAjqVHzwOHE .rough-node .label text,#mermaid-svg-P9JSxEAjqVHzwOHE .node .label text,#mermaid-svg-P9JSxEAjqVHzwOHE .image-shape .label,#mermaid-svg-P9JSxEAjqVHzwOHE .icon-shape .label{text-anchor:middle;}#mermaid-svg-P9JSxEAjqVHzwOHE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P9JSxEAjqVHzwOHE .rough-node .label,#mermaid-svg-P9JSxEAjqVHzwOHE .node .label,#mermaid-svg-P9JSxEAjqVHzwOHE .image-shape .label,#mermaid-svg-P9JSxEAjqVHzwOHE .icon-shape .label{text-align:center;}#mermaid-svg-P9JSxEAjqVHzwOHE .node.clickable{cursor:pointer;}#mermaid-svg-P9JSxEAjqVHzwOHE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P9JSxEAjqVHzwOHE .arrowheadPath{fill:#333333;}#mermaid-svg-P9JSxEAjqVHzwOHE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P9JSxEAjqVHzwOHE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P9JSxEAjqVHzwOHE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P9JSxEAjqVHzwOHE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P9JSxEAjqVHzwOHE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P9JSxEAjqVHzwOHE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P9JSxEAjqVHzwOHE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P9JSxEAjqVHzwOHE .cluster text{fill:#333;}#mermaid-svg-P9JSxEAjqVHzwOHE .cluster span{color:#333;}#mermaid-svg-P9JSxEAjqVHzwOHE 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-P9JSxEAjqVHzwOHE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P9JSxEAjqVHzwOHE rect.text{fill:none;stroke-width:0;}#mermaid-svg-P9JSxEAjqVHzwOHE .icon-shape,#mermaid-svg-P9JSxEAjqVHzwOHE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P9JSxEAjqVHzwOHE .icon-shape p,#mermaid-svg-P9JSxEAjqVHzwOHE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P9JSxEAjqVHzwOHE .icon-shape .label rect,#mermaid-svg-P9JSxEAjqVHzwOHE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P9JSxEAjqVHzwOHE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P9JSxEAjqVHzwOHE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P9JSxEAjqVHzwOHE :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-P9JSxEAjqVHzwOHE .blue>*{fill:#99ccff!important;stroke:#0066cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-P9JSxEAjqVHzwOHE .blue span{fill:#99ccff!important;stroke:#0066cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-P9JSxEAjqVHzwOHE .blue tspan{fill:#000!important;}#mermaid-svg-P9JSxEAjqVHzwOHE .green>*{fill:#99ff99!important;stroke:#009900!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-P9JSxEAjqVHzwOHE .green span{fill:#99ff99!important;stroke:#009900!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-P9JSxEAjqVHzwOHE .green tspan{fill:#000!important;}
graphs字段
key: customer_support
key: data_analyst
./src/support.py:graph
./src/analyst.py:workflow
痛点分析
新手在这个字段上栽的跟头,主要集中在三个地方。
第一,路径写错。很多人本地开发习惯直接 python agent.py 跑,文件放哪儿都无所谓。但LangGraph平台的加载机制,本质上是从项目根目录做模块导入。你写 "./agent.py:graph",结果文件实际是 ./src/agent.py,平台一启动就给你抛一个找不到模块的错误。还有人在Windows上开发,路径大小写不敏感,写成 "./Agent.py:graph",本地跑得欢,一上Linux服务器,直接挂。
第二,变量名对不上。你在代码里可能写的是 workflow = StateGraph(…),然后 app = workflow.compile(),结果配置文件里心血来潮写了个 "agent.py:graph"。平台会去 agent.py 里找一个叫 graph 的变量,找不到就傻眼了。更隐蔽的是,有人返回的是 StateGraph 构建器,忘了调用 compile(),平台拿到一个未编译的图,运行时各种诡异行为。
第三,相对导入搞崩心态。你的 agent.py 里如果用了 from .utils import some_tool,那整个文件必须处于一个合法的Python包结构里。平台在导入 ./agent.py 时,如果周围缺少 __init__.py 或者包根目录没处理好,相对导入会直接炸。
看看这个典型的错误配置:
{
"graphs": {
"my_agent": "agent.py:graph"
}
}
发现问题了吗?路径没加 ./,在某些解析场景下,平台可能把它当成一个第三方包名去import,而不是本地文件。再看代码里:
from langgraph.graph import StateGraph
from .utils import tool # 相对导入!
builder = StateGraph(...)
builder.add_node("node1", tool)
app = builder.compile()
配置里写的是 agent.py:graph,但代码里变量叫 app,而且用了相对导入。这部署上去,报错信息能让你怀疑人生。
解决方案/正确做法
首先,命名要老实。图编译完了,变量名就规范一点,叫 graph 挺好,别整那些 app、workflow_final_v2 之类的花活,除非你记得住。
其次,路径要用从 langgraph.json 所在目录出发的相对路径,并且建议明确加上 ./。项目结构要清晰:
my_project/
├── langgraph.json
├── .env
└── src/
├── __init__.py
├── agent.py
└── utils.py
agent.py 里的代码:
from langgraph.graph import StateGraph
from .utils import tool # 包内相对导入,合法
builder = StateGraph(...)
builder.add_node("node1", tool)
builder.add_edge("node1", "end")
graph = builder.compile() # 明确编译,变量名对齐
对应的 langgraph.json:
{
"graphs": {
"my_agent": "./src/agent.py:graph"
}
}
如果项目简单,不想搞包结构,那就全放根目录,用平级导入,避免相对导入的麻烦:
from langgraph.graph import StateGraph
from utils import tool # 同级导入
builder = StateGraph(...)
graph = builder.compile()
{
"graphs": {
"my_agent": "./agent.py:graph"
}
}
这样做的好处是,平台导入机制和你本地 python agent.py 的行为高度一致,排查问题时脑子不用切换上下文。而且一个项目完全可以注册多个图,比如一个客服Agent、一个数据分析Agent,各自独立升级:
{
"graphs": {
"customer_support": "./src/support.py:graph",
"data_analyst": "./src/analyst.py:graph"
}
}
小结
graphs 字段是LangGraph平台认识你的唯一入口。路径要对,变量名要准,图一定要是编译后的实例。这就像给Agent办身份证,照片和姓名错一个,安检都过不了。
2. dependencies字段:依赖管理的"生死簿"
点题
代码再牛,缺少依赖包,跑到云端就是一个光杆司令。dependencies 字段就是告诉LangGraph平台:"我这个项目需要这些第三方库,麻烦你在容器里给我装上。"这个字段支持多种形式,最常见的是字符串数组,每个元素可以是一个具体的包名加版本号,也可以是一个依赖文件(如 requirements.txt)。
痛点分析
新手在这个字段上的迷惑行为,堪称人类多样性观察样本。
第一种,重复造轮子式手动搬运。本地明明有 requirements.txt,里面写得整整齐齐,新手愣是把每个包重新手打到 langgraph.json 里。结果两边版本对不上,本地跑版本A,云端装版本B,行为不一致,排查到吐血。
第二种,格式乱炖。有人把 requirements.txt 的内容原样粘贴进JSON数组:
{
"dependencies": [
"langgraph==0.1.0\\nopenai==1.0.0\\npandas==2.0.0"
]
}
平台一看,这是一个包含换行符的字符串,当成一个包名去装,怎么可能成功?
第三种,本地包引用一脸懵。你写了一个自定义工具包放在 ./packages/my_tools,主项目依赖它。结果你在 dependencies 里写 "./packages/my_tools",平台不认识,因为它需要特定的对象格式来声明本地路径依赖。
第四种,开发依赖生产依赖不分。把 pytest、black、jupyter 全塞进去,构建镜像时慢得像乌龟爬,还可能因为某些开发包的子依赖冲突导致安装失败。
解决方案/正确做法
简单项目,直接用字符串数组最稳。平台支持在数组里直接引用 requirements.txt,这是我最推荐的写法,避免维护两份依赖清单:
{
"dependencies": [
"-r requirements.txt"
]
}
你的 requirements.txt 就按老规矩写:
langgraph>=0.1.0,<0.2.0
langchain-openai>=0.1.0
pandas>=2.0.0
python-dotenv>=1.0.0
如果你不想用文件,或者需要更精细的控制,直接写数组也行,但记得锁版本号,别裸写 langgraph,要写成 langgraph>=0.1.0 这种形式,防止未来版本 breaking change 把你埋了:
{
"dependencies": [
"langgraph>=0.1.0",
"langchain-openai>=0.1.0",
"pandas>=2.0.0"
]
}
对于有本地包的场景,可以使用对象形式(具体取决于你使用的LangGraph平台版本,但核心思路一致):
{
"dependencies": {
"pip": [
"langgraph>=0.1.0",
"openai>=1.0.0"
],
"local": [
"./packages/my_tools"
]
}
}
这里的关键是,local 指向的目录里,必须有一个合法的 setup.py 或 pyproject.toml,让 pip 能以 editable 或普通模式安装它。别只是一个光秃秃的 .py 文件。
另外,检查一下,你的依赖里真的需要 pytest 吗?不需要就删掉。云端运行只关心能让你的Agent活起来的包,其他的都是噪音。
小结
依赖声明是部署的粮草。能用 requirements.txt 统一管理就别手抄,版本号该锁就锁,本地包确保是可安装的合法包。粮草不足,再好的Agent也得饿死在半路。
3. env字段:环境变量的"暗号本"
点题
大模型应用离不开API Key、数据库连接串、第三方服务令牌。这些东西打死都不能硬编码进代码里。env 字段就是告诉LangGraph平台:“去这个文件里找环境变量,然后注入到运行环境里。” 通常它指向项目根目录下的一个 .env 文件。
痛点分析
这个字段看似简单,但新手在这里犯的错,往往带着一种"我以为这样就行"的自信。
第一种错误,路径对不上。你明明把环境变量写在了 .env.local 里,结果 langgraph.json 里写的是 "env": ".env"。平台找不到文件,自然注入了个寂寞,你的代码里 os.getenv("OPENAI_API_KEY") 返回 None,调用模型时直接401。
第二种错误,格式带引号。有些同学习惯在 .env 文件里这样写:
OPENAI_API_KEY="sk-1234567890"
这在某些解析器眼里,那对英文双引号会被当成值的一部分。结果你拿到的Key变成了 "sk-1234567890"(带引号),发给OpenAI,人家不认。
第三种错误,也是最严重的错误——硬编码。本地测试图省事,直接把Key写在代码里:
import os
# 千万别这样!!!
api_key = "sk-abcdef123456"
这要是提交到Git,再部署到平台,等于把你的密钥贴在电线杆上。
还有一种情况:以为写了 env 字段,本地开发就不用管了。结果本地直接跑 python agent.py,发现 os.getenv 啥也读不到,因为本地并没有自动加载 .env 文件,那是平台部署时的行为。
解决方案/正确做法
首先,.env 文件的格式要干净,不要加引号:
OPENAI_API_KEY=sk-1234567890
DATABASE_URL=postgresql://user:pass@host/db
LANGCHAIN_TRACING_V2=true
然后,langgraph.json 里明确指向它:
{
"env": ".env"
}
如果你的环境文件放在子目录里,比如 config/.env,那就写 "env": "config/.env",但通常建议放根目录,简单直接。
代码里要做兼容处理,让本地开发和云端部署都能跑通:
import os
from dotenv import load_dotenv
# 本地开发时加载.env文件
# 云端平台会自动注入环境变量,这行有则加载,无则跳过
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("OPENAI_API_KEY 没设置!检查一下.env文件和langgraph.json配置。")
# 后续正常使用
最后,也是最关键的一步:确保 .env 文件在你的 .gitignore 里!这是底线,红线,高压线!
.env
.env.local
这样做的好处是,你的代码仓库永远是干净的,密钥只在本地和云端运行时存在。平台通过 env 字段读取并注入,代码通过 os.getenv 读取,两边配合天衣无缝。
小结
env 字段是你的保险箱,.env 文件是暗号本。路径要对,格式要纯,代码要读,Git要 ignore。密钥安全无小事,一次泄露,全家桶换Key换到你手软。
4. docker与node字段:运行环境的"私人订制"
点题
LangGraph平台默认会给你一个容器环境跑你的Agent。但有时候,默认环境不够用。比如你的某个Python库需要系统级的 gcc 编译器,或者你需要用 playwright 调浏览器,又或者你的团队对Python版本有硬性要求(必须是3.11)。这时候,docker 字段允许你指定一个自定义的 Dockerfile,而 node 字段则用于指定Node.js运行时版本(如果你的项目涉及JS/TS前端或自定义服务)。
痛点分析
很多新手对这个字段的态度是两个极端:要么完全无视,遇到环境报错就抓瞎;要么过度定制,写一个巨复杂的 Dockerfile,把平台原本帮你做好的事也抢了。
典型的错误案例一:基于一个不合适的镜像从头造轮子。
FROM python:3.9-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "agent.py"]
这里面有几个问题。首先,CMD 完全是多余的,LangGraph平台有自己的进程管理和启动逻辑,不需要你指定容器启动命令。其次,如果你代码里用了Python 3.10的 match-case 语法,3.9的环境会直接语法错误。再者,slim 镜像缺少很多系统库,某些依赖(比如含C扩展的包)编译不过。
典型的错误案例二:乱加 node 字段。明明是一个纯Python后端项目,看了某篇教程里有个 node 配置,也跟着写:
{
"node": {
"version": "18"
}
}
这不会让你的Python跑得更欢,反而可能让平台误解你的项目类型,增加不必要的构建步骤。
还有一种痛:路径问题。dockerfile 的路径是相对于 langgraph.json 的,有人写成绝对路径 "dockerfile": "/home/user/project/Dockerfile",或者文件放在 ./docker/Dockerfile,配置里却写 "./Dockerfile",构建时找不到文件。
解决方案/正确做法
如果你不需要特殊系统依赖,最好的做法就是:不写 docker 字段。默认环境通常基于较新的Python版本,对LangGraph生态支持最好。
但如果你确实需要自定义,比如要装一个系统依赖,那就基于官方推荐的基础镜像来写:
FROM langchain/langgraph-api:latest
# 或者明确指定python版本:FROM python:3.11-slim
WORKDIR /app
# 安装系统级依赖(按需)
RUN apt-get update && apt-get install -y gcc libpq-dev
COPY . .
# 如果langgraph.json里没有完全声明依赖,可以在这里补装
# 但推荐统一在langgraph.json里管理
RUN pip install –no-cache-dir -e .
对应的 langgraph.json:
{
"docker": {
"dockerfile": "./Dockerfile"
}
}
注意,这里没有 CMD,没有 ENTRYPOINT,平台会接管启动。你的职责只是准备好环境、把代码放进去。
对于 node 字段,纯Python项目请直接忽略它。只有当你确实需要Node.js运行时(比如你的项目包含一个需要编译的React前端,或者你的自定义HTTP服务是基于Node的),才考虑配置。别让配置文件变成"大杂烩"。
小结
运行环境是地基。默认够用就别折腾,折腾就要把 Dockerfile 写扎实。记住,平台才是房东,你只需要把房子装修好,别抢房东的钥匙。
5. http字段:API门面的"装修手册"
点题
LangGraph平台部署成功后,会自动暴露一套REST API,让你能通过HTTP请求触发你的Agent。但有时候,你想在这个基础上扩展一些东西,比如加一个 /health 健康检查接口,或者加一个业务相关的 /webhook 接收第三方回调。http 字段就是干这个的,它允许你挂载一个自定义的FastAPI应用(或其他ASGI兼容应用),与LangGraph的原生接口共存。
痛点分析
新手对这个字段最大的误解,是以为不写它就没有API,于是拼命在代码里自己起一个HTTP服务器。
看看这个令人窒息的操作:
# agent.py 的末尾
if __name__ == "__main__":
import uvicorn
from fastapi import FastAPI
app = FastAPI()
uvicorn.run(app, host="0.0.0.0", port=8000)
然后配上这样的 langgraph.json:
{
"graphs": {
"agent": "./agent.py:graph"
},
"http": {
"app": "./agent.py:app"
}
}
这会导致什么?平台本身已经是一个HTTP服务器了,它去导入你的模块时,一碰到 uvicorn.run() 就试图绑定端口。端口冲突、启动失败、容器崩溃,一条龙服务。
还有一种错误:挂载的应用类型不对。http.app 需要的是一个ASGI应用实例(通常是FastAPI)。有人写了一个Flask应用传进去:
from flask import Flask
app = Flask(__name__)
Flask是WSGI,不是ASGI,平台挂载时会直接报错。
另外,assistant_id 等子字段如果用错了场景,也会导致默认路由行为异常。比如你想指定默认助手,但拼写错误写成 assistand_id,平台无视,你调用时还得显式传ID。
解决方案/正确做法
先在心里默念三遍:平台本身就是服务器,我不需要再起一个!
如果你不需要自定义接口,那么 langgraph.json 里完全可以不出现 http 字段。原生REST API足够你用了。
如果你确实需要扩展,单独写一个API文件,保持职责清晰:
# custom_api.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "langgraph-agent"}
@app.post("/webhook")
async def receive_webhook(payload: dict):
# 处理第三方回调
return {"received": True}
然后在 langgraph.json 里引用:
{
"graphs": {
"agent": "./agent.py:graph"
},
"http": {
"app": "./custom_api.py:app"
}
}
这样,你的Agent接口(如 /threads/{thread_id}/runs)和自定义接口(/health、/webhook)就能在同一个域名下共存,互不影响。
如果你的配置里还需要指定默认的 assistant_id,确保拼写正确:
{
"http": {
"app": "./custom_api.py:app",
"assistant_id": "my-default-agent"
}
}
但大多数情况下,新手先别碰这个字段,把默认API用熟再说。
小结
http 字段是高级装修,没需求就留空,有需求就确保你的 app 是标准FastAPI实例,千万别在代码里重复启动服务器。门面要大气,但不能把承重墙砸了。
6. 避坑急诊室:配置检查的"六脉神剑"
点题
前面我们把五个核心字段单独拉出来聊了,但在实战中,真正的失败往往不是某一个字段的锅,而是各种细节凑在一起引发的"复合型雪崩"。这一节,我把新手最容易忽略的六个综合陷阱集中曝光,并给你一份可直接落地的检查清单。
#mermaid-svg-LNH850SPnTVuEIGU{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-LNH850SPnTVuEIGU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LNH850SPnTVuEIGU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LNH850SPnTVuEIGU .error-icon{fill:#552222;}#mermaid-svg-LNH850SPnTVuEIGU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LNH850SPnTVuEIGU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LNH850SPnTVuEIGU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LNH850SPnTVuEIGU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LNH850SPnTVuEIGU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LNH850SPnTVuEIGU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LNH850SPnTVuEIGU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LNH850SPnTVuEIGU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LNH850SPnTVuEIGU .marker.cross{stroke:#333333;}#mermaid-svg-LNH850SPnTVuEIGU svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LNH850SPnTVuEIGU p{margin:0;}#mermaid-svg-LNH850SPnTVuEIGU .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LNH850SPnTVuEIGU .cluster-label text{fill:#333;}#mermaid-svg-LNH850SPnTVuEIGU .cluster-label span{color:#333;}#mermaid-svg-LNH850SPnTVuEIGU .cluster-label span p{background-color:transparent;}#mermaid-svg-LNH850SPnTVuEIGU .label text,#mermaid-svg-LNH850SPnTVuEIGU span{fill:#333;color:#333;}#mermaid-svg-LNH850SPnTVuEIGU .node rect,#mermaid-svg-LNH850SPnTVuEIGU .node circle,#mermaid-svg-LNH850SPnTVuEIGU .node ellipse,#mermaid-svg-LNH850SPnTVuEIGU .node polygon,#mermaid-svg-LNH850SPnTVuEIGU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LNH850SPnTVuEIGU .rough-node .label text,#mermaid-svg-LNH850SPnTVuEIGU .node .label text,#mermaid-svg-LNH850SPnTVuEIGU .image-shape .label,#mermaid-svg-LNH850SPnTVuEIGU .icon-shape .label{text-anchor:middle;}#mermaid-svg-LNH850SPnTVuEIGU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LNH850SPnTVuEIGU .rough-node .label,#mermaid-svg-LNH850SPnTVuEIGU .node .label,#mermaid-svg-LNH850SPnTVuEIGU .image-shape .label,#mermaid-svg-LNH850SPnTVuEIGU .icon-shape .label{text-align:center;}#mermaid-svg-LNH850SPnTVuEIGU .node.clickable{cursor:pointer;}#mermaid-svg-LNH850SPnTVuEIGU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LNH850SPnTVuEIGU .arrowheadPath{fill:#333333;}#mermaid-svg-LNH850SPnTVuEIGU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LNH850SPnTVuEIGU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LNH850SPnTVuEIGU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LNH850SPnTVuEIGU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LNH850SPnTVuEIGU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LNH850SPnTVuEIGU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LNH850SPnTVuEIGU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LNH850SPnTVuEIGU .cluster text{fill:#333;}#mermaid-svg-LNH850SPnTVuEIGU .cluster span{color:#333;}#mermaid-svg-LNH850SPnTVuEIGU 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-LNH850SPnTVuEIGU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LNH850SPnTVuEIGU rect.text{fill:none;stroke-width:0;}#mermaid-svg-LNH850SPnTVuEIGU .icon-shape,#mermaid-svg-LNH850SPnTVuEIGU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LNH850SPnTVuEIGU .icon-shape p,#mermaid-svg-LNH850SPnTVuEIGU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LNH850SPnTVuEIGU .icon-shape .label rect,#mermaid-svg-LNH850SPnTVuEIGU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LNH850SPnTVuEIGU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LNH850SPnTVuEIGU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LNH850SPnTVuEIGU :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-LNH850SPnTVuEIGU .blue>*{fill:#99ccff!important;stroke:#0066cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-LNH850SPnTVuEIGU .blue span{fill:#99ccff!important;stroke:#0066cc!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-LNH850SPnTVuEIGU .blue tspan{fill:#000!important;}#mermaid-svg-LNH850SPnTVuEIGU .green>*{fill:#99ff99!important;stroke:#009900!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-LNH850SPnTVuEIGU .green span{fill:#99ff99!important;stroke:#009900!important;stroke-width:2px!important;color:#000!important;}#mermaid-svg-LNH850SPnTVuEIGU .green tspan{fill:#000!important;}
写完langgraph.json
JSON格式校验
路径大小写检查
依赖版本锁定
环境变量核对
Gitignore确认
云端部署
痛点分析
第一剑:JSON的尾随逗号(Trailing Comma)
这是史诗级巨坑。写Python写习惯了,末尾加个逗号觉得无所谓,但JSON标准不允许!
{
"graphs": {
"agent": "./agent.py:graph",
},
"dependencies": [
"langgraph",
]
}
看到那两个逗号了吗?在VS Code里可能都高亮得不明显,但平台一解析,直接抛 JSONDecodeError。而且报错位置往往指得模棱两可,让你以为是文件编码问题。
第二剑:大小写与路径幻觉
Windows开发者特别容易中招。你的文件叫 ./Agent.py(大写A),Windows本地不区分大小写,跑得飞起。平台容器多半是Linux,大小写敏感,直接 FileNotFoundError。
第三剑:依赖版本裸奔
写 "langgraph" 而不带版本号,平台默认装最新版。今天部署成功,下个月LangGraph发了个大版本,新特性改了API,你的代码没动,但重新构建时装上了新版,直接挂掉。这叫"时间炸弹"。
第四剑:.env裸奔进Git
代码里明明用了 .env,但忘记写进 .gitignore,一提交,密钥全剧透。更惨的是,如果仓库是公开的,爬虫几秒钟就能扫到你的OpenAI Key,账单直接飞天。
第五剑:包结构残缺
你用了 from .utils import …,但目录下没有 __init__.py。虽然Python 3.3+支持隐式命名空间包,但某些构建工具链或旧版平台运行时可能不买账,导入时报 attempted relative import with no known parent package。
第六剑:环境变量只在本地,忘了云端
本地 .env 里填得满满当当,但部署到平台时,忘了把 .env 的内容同步到平台的Secrets管理面板,或者 langgraph.json 里的 env 路径指向错误。结果本地能调OpenAI,云端报401。
解决方案/正确做法
来,给你一个可以直接抄作业的、相对完整的 langgraph.json 模板,对照着检查:
{
"graphs": {
"customer_support": "./src/support_agent.py:graph",
"research_bot": "./src/research_agent.py:graph"
},
"dependencies": [
"langgraph>=0.1.0,<0.2.0",
"langchain-openai>=0.1.0",
"pandas>=2.0.0",
"python-dotenv>=1.0.0"
],
"env": ".env",
"docker": {
"dockerfile": "./Dockerfile"
}
}
逐行解释:
- graphs:两个Agent,路径明确,变量名 graph 与代码对齐。
- dependencies:版本号锁区间,不裸奔;只装运行必要的包。
- env:指向根目录 .env。
- docker:按需启用,不需要时整段删掉。
检查清单(建议保存):
小结
魔鬼藏在细节里。一个逗号、一个大小写、一个版本号、一次Git提交,都能让云端部署变成灾难。写完配置,请像Code Review一样审视它,六脉神剑走一遍,基本能挡住九成血案。
写在最后
兄弟,咱们写代码的人,天生喜欢折腾逻辑、搞算法、调模型,总觉得配置文件这种"体力活"不值得花心思。但LangGraph平台不吃这一套,它只认你白纸黑字写进 langgraph.json 的规则。你可以把Agent调得比诸葛亮还聪明,但配置表填错了,上云那一刻,它就是个黑户。
这篇文章把 langgraph.json 从 graphs 到 http,从依赖到环境,再到各种新手坟场,都捋了一遍。核心就一句话:平台不会读心术,你写的每一个字符,都是给它的指令。 把路径写对,把依赖锁死,把密钥藏好,把JSON校验完,你就已经击败了百分之八十的同级选手。
编程之路不易,但每一步成长都算数。别害怕那些在配置里摔过的跤,它们都是你往后排查问题的肌肉记忆。保持好奇,持续学习,下次部署时,愿你的容器一路绿灯,Agent秒级响应。你只管写好业务,配置这关,咱已经拿下了。
关注私信备注:“资料代找获取”,全网计算机学习资料代找:例如: 《课程:AI 大模型工程师系统课程 (22 章完整版 持续更新)》 《课程:AI 大模型系统实战课第四期 (2026 年开课 持续更新)》 《课程:2026 年 AGI 大模型系统课 23 期》 《课程:2026 年 AGI 大模型系统课 21 期》 《课程:AI 大模型实战课 8 期 (2026 年 2 月最新完结版)》 《课程:AI 大模型系统实战课三期》 《课程:AI 大模型系统课程 (2026 年 2 月开课 持续更新)》 《课程:AI 大模型全阶课程 (2025 年 12 月开课 2026 年 6 月结课)》 《课程:AI 大模型工程师全阶课程 (2025 年 10 月开课 2026 年 4 月结课)》 《课程:2026 年最新大模型 Agent 开发系统课 (持续更新)》 《课程:LLM 多模态视觉大模型系统课》 《课程:大模型 AI 应用开发企业级项目实战课 (2026 年 1 月开课)》 《课程:大模型智能体线上速成班 V2.0》 《课程:Java+AI 大模型智能应用开发全阶课》 《课程:Python+AI 大模型实战视频教程》 《书籍:软件工程 3.0: 大模型驱动的研发新范式.pdf》 《课程:人工智能大模型系统课 (2026 年 1 月底完结版)》 《课程:AI 大模型零基础到商业实战全栈课第五期》 《课程:Vue3.5+Electron + 大模型跨平台 AI 桌面聊天应用实战 (2025)》 《课程:AI 大模型实战训练营 从入门到实战轻松上手》 《课程:2026 年 AI 大模型 RAG 与 Agent 智能体项目实战开发课》 《课程:大模型训练营配套补充资料》
网硕互联帮助中心![【LangGraph实战】《LangGraph实战》_80.[第4章 交互体验] 人机环路四大模式:审批、编辑、反馈、对话-网硕互联帮助中心](https://www.wsisp.com/helps/wp-content/uploads/2026/07/20260727013250-6a66b542d0d46-220x150.png)


评论前必须登录!
注册