[{"data":1,"prerenderedAt":1965},["ShallowReactive",2],{"blog-post-/docs/langgraph":3},{"id":4,"title":5,"body":6,"description":1954,"extension":1955,"meta":1956,"navigation":534,"ogImage":1958,"path":1961,"seo":1962,"stem":1963,"__hash__":1964},"content/docs/LangGraph.md","LangGraph",{"type":7,"value":8,"toc":1942},"minimark",[9,13,18,23,27,33,40,65,70,76,101,106,109,130,133,138,229,231,236,239,253,260,266,281,283,287,301,307,314,320,347,349,357,363,439,441,448,451,456,478,483,505,507,512,515,825,827,832,855,857,861,874,881,899,913,918,921,939,944,998,1008,1010,1017,1024,1072,1074,1079,1090,1111,1146,1148,1153,1163,1184,1186,1190,1194,1200,1222,1224,1231,1238,1241,1248,1283,1312,1314,1319,1322,1331,1337,1345,1347,1352,1355,1395,1397,1402,1405,1528,1530,1535,1553,1555,1559,1562,1569,1579,1600,1602,1609,1612,1658,1660,1666,1673,1675,1682,1685,1691,1702,1731,1733,1738,1744,1791,1793,1798,1805,1839,1841,1846,1849,1874,1876,1881,1888,1934,1936,1938],[10,11,5],"h1",{"id":12},"langgraph",[14,15,17],"h2",{"id":16},"_1-架构基石-从线性到循环的思维跃迁","1 架构基石 —— 从线性到循环的思维跃迁",[19,20,22],"h3",{"id":21},"_11-为什么是-langgraph","1.1 为什么是 LangGraph",[24,25,26],"p",{},"在传统的 LangChain 开发中，我们习惯于将组件（Prompt, Model, OutputParser）像乐高一样串联起来。这种模式在处理简单的、流向明确的任务时非常优雅。但在构建真正的“智能体（Agent）”时，你会撞上几面无形的墙。",[24,28,29],{},[30,31,32],"strong",{},"1. 线性 vs 循环 (DAG vs. Cycles)",[24,34,35,36,39],{},"传统的 LangChain 链（Chains）本质上是一个 ",[30,37,38],{},"DAG（有向无环图）","。",[41,42,43,55],"ul",{},[44,45,46,49,50,54],"li",{},[30,47,48],{},"线性思维："," 数据从 A 流向 B，再流向 C。如果你想让 AI 发现结果不对并“退回去重写”，在 LCEL 中实现这种“循环”会变得异常痛苦，通常需要嵌套复杂的 ",[51,52,53],"code",{},"while"," 循环或递归函数，代码可读性极差。",[44,56,57,60,61,64],{},[30,58,59],{},"智能体思维："," 真实的推理过程往往是：思考 -> 行动 -> 观察 -> ",[30,62,63],{},"（如果不满意）回到思考","。LangGraph 允许你显式地定义这种“循环”，让 AI 在图中反复迭代直到满足终止条件。",[24,66,67],{},[30,68,69],{},"2. “黑盒” AgentExecutor 的局限性",[24,71,72,73,39],{},"在 LangGraph 出现之前，我们主要依赖 LangChain 内置的 ",[51,74,75],{},"AgentExecutor",[41,77,78,95],{},[44,79,80,83,84,86,87],{},[30,81,82],{},"痛点："," ",[51,85,75],{}," 就像一个黑盒子。你很难精细地控制：\n",[41,88,89,92],{},[44,90,91],{},"“在执行工具 A 之前，必须先询问人类。”",[44,93,94],{},"“如果工具返回错误，不要直接报错，而是去尝试工具 B。”",[44,96,97,100],{},[30,98,99],{},"解决方案："," LangGraph 将 Agent 的控制权交还给了开发者。你可以手动定义每一个节点（Node）和每一条边（Edge），将 Agent 的逻辑拆解为透明的状态转移过程。",[24,102,103],{},[30,104,105],{},"3. 状态管理的缺失 (The State Problem)",[24,107,108],{},"在普通的 Chain 中，上下文的传递完全依赖于前一个组件的输出作为下一个组件的输入。",[41,110,111,117],{},[44,112,113,116],{},[30,114,115],{},"问题："," 当你的 Agent 运行了 10 个步骤后，你很难管理哪些信息是需要保留的长效记忆，哪些是临时的中间变量。",[44,118,119,122,123,129],{},[30,120,121],{},"LangGraph 的革新："," 它引入了 ",[30,124,125,128],{},[51,126,127],{},"State","（状态对象）","。整个图共用一个状态，每个节点都可以按需读取或更新这个状态的部分字段，类似于前端开发中的 Redux 或 Vuex。",[131,132],"hr",{},[24,134,135],{},[30,136,137],{},"核心对比：Chain vs Graph",[139,140,141,162],"table",{},[142,143,144],"thead",{},[145,146,147,153,158],"tr",{},[148,149,150],"th",{},[30,151,152],{},"特性",[148,154,155],{},[30,156,157],{},"LangChain (LCEL)",[148,159,160],{},[30,161,5],{},[163,164,165,181,199,214],"tbody",{},[145,166,167,173,176],{},[168,169,170],"td",{},[30,171,172],{},"逻辑流",[168,174,175],{},"主要是线性的（DAG）",[168,177,178],{},[30,179,180],{},"支持循环（Cycles）",[145,182,183,188,191],{},[168,184,185],{},[30,186,187],{},"状态管理",[168,189,190],{},"局部传递，难以持久化",[168,192,193],{},[30,194,195,196,198],{},"全局 ",[51,197,127],{}," 对象，支持持久化",[145,200,201,206,209],{},[168,202,203],{},[30,204,205],{},"人机交互",[168,207,208],{},"极难中途中断并恢复",[168,210,211],{},[30,212,213],{},"原生支持断点（Breakpoints）",[145,215,216,221,224],{},[168,217,218],{},[30,219,220],{},"透明度",[168,222,223],{},"较高（链式结构清晰）",[168,225,226],{},[30,227,228],{},"极高（每一个决策分支都可见）",[131,230],{},[24,232,233],{},[30,234,235],{},"🛠️ 深度解析：LCEL 的“痛点”实例",[24,237,238],{},"想象一下，你要写一个“论文润色助手”。它的逻辑是：",[240,241,242,245,248],"ol",{},[44,243,244],{},"润色文本。",[44,246,247],{},"检查字数。",[44,249,250],{},[30,251,252],{},"如果字数超标，重新压缩润色。",[24,254,255,256,259],{},"使用 ",[30,257,258],{},"LCEL","，你可能需要通过嵌套逻辑来实现，一旦逻辑稍微复杂（比如还要检查语法、语气），代码就会变成一团乱麻。",[24,261,262,263,265],{},"而在 ",[30,264,5],{}," 中，这只是一个简单的循环：",[267,268,269],"blockquote",{},[24,270,271,274,275,274,278,39],{},[30,272,273],{},"节点 A (润色)"," $\\rightarrow$ ",[30,276,277],{},"节点 B (检查)",[30,279,280],{},"条件边 (若超标则回到 A，否则去往结束)",[131,282],{},[19,284,286],{"id":285},"_12-状态机模型stategraphnodes-与-edges","1.2 状态机模型：StateGraph、Nodes 与 Edges",[24,288,289,290,293,294,297,298,39],{},"在 LangGraph 中，构建一个 AI 应用不再是简单的“连点成线”，而是**“设计一个生命体”**。这个生命体由三个核心支柱组成：",[30,291,292],{},"State（状态）","、",[30,295,296],{},"Nodes（节点）"," 和 ",[30,299,300],{},"Edges（边）",[24,302,303,304,39],{},"我们可以把 LangGraph 想象成一个",[30,305,306],{},"精密自动化的工厂流水线",[240,308,309],{},[44,310,311],{},[30,312,313],{},"StateGraph：工厂的设计蓝图",[24,315,316,319],{},[51,317,318],{},"StateGraph"," 是整个应用的载体。它定义了流水线上流转的“工单”格式是什么样的。",[41,321,322,331],{},[44,323,324,327,328,330],{},[30,325,326],{},"核心逻辑："," 在 LangGraph 中，所有的信息交换都发生在一个全局的 ",[51,329,127],{}," 对象中 。",[44,332,333,336,337,339,340,343,344,346],{},[30,334,335],{},"设计原则："," 你必须先定义这个 ",[51,338,127],{},"（通常是一个 ",[51,341,342],{},"TypedDict","），然后用它来初始化 ",[51,345,318],{},"。这就像是工厂开工前，先确定好工单上必须包含哪些字段（比如：客户需求、初始代码、错误日志等）。",[131,348],{},[240,350,352],{"start":351},2,[44,353,354],{},[30,355,356],{},"Nodes (节点)：工厂里的职能车间",[24,358,359,360,39],{},"节点是逻辑发生的地方。每一个节点本质上都是一个 ",[30,361,362],{},"Python 函数",[41,364,365,377,394],{},[44,366,367,370,371,373,374,39],{},[30,368,369],{},"输入与输出："," 节点接收当前的 ",[51,372,127],{}," 作为输入，处理完毕后，输出一个",[30,375,376],{},"修改后的 State 增量",[44,378,379,382,383,386,387,390,391,393],{},[30,380,381],{},"独立性："," 每个车间（节点）只负责自己的活。例如，",[51,384,385],{},"Researcher"," 节点只负责上网搜资料，它不需要知道 ",[51,388,389],{},"Writer"," 节点是怎么写的。它只需要把搜到的结果填进 ",[51,392,127],{}," 里的对应字段即可。",[44,395,396,399],{},[30,397,398],{},"代码示例：",[400,401,406],"pre",{"className":402,"code":403,"language":404,"meta":405,"style":405},"language-Python shiki shiki-themes github-light github-dark","def research_node(state: MyState):\n    # 执行搜索逻辑\n    content = web_search(state[\"topic\"])\n    # 返回更新后的状态（注意：只返回需要改变的部分）\n    return {\"docs\": content}\n","Python","",[51,407,408,416,421,427,433],{"__ignoreMap":405},[409,410,413],"span",{"class":411,"line":412},"line",1,[409,414,415],{},"def research_node(state: MyState):\n",[409,417,418],{"class":411,"line":351},[409,419,420],{},"    # 执行搜索逻辑\n",[409,422,424],{"class":411,"line":423},3,[409,425,426],{},"    content = web_search(state[\"topic\"])\n",[409,428,430],{"class":411,"line":429},4,[409,431,432],{},"    # 返回更新后的状态（注意：只返回需要改变的部分）\n",[409,434,436],{"class":411,"line":435},5,[409,437,438],{},"    return {\"docs\": content}\n",[131,440],{},[240,442,443],{"start":423},[44,444,445],{},[30,446,447],{},"Edges (边)：自动传送带与分拣机",[24,449,450],{},"边决定了工单（State）从一个车间流向哪一个车间。它们是控制逻辑的灵魂。在 LangGraph 中，边分为两种：",[24,452,453],{},[30,454,455],{},"A. 普通边 (Normal Edges)",[41,457,458,464,470],{},[44,459,460,463],{},[30,461,462],{},"作用："," 固定的逻辑流。",[44,465,466,469],{},[30,467,468],{},"类比："," A 车间加工完，必须送往 B 车间。",[44,471,472,83,475],{},[30,473,474],{},"语法：",[51,476,477],{},"workflow.add_edge(\"node_a\", \"node_b\")",[24,479,480],{},[30,481,482],{},"B. 条件边 (Conditional Edges)",[41,484,485,490,495],{},[44,486,487,489],{},[30,488,462],{}," 逻辑分支与循环的关键 。",[44,491,492,494],{},[30,493,468],{}," “质检车间”检查产品。如果合格，送往“包装车间”；如果不合格，送回“加工车间”重做。",[44,496,497,500,501,504],{},[30,498,499],{},"核心机制："," 依靠一个",[30,502,503],{},"路由函数 (Router Function)"," 来决定下一步去哪。",[131,506],{},[24,508,509],{},[30,510,511],{},"🛠️ 动手实践：构建你的第一个“骨架”代码",[24,513,514],{},"让我们把这些概念拼成一段可运行的逻辑结构。假设我们要写一个简单的“翻译校验助手”：翻译 -> 校验 -> 不合格则重翻。",[400,516,518],{"className":402,"code":517,"language":404,"meta":405,"style":405},"from typing import TypedDict, List\nfrom langgraph.graph import StateGraph, END\n\n# 1. 定义状态 (State)\nclass AgentState(TypedDict):\n    content: str\n    is_ok: bool\n    review_feedback: str\n\n# 2. 定义节点 (Nodes)\ndef translator(state: AgentState):\n    print(\"---翻译中---\")\n    return {\"content\": \"Translated text...\"}\n\ndef critic(state: AgentState):\n    print(\"---评审中---\")\n    # 模拟逻辑：如果内容太短就判为不合格\n    if len(state[\"content\"]) \u003C 5:\n        return {\"is_ok\": False, \"review_feedback\": \"太短了\"}\n    return {\"is_ok\": True}\n\n# 3. 定义路由逻辑 (Conditional Function)\ndef decide_next_step(state: AgentState):\n    if state[\"is_ok\"]:\n        return \"end\"\n    else:\n        return \"translate\"\n\n# 4. 构建图 (The Graph)\nworkflow = StateGraph(AgentState)\n\n# 添加节点\nworkflow.add_node(\"translator\", translator)\nworkflow.add_node(\"critic\", critic)\n\n# 设置起点\nworkflow.set_entry_point(\"translator\")\n\n# 添加边\nworkflow.add_edge(\"translator\", \"critic\") # 翻译完必去评审\n\n# 添加条件边：评审完根据结果决定去向\nworkflow.add_conditional_edges(\n    \"critic\",\n    decide_next_step,\n    {\n        \"translate\": \"translator\", # 路由函数返回 translate 则回到翻译节点\n        \"end\": END                 # 路由函数返回 end 则结束\n    }\n)\n\n# 5. 编译 (Compile)\napp = workflow.compile()\n",[51,519,520,525,530,536,541,546,552,558,564,569,575,581,587,593,598,604,610,616,622,628,634,639,645,651,657,663,669,675,680,686,692,697,703,709,715,720,726,732,737,743,749,754,760,766,772,778,784,790,796,802,808,813,819],{"__ignoreMap":405},[409,521,522],{"class":411,"line":412},[409,523,524],{},"from typing import TypedDict, List\n",[409,526,527],{"class":411,"line":351},[409,528,529],{},"from langgraph.graph import StateGraph, END\n",[409,531,532],{"class":411,"line":423},[409,533,535],{"emptyLinePlaceholder":534},true,"\n",[409,537,538],{"class":411,"line":429},[409,539,540],{},"# 1. 定义状态 (State)\n",[409,542,543],{"class":411,"line":435},[409,544,545],{},"class AgentState(TypedDict):\n",[409,547,549],{"class":411,"line":548},6,[409,550,551],{},"    content: str\n",[409,553,555],{"class":411,"line":554},7,[409,556,557],{},"    is_ok: bool\n",[409,559,561],{"class":411,"line":560},8,[409,562,563],{},"    review_feedback: str\n",[409,565,567],{"class":411,"line":566},9,[409,568,535],{"emptyLinePlaceholder":534},[409,570,572],{"class":411,"line":571},10,[409,573,574],{},"# 2. 定义节点 (Nodes)\n",[409,576,578],{"class":411,"line":577},11,[409,579,580],{},"def translator(state: AgentState):\n",[409,582,584],{"class":411,"line":583},12,[409,585,586],{},"    print(\"---翻译中---\")\n",[409,588,590],{"class":411,"line":589},13,[409,591,592],{},"    return {\"content\": \"Translated text...\"}\n",[409,594,596],{"class":411,"line":595},14,[409,597,535],{"emptyLinePlaceholder":534},[409,599,601],{"class":411,"line":600},15,[409,602,603],{},"def critic(state: AgentState):\n",[409,605,607],{"class":411,"line":606},16,[409,608,609],{},"    print(\"---评审中---\")\n",[409,611,613],{"class":411,"line":612},17,[409,614,615],{},"    # 模拟逻辑：如果内容太短就判为不合格\n",[409,617,619],{"class":411,"line":618},18,[409,620,621],{},"    if len(state[\"content\"]) \u003C 5:\n",[409,623,625],{"class":411,"line":624},19,[409,626,627],{},"        return {\"is_ok\": False, \"review_feedback\": \"太短了\"}\n",[409,629,631],{"class":411,"line":630},20,[409,632,633],{},"    return {\"is_ok\": True}\n",[409,635,637],{"class":411,"line":636},21,[409,638,535],{"emptyLinePlaceholder":534},[409,640,642],{"class":411,"line":641},22,[409,643,644],{},"# 3. 定义路由逻辑 (Conditional Function)\n",[409,646,648],{"class":411,"line":647},23,[409,649,650],{},"def decide_next_step(state: AgentState):\n",[409,652,654],{"class":411,"line":653},24,[409,655,656],{},"    if state[\"is_ok\"]:\n",[409,658,660],{"class":411,"line":659},25,[409,661,662],{},"        return \"end\"\n",[409,664,666],{"class":411,"line":665},26,[409,667,668],{},"    else:\n",[409,670,672],{"class":411,"line":671},27,[409,673,674],{},"        return \"translate\"\n",[409,676,678],{"class":411,"line":677},28,[409,679,535],{"emptyLinePlaceholder":534},[409,681,683],{"class":411,"line":682},29,[409,684,685],{},"# 4. 构建图 (The Graph)\n",[409,687,689],{"class":411,"line":688},30,[409,690,691],{},"workflow = StateGraph(AgentState)\n",[409,693,695],{"class":411,"line":694},31,[409,696,535],{"emptyLinePlaceholder":534},[409,698,700],{"class":411,"line":699},32,[409,701,702],{},"# 添加节点\n",[409,704,706],{"class":411,"line":705},33,[409,707,708],{},"workflow.add_node(\"translator\", translator)\n",[409,710,712],{"class":411,"line":711},34,[409,713,714],{},"workflow.add_node(\"critic\", critic)\n",[409,716,718],{"class":411,"line":717},35,[409,719,535],{"emptyLinePlaceholder":534},[409,721,723],{"class":411,"line":722},36,[409,724,725],{},"# 设置起点\n",[409,727,729],{"class":411,"line":728},37,[409,730,731],{},"workflow.set_entry_point(\"translator\")\n",[409,733,735],{"class":411,"line":734},38,[409,736,535],{"emptyLinePlaceholder":534},[409,738,740],{"class":411,"line":739},39,[409,741,742],{},"# 添加边\n",[409,744,746],{"class":411,"line":745},40,[409,747,748],{},"workflow.add_edge(\"translator\", \"critic\") # 翻译完必去评审\n",[409,750,752],{"class":411,"line":751},41,[409,753,535],{"emptyLinePlaceholder":534},[409,755,757],{"class":411,"line":756},42,[409,758,759],{},"# 添加条件边：评审完根据结果决定去向\n",[409,761,763],{"class":411,"line":762},43,[409,764,765],{},"workflow.add_conditional_edges(\n",[409,767,769],{"class":411,"line":768},44,[409,770,771],{},"    \"critic\",\n",[409,773,775],{"class":411,"line":774},45,[409,776,777],{},"    decide_next_step,\n",[409,779,781],{"class":411,"line":780},46,[409,782,783],{},"    {\n",[409,785,787],{"class":411,"line":786},47,[409,788,789],{},"        \"translate\": \"translator\", # 路由函数返回 translate 则回到翻译节点\n",[409,791,793],{"class":411,"line":792},48,[409,794,795],{},"        \"end\": END                 # 路由函数返回 end 则结束\n",[409,797,799],{"class":411,"line":798},49,[409,800,801],{},"    }\n",[409,803,805],{"class":411,"line":804},50,[409,806,807],{},")\n",[409,809,811],{"class":411,"line":810},51,[409,812,535],{"emptyLinePlaceholder":534},[409,814,816],{"class":411,"line":815},52,[409,817,818],{},"# 5. 编译 (Compile)\n",[409,820,822],{"class":411,"line":821},53,[409,823,824],{},"app = workflow.compile()\n",[131,826],{},[24,828,829],{},[30,830,831],{},"🎓 本节小结",[240,833,834,839,845],{},[44,835,836,838],{},[30,837,318],{}," 是大管家，管理着全局的“账本” ($State$)。",[44,840,841,844],{},[30,842,843],{},"Nodes"," 是打工人，只负责接收 $State$、干活、更新 $State$。",[44,846,847,850,851,854],{},[30,848,849],{},"Edges"," 是交通警察，负责指引 $State$ 的流向，尤其是",[30,852,853],{},"条件边","实现了我们梦寐以求的“循环”能力。",[131,856],{},[19,858,860],{"id":859},"_13-核心机制详解状态更新与图的编译","1.3 核心机制详解：状态更新与图的编译",[24,862,863,864,866,867,869,870,873],{},"如果说 ",[51,865,843],{}," 是工厂的工人，那么 ",[30,868,292],{}," 就是那本流转的账本。但在复杂的智能体协作中，如何保证多个工人同时写账本时不打架？这就是 ",[30,871,872],{},"Reducers"," 的职责。",[240,875,876],{},[44,877,878],{},[30,879,880],{},"State & Reducers：精妙的增量更新",[24,882,883,884,887,888,891,892,895,896,39],{},"在 LangGraph 中，状态更新默认是**覆盖（Overwrite）**模式。也就是说，如果节点 A 返回了 ",[51,885,886],{},"{\"count\": 1}","，节点 B 随后返回了 ",[51,889,890],{},"{\"count\": 2}","，那么状态中的 ",[51,893,894],{},"count"," 就会变成 ",[51,897,898],{},"2",[24,900,901,902,905,906,297,909,912],{},"但在 AI 应用（尤其是对话系统）中，我们往往需要",[30,903,904],{},"累加（Append）","，比如保留完整的聊天记录。这时我们就需要用到 ",[51,907,908],{},"Annotated",[51,910,911],{},"operator"," 。",[24,914,915],{},[30,916,917],{},"核心机制：Reducer 函数",[24,919,920],{},"Reducer 定义了如何将节点返回的“新值”合并到“旧状态”中。",[41,922,923,929],{},[44,924,925,928],{},[30,926,927],{},"默认行为："," 直接覆盖旧值。",[44,930,931,934,935,938],{},[30,932,933],{},"增量累加："," 使用 ",[51,936,937],{},"operator.add","。这在处理消息列表（Message List）时最为常见。",[24,940,941],{},[30,942,943],{},"代码实现对比：",[400,945,947],{"className":402,"code":946,"language":404,"meta":405,"style":405},"from typing import Annotated, TypedDict\nimport operator\n\nclass GlobalState(TypedDict):\n    # 默认模式：新的字符串会直接覆盖旧的字符串\n    current_task: str \n    \n    # Reducer 模式：新的列表会通过 operator.add 自动拼接在旧列表后面\n    # 比如：[\"hi\"] + [\"hello\"] = [\"hi\", \"hello\"]\n    history: Annotated[list, operator.add] \n",[51,948,949,954,959,963,968,973,978,983,988,993],{"__ignoreMap":405},[409,950,951],{"class":411,"line":412},[409,952,953],{},"from typing import Annotated, TypedDict\n",[409,955,956],{"class":411,"line":351},[409,957,958],{},"import operator\n",[409,960,961],{"class":411,"line":423},[409,962,535],{"emptyLinePlaceholder":534},[409,964,965],{"class":411,"line":429},[409,966,967],{},"class GlobalState(TypedDict):\n",[409,969,970],{"class":411,"line":435},[409,971,972],{},"    # 默认模式：新的字符串会直接覆盖旧的字符串\n",[409,974,975],{"class":411,"line":548},[409,976,977],{},"    current_task: str \n",[409,979,980],{"class":411,"line":554},[409,981,982],{},"    \n",[409,984,985],{"class":411,"line":560},[409,986,987],{},"    # Reducer 模式：新的列表会通过 operator.add 自动拼接在旧列表后面\n",[409,989,990],{"class":411,"line":566},[409,991,992],{},"    # 比如：[\"hi\"] + [\"hello\"] = [\"hi\", \"hello\"]\n",[409,994,995],{"class":411,"line":571},[409,996,997],{},"    history: Annotated[list, operator.add]\n",[267,999,1000],{},[24,1001,1002,83,1005,1007],{},[30,1003,1004],{},"专家提示：",[51,1006,937],{}," 不仅仅能用于列表，还能用于整数累加（计数器）或者字典合并。它是实现智能体“记忆”和“进度追踪”的核心工具。",[131,1009],{},[240,1011,1012],{"start":351},[44,1013,1014],{},[30,1015,1016],{},"The Compile Step：从逻辑图到可执行实体",[24,1018,1019,1020,1023],{},"当定义好所有的节点和边之后，必须执行 ",[51,1021,1022],{},"workflow.compile()","。这个步骤不仅仅是简单的封装，它在幕后完成了几件至关重要的事 ：",[240,1025,1026,1032,1056,1066],{},[44,1027,1028,1031],{},[30,1029,1030],{},"逻辑验证："," 检查你的图是否有孤立节点、是否有无法到达的终点、以及条件边的路由逻辑是否闭环。",[44,1033,1034,1037,1038,1041,1042,1045,1046,293,1049,1052,1053,39],{},[30,1035,1036],{},"转化为 Runnable："," 编译后的对象是一个 ",[51,1039,1040],{},"CompiledGraph","。它遵循 LangChain 的 ",[30,1043,1044],{},"LCEL 协议","，这意味着你可以像调用普通 Chain 一样使用 ",[51,1047,1048],{},".invoke()",[51,1050,1051],{},".stream()"," 或 ",[51,1054,1055],{},".astream_log()",[44,1057,1058,1061,1062,1065],{},[30,1059,1060],{},"接入持久化层："," 只有编译后的图才能绑定 ",[30,1063,1064],{},"Checkpointer（检查点）","。这是实现“时光倒流”、“断点续传”以及多轮对话记忆的前提。",[44,1067,1068,1071],{},[30,1069,1070],{},"支持人机交互："," 编译阶段可以指定哪些节点需要“中断（interrupt）”，从而实现人类审批逻辑。",[131,1073],{},[24,1075,1076],{},[30,1077,1078],{},"🛠️ 深度解析：编译后的对象长什么样？",[24,1080,1081,1082,1085,1086,1089],{},"当你运行 ",[51,1083,1084],{},"app = workflow.compile()"," 后，",[51,1087,1088],{},"app"," 就变成了一个功能强大的引擎。",[41,1091,1092,1102],{},[44,1093,1094,1097,1098,1101],{},[30,1095,1096],{},"可视化："," 你可以立即通过 ",[51,1099,1100],{},"app.get_graph().print_ascii()"," 查看逻辑流是否符合预期。",[44,1103,1104,1107,1108,1110],{},[30,1105,1106],{},"输入输出适配："," 它会自动根据你定义的 ",[51,1109,127],{}," 结构来校验输入数据。",[400,1112,1114],{"className":402,"code":1113,"language":404,"meta":405,"style":405},"# 编译后的调用示例\ninitial_state = {\"history\": [\"用户: 你好\"], \"current_task\": \"打招呼\"}\nfor event in app.stream(initial_state):\n    # 每一个 event 代表一个节点执行完毕后的输出\n    # 这让你能实时监控 Agent 的思考过程\n    print(event)\n",[51,1115,1116,1121,1126,1131,1136,1141],{"__ignoreMap":405},[409,1117,1118],{"class":411,"line":412},[409,1119,1120],{},"# 编译后的调用示例\n",[409,1122,1123],{"class":411,"line":351},[409,1124,1125],{},"initial_state = {\"history\": [\"用户: 你好\"], \"current_task\": \"打招呼\"}\n",[409,1127,1128],{"class":411,"line":423},[409,1129,1130],{},"for event in app.stream(initial_state):\n",[409,1132,1133],{"class":411,"line":429},[409,1134,1135],{},"    # 每一个 event 代表一个节点执行完毕后的输出\n",[409,1137,1138],{"class":411,"line":435},[409,1139,1140],{},"    # 这让你能实时监控 Agent 的思考过程\n",[409,1142,1143],{"class":411,"line":548},[409,1144,1145],{},"    print(event)\n",[131,1147],{},[24,1149,1150],{},[30,1151,1152],{},"⚖️ 本节小结",[24,1154,1155,1156,297,1159,1162],{},"理解了 ",[30,1157,1158],{},"Reducer",[30,1160,1161],{},"Compile","，你就掌握了 LangGraph 的“内功心法”：",[41,1164,1165,1175],{},[44,1166,1167,1170,1171,1174],{},[30,1168,1169],{},"State & Reducers"," 解决了数据的",[30,1172,1173],{},"流动与保存问题","，让 Agent 不再是“健忘症”。",[44,1176,1177,1179,1180,1183],{},[30,1178,1161],{}," 解决了逻辑的",[30,1181,1182],{},"封装与工程化问题","，让复杂的图变成一个可交互、可追踪的标准组件。",[131,1185],{},[14,1187,1189],{"id":1188},"_2-深度人机交互-human-in-the-loop-hitl","2 深度人机交互 (Human-in-the-loop, HITL)。",[19,1191,1193],{"id":1192},"_21-断点控制-breakpoints","2.1 断点控制 (Breakpoints)",[24,1195,1196,1197,39],{},"在普通的程序开发中，断点用于调试；但在 LangGraph 中，断点是一种",[30,1198,1199],{},"生产环境的逻辑控制",[41,1201,1202,1216],{},[44,1203,1204,1207,1208,1211,1212,1215],{},[30,1205,1206],{},"定义："," 断点允许你在图执行到某个特定节点",[30,1209,1210],{},"之前","或",[30,1213,1214],{},"之后","，强制暂停执行并挂起当前状态 。",[44,1217,1218,1221],{},[30,1219,1220],{},"特性："," 此时 Agent 的所有状态（State）会被保存到持久化层（Checkpointer），等待外部信号（通常是人类的审批或输入）来触发续传 。",[131,1223],{},[240,1225,1226],{},[44,1227,1228],{},[30,1229,1230],{},"静态断点：确定性的风险预防",[24,1232,1233,1234,1237],{},"静态断点是最简单的模式，你在",[30,1235,1236],{},"编译图","时直接指定哪些节点需要被“阻断” 。",[24,1239,1240],{},"核心场景：人工授权",[24,1242,1243,1244,1247],{},"想象一个“自动运维 Agent”，当它决定执行 ",[51,1245,1246],{},"rm -rf"," 命令前，必须停下来。",[41,1249,1250,1264],{},[44,1251,1252,1255,1256,1259,1260,1263],{},[30,1253,1254],{},"实现："," 在 ",[51,1257,1258],{},"compile"," 时使用 ",[51,1261,1262],{},"interrupt_before"," 参数。",[44,1265,1266,1269],{},[30,1267,1268],{},"流程：",[240,1270,1271,1274,1277,1280],{},[44,1272,1273],{},"Agent 运行到该节点前停止。",[44,1275,1276],{},"状态存入数据库。",[44,1278,1279],{},"人类收到通知，查看 State。",[44,1281,1282],{},"人类批准，Agent 继续运行。",[400,1284,1286],{"className":402,"code":1285,"language":404,"meta":405,"style":405},"# 示例：在执行 'action' 节点前设置静态断点\napp = workflow.compile(\n    checkpointer=memory, # 必须绑定检查点才能支持断点\n    interrupt_before=[\"action\"] \n)\n",[51,1287,1288,1293,1298,1303,1308],{"__ignoreMap":405},[409,1289,1290],{"class":411,"line":412},[409,1291,1292],{},"# 示例：在执行 'action' 节点前设置静态断点\n",[409,1294,1295],{"class":411,"line":351},[409,1296,1297],{},"app = workflow.compile(\n",[409,1299,1300],{"class":411,"line":423},[409,1301,1302],{},"    checkpointer=memory, # 必须绑定检查点才能支持断点\n",[409,1304,1305],{"class":411,"line":429},[409,1306,1307],{},"    interrupt_before=[\"action\"] \n",[409,1309,1310],{"class":411,"line":435},[409,1311,807],{},[131,1313],{},[24,1315,1316],{},[30,1317,1318],{},"2. 动态断点：基于逻辑的智能拦截",[24,1320,1321],{},"有时候，我们并不想每次都拦截，只有在 AI 觉得“心里没底”时才请求人工介入。",[24,1323,1324],{},[30,1325,1326,1327,1330],{},"实现方式：",[51,1328,1329],{},"NodeInterrupt"," 异常",[24,1332,1333,1334,1336],{},"你可以在节点函数内部进行逻辑判断，如果满足特定条件（如：LLM 输出的置信度低于 0.8，或者涉及敏感关键词），则抛出一个 ",[51,1335,1329],{}," 异常。",[41,1338,1339],{},[44,1340,1341,1344],{},[30,1342,1343],{},"优势："," 更加灵活。它允许 Agent 自主决定何时需要“求助”人类。",[131,1346],{},[24,1348,1349],{},[30,1350,1351],{},"3. 深度交互流程：暂停 -> 恢复",[24,1353,1354],{},"要实现断点，必须具备持久化（Persistence）能力。",[41,1356,1357,1371,1381],{},[44,1358,1359,1362,1363,1366,1367,1370],{},[30,1360,1361],{},"运行并中断："," 当你调用 ",[51,1364,1365],{},"app.invoke()"," 且触碰断点时，程序会结束运行，但 ",[51,1368,1369],{},"Thread ID"," 下的状态已被保留 。",[44,1372,1373,1376,1377,1380],{},[30,1374,1375],{},"获取快照："," 开发者可以通过 ",[51,1378,1379],{},"app.get_state(config)"," 获取当前停在哪了。",[44,1382,1383,1386,1387,1390,1391,1394],{},[30,1384,1385],{},"恢复执行："," 人类通过 ",[51,1388,1389],{},"app.invoke(None, config)","。传入 ",[51,1392,1393],{},"None"," 表示：“不用输入新信息，接着上次没干完的活继续干” 。",[131,1396],{},[24,1398,1399],{},[30,1400,1401],{},"🛠️ 资深工程师的实战代码片段",[24,1403,1404],{},"假设我们要构建一个“高额消费审批系统”：",[400,1406,1408],{"className":402,"code":1407,"language":404,"meta":405,"style":405},"# 定义节点逻辑\ndef process_payment(state: AgentState):\n    # 如果金额大于 1000，我们在编译阶段会拦截它\n    print(f\"正在处理支付：{state['amount']}元\")\n    return {\"status\": \"success\"}\n\n# 编译时指定拦截\napp = workflow.compile(\n    checkpointer=memory, \n    interrupt_before=[\"process_payment\"] # 支付前必须人看一眼\n)\n\n# --- 运行逻辑 ---\nconfig = {\"configurable\": {\"thread_id\": \"user_123\"}}\n\n# 1. 触发运行\n# Agent 会停在 process_payment 节点之前\napp.invoke({\"amount\": 5000}, config)\n\n# 2. 此时 Agent 处于等待状态，我们可以检查它\ncurrent_state = app.get_state(config)\nprint(f\"当前节点: {current_state.next}\") # 输出: ('process_payment',)\n\n# 3. 人类确认没问题，继续执行\napp.invoke(None, config) \n",[51,1409,1410,1415,1420,1425,1430,1435,1439,1444,1448,1453,1458,1462,1466,1471,1476,1480,1485,1490,1495,1499,1504,1509,1514,1518,1523],{"__ignoreMap":405},[409,1411,1412],{"class":411,"line":412},[409,1413,1414],{},"# 定义节点逻辑\n",[409,1416,1417],{"class":411,"line":351},[409,1418,1419],{},"def process_payment(state: AgentState):\n",[409,1421,1422],{"class":411,"line":423},[409,1423,1424],{},"    # 如果金额大于 1000，我们在编译阶段会拦截它\n",[409,1426,1427],{"class":411,"line":429},[409,1428,1429],{},"    print(f\"正在处理支付：{state['amount']}元\")\n",[409,1431,1432],{"class":411,"line":435},[409,1433,1434],{},"    return {\"status\": \"success\"}\n",[409,1436,1437],{"class":411,"line":548},[409,1438,535],{"emptyLinePlaceholder":534},[409,1440,1441],{"class":411,"line":554},[409,1442,1443],{},"# 编译时指定拦截\n",[409,1445,1446],{"class":411,"line":560},[409,1447,1297],{},[409,1449,1450],{"class":411,"line":566},[409,1451,1452],{},"    checkpointer=memory, \n",[409,1454,1455],{"class":411,"line":571},[409,1456,1457],{},"    interrupt_before=[\"process_payment\"] # 支付前必须人看一眼\n",[409,1459,1460],{"class":411,"line":577},[409,1461,807],{},[409,1463,1464],{"class":411,"line":583},[409,1465,535],{"emptyLinePlaceholder":534},[409,1467,1468],{"class":411,"line":589},[409,1469,1470],{},"# --- 运行逻辑 ---\n",[409,1472,1473],{"class":411,"line":595},[409,1474,1475],{},"config = {\"configurable\": {\"thread_id\": \"user_123\"}}\n",[409,1477,1478],{"class":411,"line":600},[409,1479,535],{"emptyLinePlaceholder":534},[409,1481,1482],{"class":411,"line":606},[409,1483,1484],{},"# 1. 触发运行\n",[409,1486,1487],{"class":411,"line":612},[409,1488,1489],{},"# Agent 会停在 process_payment 节点之前\n",[409,1491,1492],{"class":411,"line":618},[409,1493,1494],{},"app.invoke({\"amount\": 5000}, config)\n",[409,1496,1497],{"class":411,"line":624},[409,1498,535],{"emptyLinePlaceholder":534},[409,1500,1501],{"class":411,"line":630},[409,1502,1503],{},"# 2. 此时 Agent 处于等待状态，我们可以检查它\n",[409,1505,1506],{"class":411,"line":636},[409,1507,1508],{},"current_state = app.get_state(config)\n",[409,1510,1511],{"class":411,"line":641},[409,1512,1513],{},"print(f\"当前节点: {current_state.next}\") # 输出: ('process_payment',)\n",[409,1515,1516],{"class":411,"line":647},[409,1517,535],{"emptyLinePlaceholder":534},[409,1519,1520],{"class":411,"line":653},[409,1521,1522],{},"# 3. 人类确认没问题，继续执行\n",[409,1524,1525],{"class":411,"line":659},[409,1526,1527],{},"app.invoke(None, config)\n",[131,1529],{},[24,1531,1532],{},[30,1533,1534],{},"🎓 本节小节",[41,1536,1537,1543],{},[44,1538,1539,1542],{},[30,1540,1541],{},"断点"," 是实现“人类在环（HITL）”的基础，它将 Agent 从“全自动”降级为“半自动”，从而换取极高的安全性 。",[44,1544,1545,1546,1549,1550,912],{},"实现断点有两个前提：",[30,1547,1548],{},"编译时指定（静态）/代码内触发（动态）","，以及",[30,1551,1552],{},"必须配置 Checkpointer",[131,1554],{},[19,1556,1558],{"id":1557},"_22-时光倒流与状态编辑重塑-agent-的历史","2.2 时光倒流与状态编辑：重塑 Agent 的历史",[24,1560,1561],{},"在复杂的任务中，Agent 可能会在第 5 步犯错，导致后面全盘皆错。如果只能重头再来，效率太低。LangGraph 提供的“时光倒流”能力，本质上是赋予了开发者和用户“修改过去，改写未来”的超能力 。",[240,1563,1564],{},[44,1565,1566],{},[30,1567,1568],{},"时光倒流 (Time Travel)：浏览历史快照",[24,1570,1571,1572,1574,1575,1578],{},"当你在图中配置了 ",[30,1573,1064],{}," 后，Agent 的每一次状态转移都会被永久记录在一个 ",[51,1576,1577],{},"thread","（线程）中 。",[41,1580,1581,1590],{},[44,1582,1583,1585,1586,1589],{},[30,1584,499],{}," 通过 ",[51,1587,1588],{},"app.get_state_history(config)","，你可以获取该线程下所有历史状态的列表 。",[44,1591,1592,1595,1596,1599],{},[30,1593,1594],{},"不仅仅是查看："," 每一个快照（Checkpoint）都有一个唯一的 ",[51,1597,1598],{},"checkpoint_id","。你可以随时“跳回”到任何一个历史点查看当时的输入、输出和完整状态 。",[131,1601],{},[240,1603,1604],{"start":351},[44,1605,1606],{},[30,1607,1608],{},"状态编辑 (Forking & Editing)：手动纠偏",[24,1610,1611],{},"这是 LangGraph 最具威力的特性之一。当你发现 Agent 走偏了，你可以直接干预它的“大脑状态” 。",[41,1613,1614,1624],{},[44,1615,1616,1619,1620,1623],{},[30,1617,1618],{},"应用场景：纠正 AI 偏差。"," 比如一个写代码的 Agent 在第二步选错了库，你不需要等它报错，可以直接暂停，把状态里的 ",[51,1621,1622],{},"library_name"," 改成正确的，然后让它继续执行 。",[44,1625,1626,1629],{},[30,1627,1628],{},"操作流程：",[240,1630,1631,1637,1643,1652],{},[44,1632,1633,1636],{},[30,1634,1635],{},"暂停："," 结合上一节的断点机制，让 Agent 停下。",[44,1638,1639,1642],{},[30,1640,1641],{},"获取状态："," 拿到当前或历史的状态快照。",[44,1644,1645,934,1648,1651],{},[30,1646,1647],{},"更新状态：",[51,1649,1650],{},"app.update_state(config, {\"key\": \"new_value\"})"," 强行覆盖 。",[44,1653,1654,1657],{},[30,1655,1656],{},"分叉执行（Forking）："," 从这个修改后的点重新启动图，Agent 会带着你给它的“新指令”继续往下走 。",[131,1659],{},[24,1661,1662,1663],{},"这个问题问得非常到位！这正是从“实验原型”转向“生产级应用”时最核心的挑战：",[30,1664,1665],{},"如果开发者和用户看不见 Agent 的思考过程，那么所谓的“时光倒流”和“状态编辑”就只是空中楼阁。",[24,1667,1668,1669,1672],{},"要让这些机制真正发挥作用，我们需要通过可观测性（Observability）",[30,1670,1671],{},"和","防御性设计（Defensive Design）来捕捉那些“隐形”的错误。",[131,1674],{},[240,1676,1677],{"start":423},[44,1678,1679],{},[30,1680,1681],{},"捕捉隐形错误：如何让“纠错机制”落地？",[24,1683,1684],{},"在一个真实的工作流中，我们通常采用以下四种策略来发现并追溯错误状态。",[24,1686,1687,1688],{},"1️⃣ ",[30,1689,1690],{},"节点级流式输出 (Node-level Streaming) —— “看得见”的思考",[24,1692,1693,1694,1697,1698,1701],{},"开发者不能等到 ",[51,1695,1696],{},"invoke()"," 全部结束才看结果。LangGraph 支持",[30,1699,1700],{},"流式更新","，允许你实时查看每一个节点的输入输出。",[41,1703,1704,1712,1722],{},[44,1705,1706,934,1709,39],{},[30,1707,1708],{},"实现方法：",[51,1710,1711],{},"app.stream()",[44,1713,1714,1717,1718,912],{},[30,1715,1716],{},"应用逻辑："," 在 UI 界面或开发者控制台中，实时打印出：",[1719,1720,1721],"em",{},"“当前 Agent 选择了工具 X，参数为 Y”",[44,1723,1724,1727,1728,39],{},[30,1725,1726],{},"价值："," 就像看进度条一样，开发者或用户一旦在屏幕上看到 Agent 选错了工具，可以立即通过 UI 触发",[30,1729,1730],{},"手动中断",[131,1732],{},[24,1734,1735],{},[30,1736,1737],{},"2️⃣ 引入“校验节点” (Validation Nodes) —— “内行”看门道",[24,1739,1740,1741,39],{},"不要指望 Agent 每次都能完美自省。我们可以在图中显式地设计一个",[30,1742,1743],{},"评审节点（Critic/Validator Node）",[41,1745,1746,1752],{},[44,1747,1748,1751],{},[30,1749,1750],{},"机制："," 在“工具调用”节点之后，紧跟一个“校验”节点。",[44,1753,1754,1757],{},[30,1755,1756],{},"逻辑示例：",[41,1758,1759,1765,1774],{},[44,1760,1761,1764],{},[51,1762,1763],{},"Tool_Call_Node"," 执行完毕。",[44,1766,1767,1770,1771],{},[51,1768,1769],{},"Validator_Node"," 检查：",[1719,1772,1773],{},"“调用的工具是否符合安全规范？输出结果是否包含预期关键词？”",[44,1775,1776,1779,1780,1783,1784,1786,1787,1790],{},[30,1777,1778],{},"发现错误："," 如果校验失败，该节点直接抛出异常触发",[30,1781,1782],{},"动态断点","，或者将 ",[51,1785,127],{}," 标记为 ",[51,1788,1789],{},"error"," 并引导流向人类审批节点 。",[131,1792],{},[24,1794,1795],{},[30,1796,1797],{},"3️⃣ 利用 LangSmith 进行深度回溯 —— “监控录像”回放",[24,1799,1800,1801,1804],{},"对于开发者来说，",[30,1802,1803],{},"LangSmith"," 是追溯错误的最强利器。",[41,1806,1807,1813,1826],{},[44,1808,1809,1812],{},[30,1810,1811],{},"追踪全链路："," LangSmith 会记录下 Graph 中每一个节点的详细 Trace（追踪记录），包括 Prompt、原始 LLM 响应和工具返回结果。",[44,1814,1815,1818,1819,1822,1823,1825],{},[30,1816,1817],{},"错误追溯："," 当用户反馈结果不对时，开发者可以通过 ",[51,1820,1821],{},"thread_id"," 在 LangSmith 中找到那一轮对话，定位是哪一个节点的 ",[51,1824,127],{}," 出现了偏差。",[44,1827,1828,1831,1832,1834,1835,1838],{},[30,1829,1830],{},"配合时光倒流："," 找到出错的 ",[51,1833,1598],{}," 后，开发者可以在本地环境使用 ",[51,1836,1837],{},"app.update_state"," 模拟修复，测试改写历史后能否得到正确答案。",[131,1840],{},[24,1842,1843],{},[30,1844,1845],{},"4️⃣ 动态断点与置信度拦截 —— “自知之明”",[24,1847,1848],{},"这是最智能的方式：让 Agent 在“拿不准”的时候自己停下来。",[41,1850,1851,1857,1865],{},[44,1852,1853,1856],{},[30,1854,1855],{},"置信度检查："," 编写 Node 逻辑时，要求 LLM 输出一个置信分数。",[44,1858,1859,83,1862,912],{},[30,1860,1861],{},"逻辑：",[51,1863,1864],{},"if confidence \u003C 0.8: raise NodeInterrupt(\"AI不确定，请人工检查状态\")",[44,1866,1867,1870,1871,912],{},[30,1868,1869],{},"用户感知："," 此时前端界面会弹出一个对话框，显示当前的中间状态（State），并询问用户：",[1719,1872,1873],{},"“我准备使用工具 A 来处理，你觉得对吗？”",[131,1875],{},[24,1877,1878],{},[30,1879,1880],{},"🛠️ 资深工程师的应用模式：用户纠偏流程",[24,1882,1883,1884,1887],{},"在生产环境中，这个机制通常被包装成一个 ",[30,1885,1886],{},"“撤销/修改”"," 按钮：",[240,1889,1890,1896,1901,1907],{},[44,1891,1892,1895],{},[30,1893,1894],{},"用户观察："," UI 显示 Agent 正在执行“查询数据库”动作。",[44,1897,1898,1900],{},[30,1899,1778],{}," 用户发现 Agent 理解错了条件（比如把“去年的报表”理解成了“去年的工资单”）。",[44,1902,1903,1906],{},[30,1904,1905],{},"触发动作："," 用户点击“暂停并修正”。",[44,1908,1909,1912],{},[30,1910,1911],{},"后台操作：",[41,1913,1914,1920,1923,1929],{},[44,1915,1916,1917,1919],{},"前端调用 ",[51,1918,1379],{}," 获取当前 State。",[44,1921,1922],{},"用户在界面修改错误的查询条件。",[44,1924,1925,1926,912],{},"后端调用 ",[51,1927,1928],{},"app.update_state(config, {\"query\": \"正确的条件\"})",[44,1930,1925,1931,1933],{},[51,1932,1389],{}," 让 Agent 带着正确的指令重跑该步骤 。",[131,1935],{},[19,1937],{"id":405},[1939,1940,1941],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":405,"searchDepth":423,"depth":429,"links":1943},[1944,1949],{"id":16,"depth":351,"text":17,"children":1945},[1946,1947,1948],{"id":21,"depth":423,"text":22},{"id":285,"depth":423,"text":286},{"id":859,"depth":423,"text":860},{"id":1188,"depth":351,"text":1189,"children":1950},[1951,1952,1953],{"id":1192,"depth":423,"text":1193},{"id":1557,"depth":423,"text":1558},{"id":405,"depth":423,"text":405},"从线到环","md",{"date":1957,"image":1958,"alt":5,"tags":1959,"published":534,"trending":534},"2026/4/20","/blogs-img/Langgraph.jpg",[1960],"agent","/docs/langgraph",{"title":5,"description":1954},"docs/LangGraph","dCWFi69V3tYj_OdcqGHxyuWckV9eJ72wn_i-o5THf-A",1778575222796]