L angGraph vs 链式调用

概述

learn-langgraph

使用链式调用 也能跑通Demo,但是一旦加入条件分支、循环、错误恢复、代码就变成迷宫,因为代码逻辑变得复杂了

LangGraph用图结构来描述Agent的执行逻辑,有边和节点,节点就代表操作,边就是转移,状态是贯穿全程的上下文。

这不只是换了一种说法,而是完全换了一种思维模式------从怎么写代码变成怎么设计状态机


包含了什么

章节 文章 学到了什么
1 LangGraph是什么,为什么不用链式法则 链式调用的局限性,图结构的优势,核心抽象
2 State Node Graph三件套 TypedDict状态设计,节点函数签名,编译和运行
3 顺序图:第一个可运行的workflow add_edge模式,多节点顺序流,实战BMI
4 条件分支:add_conditional_edges 路由函数,情感分析路由,Pydantic结构化输出
5 并行执行:Fan-out/Fan-in 多节点同时启动,汇聚节点,cricket统计实战
6 Prompt Chaining:分布生成 拆解生成任务,节点间传递中间结果,HuggingFace集成
7 接入LLMOpenAIHuggingFace ChatOpenAI,HuggingFaceEndPoint在节点里调用模型

前置知识

  • 读过Agent Basic或者知道什么是Agent、Tool Calling
  • 会写基本的python,知道TypedDict是什么
  • 不需要LangGraph经验

LangGraph是什么,为什么要用LangGraph不用链式调用

链式调用能做什么,做不到什么

在早期的时候大家开发LLM应用的时候都是使用链式调用:

python 复制代码
# 上一次模型执行输出的结果作为下一次模型的输入,作为下一次模型的提示词
response1 = llm.invoke(prompt1) # 括号里面的内容就是向LLM输入的内容
response2 = llm.invoke(prompt2 + response1.content) # 向LLm输入的是第一次执行的结果和第二次输入的prompt2
response3 = llm.invoke(prompt3 + response2.content)

举个例子:

python 复制代码
# 代码
prompt1 = "总结西游记"
response1 = llm.invoke(prompt1)
prompt2 = "把下面内容翻译成英文"
response2 = llm.invoke(prompt2 + response1)
prompt3 = "把英文压缩成一句话"
response3 = llm.invoke(prompt3 + resposne2)
python 复制代码
# 输出
response1.content = "西游记讲述了....."
response2.content = "把response1的内容翻译成英文:the west jounery illustrate...."
response3.content = "......"

为什么叫链式调用

复制代码
Prompt1
 ↓
LLM
 ↓
response1
 ↓
Prompt2 + response1
 ↓
LLM
 ↓
response2
 ↓
Prompt3 + response2
 ↓
LLM
 ↓
response3

像链条一样,逐步往下执行,依赖于上一步的执行结果

链式法则直观、简单、能跑通大部份的简单情况下的Demo

但是如果你的业务复杂了,分支多了,要进行判断选择就会出问题:

  • 没有条件分支 :如果要根据不同情况来进行选择操作的话,就只能使用if-else语句来实现,就像不使用设计模式来进行开发,一直使用if-else来判断,拓展性不好,而且很冗余

    代码就会不断膨胀,而且每次修改分支逻辑都要找对位置进行修改

    python 复制代码
    # 例子:根据天气状况来选择走不同的分支
    weather = analyze_weather(usser_input)
    if weather == "sunny" : 
      response = sunny_handler(user_input)
    elif weather == "raniny" : 
      response = rainy_handler(user_input)
    else : 
      response = cloudy_handler(user_input)
     # 如果还有其他情况,就继续加分支,就再加一层if-else
  • 没有循环和重试 :如果在执行过程中某个步骤失败了,在链式调用里只能自己手动实现,手写while true然后break,状态要自己传来传去

  • 状态管理混乱:在链式调用里,数据通过变量进行传递,一旦有多个分支、多个节点,你就需要手动决定哪么变量传给哪个步骤,代码的全局状态就变成了隐式依赖,就会出现变量传错的情况


LangGraph的核心思路

把执行流描述成一张图Graph

节点Node = 一个操作(函数)

edge = 节点之间的转移

状态State = 贯穿整张图的数据容器

执行过程:从起点出发,按边走过各个节点,每个节点读取状态,更新状态,最终到达终点

用图来描述的好处:

  • 条件分支变成了路由函数 ------不再使用if-else,而是一个返回下一个节点名的函数
  • 函数变成图里的环------节点之间是双向连接的,节点可以返回之前的节点,天然就支持重试和迭代
  • 状态是显式的------所有节点共享同一个状态对象,谁改了什么一目了然
  • 可视化------图结构可以直接画出来,方便理解和调试

三个概念

State状态

pythonTypedDict定义,是整张图共享的内存,是一个全局的State

python 复制代码
from typing import TypedDict

# 定义State
class AgentState(TypedDict) : 
		user_input : str
    sentiment  : str
    reply : str
   

图里的每一个节点都要接收读取这个State,然后根据执行结果进行修改后返回新的State


Node节点

节点就是普通的python函数,具体进行的是什么操作,签名是(state : state) -> dict :

python 复制代码
def analyze_node(state: AgentState) -> dict:
  # 读取State(状态)
  text = state["user_input"]
  # 做处理
  sentiment = "positive" if "好" in text else "negative"
  # 返回需要更新的字段
  return {"sentiment" : sentiment}

返回的dict会被合并到全局state中,不需要返回完整的state对象


Graph

把节点和边组装起来:就形成图了

python 复制代码
# 导入StateGraph创建状态图 END表示流程结束了
from langgrph.graph import StateGraph, END

# 创建图,整个流程里共享的数据是AgentState,AgentState是一个贯穿全流程的状态,就是前面使用TypedDict来定义的状态
graph = StateGraph(AgentState)
# 下面两个节点都是共享同一个State,对同一个State进行操作,这个State在节点之间是互相传递的
graph.add_node("analyze", analyze_node) # analyze节点,执行函数是analyze_node
graph.add_node("reply", reply_node) # reply节点

graph.set_entry_point("analyze") # 设置节点入口,整个流程从analyze开始
# 添加边,analyze执行完后会自动进入到reply中,reply执行结束后,就END执行结束了
graph.add_edge("analyze", "reply")
graph.add_edge("reply", END)

app = graph.compile() # 编译图,把前面定义的流程图变成可以编译的程序,得到一个可执行的app,可以使用invoke进行运行
python 复制代码
result = app.invoke({"user_input": "今天很开心"})
print(result)

和链式调用的对比

纬度 链式调用 langgraph
分支逻辑 if-else散落在代码里 add_coonditional_edge集中管理
循环/重试 手写while状态容易混乱 图里天然就支持重试和迭代
状态管理 变量传递,隐式依赖,手动管理的 使用TypedDict来定义一个全局的State,显式类型安全
可维护性 流程越复杂越难改 改节点不影响其他节点
调试 print大法 可视化图结构,节点独立测试

什么时候使用LangGraph

  • 执行流程有条件分支,需要根据LLM的输出来决定走那条分支
  • 需要循环迭代,Agent需要多次调用工具直到完成目标
  • 多个Agent协作,每一个Agent就是一个节点
  • 需要human-in-the-loop,暂停等待人工确认的操作
  • 需要错误调试和重试

什么时候不需要LangGraph

  • 单次LLM调用
  • 固定的线性流程(prompt -> response -> done)
  • 非常简单的两步chain

总结

LangGraph的核心:当Agent的执行逻辑复杂后怎么让代码变得可维护

简单场景链式调用更快更高效

需要分支处理、循环迭代、重试的复杂的业务逻辑场景下LangGraph更高效

LangGraph是什么,核心组成,如何实现一个简单的LangGraph


State Node Graph三件套

LangGraph是由这三个东西构成的:State状态、Node节点、Graph图,在上面粗略讲结果,在这里就详细讲解一下,后面所有的实例都是建立在这个基础上的


pycharm或者终端进行安装

dash 复制代码
pip install langgraph langchain-core

如果要用open-ai

复制代码
pip install langchain-openai

State图的内存

State是整张图共享的数据容器,贯穿整张图的所有流程,所有的节点都从State中读取数据,然后修改,最后更新


TypedDict定义State

python 复制代码
from typing import typedDict, List, Optional

class MyState(TypedDict) : 
  user_input : str # 用户输入
  analysis : str # 中间结果
  result : str # 中间结果
  error : Optional[str] # 可选字段,error要么是Node,要么是str

TypedDictpython标准库里的类型,,让字典有了类型检查,LangGraph用它来追踪状态结构


节点返回Dict,而不是返回完整的State

每次执行完节点的操作后只会返回修改的字段,然后更新会全局State中,不会返回完整的State

python 复制代码
def my_node(state : MyState) -> dict : 
  # 只会返回需要更新的字段,不会返回完整的State
  return {"analysis" : "positive"}

使用Annotated + operator.add追加列表

如果某个字段是列表,想追加而不是覆盖

python 复制代码
from typing import Annotated
import operator

class ChatState(TypedDict) : 
  messages : Annotated[List[str], operator.add]
  summary : str

这样每个节点返回的{"messages"}:["新消息"]}就会被追加到列表中,而不是替换整个列表,就不会被覆盖


Node界定------图的操作单元

节点就是普通的python函数,签名固定:

python 复制代码
def node_name(state : YourState) -> dict : 
  value = state["some_field"] # 读取状态
  result = process(value) # 做处理
  return {"another_field" : result} # 返回需要更新的字段

节点的几个原则
  • **节点只做一件事:**每个节点只聚焦于一个职责,不要把分析、调用API、格式化输出、重试都塞到一个节点中

  • **节点尽可能是纯的:**理想情况下节点的输出只依赖State输出,没有隐藏的全局状态,这样更容易测试和调试

  • 返回dict,不是state对象:

    python 复制代码
    # 正确的返回形式
    return {"field_a" : value_a, "field_b" : value_b}
    
    # 错误的返回形式
    return state

Graph图------把节点连起来

python 复制代码
from langgraph.graph import StateGraph, END, START

# 1.创建图,指定State的类型
graph = StateGraph(MyState)

# 2.添加节点
graph.add_node("node_a", function_a)
graph.add_node("node_b", function_b)
graph.add_node("node_c", function_c)

# 3.设置节点入口
# 其实就等价于:graph.set_entry_point(START, "node_a")
graph.set_entry_point("node_a")

# 4.添加边
graph.add_edge("node_a", "node_b")
graph.add_edge("node_b", "node_c")
graph.add_edge("node_c", "END")

# 5.编译
app = graph.compile()

运行图

python 复制代码
# 通过invoke同步运行, 返回最终state
result = app.invoke({"user_input" : "hello"})
print(result)

# stream流式运行,每个节点完成后返回一次
for chunk in app.stream({"user_input" : "hello"}) : 
  print(chunk)

invoke 返回的是最终的完整 state 字典。stream 每次 yield 一个 {node_name: state_update} 的字典。


完整示例:文本处理管道

python 复制代码
from typing import TypedDict
from langgraph.graph import StateGraph, END

# 1. 定义 State
class TextState(TypedDict):
    raw_text: str
    cleaned: str
    word_count: int
    summary: str

# 2. 定义节点
def clean_node(state: TextState) -> dict:
    """清理文本:去掉多余空格"""
    text = state["raw_text"].strip()
    return {"cleaned": text}

def count_node(state: TextState) -> dict:
    """统计词数"""
    count = len(state["cleaned"].split())
    return {"word_count": count}

def summary_node(state: TextState) -> dict:
    """生成摘要(这里简化为截断)"""
    text = state["cleaned"]
    summary = text[:50] + "..." if len(text) > 50 else text
    return {"summary": summary}


# 3. 组装图
graph = StateGraph(TextState)
graph.add_node("clean", clean_node)
graph.add_node("count", count_node)
graph.add_node("summarize", summary_node)

graph.set_entry_point("clean")
graph.add_edge("clean", "count")
graph.add_edge("count", "summarize")
graph.add_edge("summarize", END)

app = graph.compile()

# 4. 运行
result = app.invoke({
    "raw_text": "  LangGraph 是一个用于构建有状态 Agent 应用的框架,基于图结构描述执行流。  ",
    "cleaned": "",
    "word_count": 0,
    "summary": ""
})

print("清理后:", result["cleaned"])
print("词数:", result["word_count"])
print("摘要:", result["summary"])

输出:

清理后: LangGraph 是一个用于构建有状态 Agent 应用的框架,基于图结构描述执行流。 词数: 17 摘要: LangGraph 是一个用于构建有状态 Agent 应用的框架,基于图结构描述执行流。


可视化图结构

LangGraph可以把图渲染成ASCII或图片

python 复制代码
# ASCII可视化
print(app.get_graph().draw_ascii())

# 输出大致如下
# +-----------+
# | __start__ |
# +-----------+
#       |
#     clean
#       |
#     count
#       |
#   summarize
#       |
# +---------+
# | __end__ |
# +---------+

总结

三件套关系:

  • State是全局数据,所有节点共享,TypedDict定义结构
  • Node是操作/函数,读StateState,普通函数
  • Graph是流程,把节点用边连起来,然后compile()进行编译运行

记住这三条规则:

  1. 节点只返回要修改的字段(dict),不返回完整 state
  2. 边决定执行顺序,END 是终止信号
  3. invoke 传入的是初始 state(dict),返回的是最终 state(dict)

顺序图------第一个可运行Workflow

顺序图是LangGraph里最简单的模式:节点A执行完,接着执行节点B,再执行节点C,没有分支,没有循环

这里我们用一个BMI计算器来演示,因为他的逻辑足够清晰:输入->计算->分类->输出


为什么从顺序图开始

顺序图没有额外的复杂度,是最简单的,可以让你专注于LangGraph的基本操作:

  • 怎么设计State
  • 怎么写节点Node
  • 怎么用add_edge连接节点
  • 怎么运行和查看结果

顺序图是暂时没有进行条件分支的处理的,条件分支只是在顺序图的基础上加一个路由函数


BMI计算器:需求

输入:用户身高cm、体重kg、用户姓名

步骤:

  1. 验证输入
  2. 计算BMI
  3. 根据BMI进行分类(偏瘦/正常/超重/肥胖)
  4. 生成健康建议
  5. 格式化输出报告

代码实现

python 复制代码
from typing import TypedDict, Optional
from langgraph.graph import StateGraph, END

# State的定义
class BMIState(TypedDict) : 
  name : str
  height_cm : float
  weight_kg : float
  bmi : float
  category : str
  advice : str
  report : str
  error : Optional[str]
  
 # 定义节点
# 1.验证输入的数据
def validate_input(state : BMIState) -> dict : 
  """验证输入的数据"""
  height = state["height_cm"]
  weight = state["weight_cm"]
  if height <= 0 or height > 300:
        return {"error": f"身高数据异常: {height}cm"}
    if weight <= 0 or weight > 500:
        return {"error": f"体重数据异常: {weight}kg"}
      
      return {"erroe" : None}

# 2.计算BMI
def calculate_bmi(state : BMIState) -> dict : 
  """计算BMI"""
  if state.get("error") : 
    return {}
  
  height_m = state["height_cm"] / 100
  bmi = state["weight_kg"] / (height_m ** 2)
  bmi = round(bmi, 2)
  return {"bmi", bmi}

# 3.进行分类
def classify_bmi(state : BMIState) -> dict : 
  """BMI分类"""
  if state.get("error") : 
    return{}
  
  bmi = state["bmi"]
  if bmi < 18.5 : 
    category = "偏瘦"
  elif bmi < 24.9 : 
    category = "正常"
  elif bmi < 29.9 : 
    category = "超重"
  else : 
    category = "肥胖"
    
  return {"category", category}

# 4.生成健康报告
def generate_radvice(state : BMIState) -> dict :
  """生成健康报告"""
  if state.get("error") : 
    return {}
  
  category = state["category"]
  advice_map = {
    "偏瘦" : "建议适量增加营养摄入,加强力量训练,必要时咨询营养师"
    "正常" : "保持当前饮食和运动习惯,定期体检"
    "超重" : "控制饮食,每天进行两小时运动"
    "肥胖" : "在医生指导下进行减肥"
  }
  return {"advice", advice_map[category]}

# 5.格式化结果输出
def format_report(state : BMIState) -> dict : 
  """生成最终报告"""
  if state.get("error") : 
    report = f"错误:{state['error']}"
   else : 
    report = f"""
    ===BMI健康报告===
    姓名:{state['name']}
    身高:{state['height_cm']}cm
    体重:{state['wright_kg']}kg
    BMI:{state['bmi']}
    分类:{state['category']}
    建议:{statet['adice']}
    """.strip()
    
    	return {"repost" : repost}
    
# 组装图
graph = StateGraph(BMIState)

# 添加节点
graph.add_node("validate", validate_input)
graph.add_node("calculate", calculate_bmi)
graph.add_node("classify", classify_bmi)
graph.add_node("advise", generate_advice)
graph.add_node("format", format_report)
# 设置节点入口
graph.set_entry_point("validate")

# 添加边,串联节点
graph.add_edge("validate", "calculate")
graph.add_edge("calculate", "classify")
graph.add_edge("classify", "advise")
graph.add_edge("advise", "format")
graph.add_edge("format", END)

# 编译运行输出结果
app = graph.compile()

运行结果

python 复制代码
# 正常输入
result = app.invoke({
  "name" : "zhangsan",
  "height_cm" : 175.0,
  "weight_kg" : 70.0,
  "bmi" : 0.0,
  "category" : "",
  "advice" : "",
  "report" : "",
  "error" : None
})

	print(result["report"])
  

# 输出
=== BMI 健康报告 ===
姓名:张三
身高:175.0 cm
体重:70.0 kg
BMI:22.86
分类:正常
建议:保持当前的饮食和运动习惯,定期体检。
python 复制代码
# 异常输入
result = app.invoke({
    "name": "李四",
    "height_cm": -10.0,
    "weight_kg": 60.0,
    "bmi": 0.0,
    "category": "",
    "advice": "",
    "report": "",
    "error": None,
})
print(result["report"])
# 输出:错误:身高数据异常: -10.0cm 
# 不会输出格式化报告

stream去观察每个节点的输出

python 复制代码
# 正常输入
for step in app.stream({
    "name": "王五",
    "height_cm": 160.0,
    "weight_kg": 80.0,
    "bmi": 0.0,
    "category": "",
    "advice": "",
    "report": "",
    "error": None,
}):
  node_name, state_update = list(step.items())[0]
  print(f"[{node_name}]{state_update}")
  
 # 输出
[validate] {'error': None}
[calculate] {'bmi': 31.25}
[classify] {'category': '肥胖'}
[advise] {'advice': '建议在医生指导下制定减重计划,注意饮食结构和规律运动。'}
[format] {'report': '=== BMI 健康报告 ===\n...'}

使用stream进行运行的话,每个节点输出的是他修改的字段,不是完整的state,这就是stream模式的用法,可以实时看见每一步的结果


顺序图的图结构

复制代码
START
  |
validate
  |
calculate
  |
classify
  |
advise
  |
format
  |
END

五个节点五条边,没有分支没有交叉,add_edge(A, B)的意识是:节点A完成后,无条件去执行节点B


初始State如何设置

在调用invoke进行运行的时候需要传入完整的State,即需要传入所有的字段

因为TypedDict要求字段完整,在实际项目中通常有两种处理方式:

optional加默认值:这里初始state里未知字段传入Node即可

python 复制代码
class BMIState(TypedDict):
    name: str
    height_cm: float
    weight_kg: float
    bmi: Optional[float]      # 允许为 None
    category: Optional[str]
    advice: Optional[str]
    report: Optional[str]
    error: Optional[str]

total=False:使得所有字段都是可选的

python 复制代码
class BMIState(TypedDict, total=False):
    name: str
    height_cm: float
    # ...其他字段不是必填的

总结

顺序图的要点:

  • add_edge(A, B) = A 完成后执行 B,无条件
  • set_entry_point("node_name") = 从哪个节点开始
  • add_edge("last_node", END) = 告诉 LangGraph 到这里结束
  • invoke 运行图,传入初始 state,返回最终 state
  • stream 流式运行,每个节点完成后 yield 一次

条件分支add_conditional_edges--上面的案例是没有分支的,在这里加上条件分支

在顺序图里面每条边都是固定的,A执行完后必然会无条件去执行B。但是在真实的Agent中是需要根据每一次的运行结果决定下一步该如何走的------用户反馈是正面走一条路,负面走另外一条路

所以在LangGraph中就使用add_conditional_edges来处理这种情况


核心API

python 复制代码
graph.add_conditional_edges(
  "source_node", # 从哪个节点出发
  routing_function, #路由函数:接收state,返回下一个节点的名字
  {
    "route_a" : "node_a", # 路由函数返回"route_a"时走node_a
    "route_b" : "node_b", # 路由函数返回"route_a"时走node_a
    "end" : END, # 路由函数返回"end"时走end结束
  }
)
python 复制代码
# 路由函数就是一个普通的python函数:
def routing_function(state : MyState) -> dict : 
  if state["some_condition"]:
    return "route_a"
  else : 
    return "route_b"

他接收当前State会返回一个字符串key

LangGraph用这个key从映射表里找到下一个节点


用户反馈路由

客服机器人系统根据收到用户的反馈,根据用户不同的情绪走不同的处理流程

rust 复制代码
用户输入
  |
情绪分析
  |
  +--正面--->感谢回复
  |
  +--负面-->道歉赔偿
  |
  +--中性-->标准回复
  |
最终格式化输出

具体实现

python 复制代码
from typing import TypedDict
from langgraph.graph import StateGraph, END

class FeedbackState(TypedDict):
    user_input: str
    sentiment: str      # "positive" / "negative" / "neutral"
    response: str
    final_output: str
    
def analyze_sentiment(state: FeedbackState) -> dict:
    """情绪分析(简化版,实际可以用 LLM)"""
    text = state["user_input"].lower()

    positive_words = ["好", "棒", "满意", "喜欢", "excellent", "great", "happy"]
    negative_words = ["差", "烂", "不满", "投诉", "terrible", "bad", "angry"]

    pos_count = sum(1 for w in positive_words if w in text)
    neg_count = sum(1 for w in negative_words if w in text)

    if pos_count > neg_count:
        sentiment = "positive"
    elif neg_count > pos_count:
        sentiment = "negative"
    else:
        sentiment = "neutral"

    return {"sentiment": sentiment}

def handle_positive(state: FeedbackState) -> dict:
    """处理正面反馈"""
    response = f"感谢您的好评!很高兴我们的服务让您满意。您的反馈:'{state['user_input']}'"
    return {"response": response}

def handle_negative(state: FeedbackState) -> dict:
    """处理负面反馈"""
    response = (
        f"非常抱歉给您带来了不好的体验!"
        f"您的反馈已记录:'{state['user_input']}'。"
        f"我们的高级客服将在 24 小时内联系您。"
    )
    return {"response": response}

def handle_neutral(state: FeedbackState) -> dict:
    """处理中性反馈"""
    response = f"感谢您的反馈:'{state['user_input']}'。我们会继续改进服务。"
    return {"response": response}
  
def format_output(state: FeedbackState) -> dict:
    """格式化最终输出"""
    output = f"[情绪:{state['sentiment']}]\n{state['response']}"
    return {"final_output": output}

def route_by_sentiment(state: FeedbackState) -> str:
    """根据情绪返回路由 key"""
    return state["sentiment"]  # 直接返回 "positive" / "negative" / "neutral"

graph = StateGraph(FeedbackState)

# 已经不再是一连串往下的逻辑了,是有分支的
graph.add_node("analyze", analyze_sentiment)
graph.add_node("positive_handler", handle_positive)
graph.add_node("negative_handler", handle_negative)
graph.add_node("neutral_handler", handle_neutral)
graph.add_node("format", format_output)

graph.set_entry_point("analyze")
  
  
graph.add_conditional_edges(
    "analyze",
    route_by_sentiment,
    {
        "positive": "positive_handler",
        "negative": "negative_handler",
        "neutral": "neutral_handler",
    }
)

# 三个分支最终都汇入 format
graph.add_edge("positive_handler", "format")
graph.add_edge("negative_handler", "format")
graph.add_edge("neutral_handler", "format")
graph.add_edge("format", END)

app = graph.compile()

运行

python 复制代码
# 正面反馈
r = app.invoke({
    "user_input": "产品很棒,服务也很满意!",
    "sentiment": "",
    "response": "",
    "final_output": ""
})
print(r["final_output"])

# 负面反馈
r = app.invoke({
    "user_input": "太差了,完全不满意,要投诉!",
    "sentiment": "",
    "response": "",
    "final_output": ""
})
print(r["final_output"])
# [情绪:negative]
# 非常抱歉给您带来了不好的体验!...

图结构

rust 复制代码
START
  |
analyze
  |
  +-- positive --> positive_handler --> format --> END
  |
  +-- negative --> negative_handler --> format --> END
  |
  +-- neutral  --> neutral_handler  --> format --> END

三条分支汇入同一个format节点------这叫 Fan-out + Fan-in,是多分支合并的标准模式。


配合llm做情绪分析

在实际的项目中,analyze_sentiment会调用llm来进行处理:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

def analyze_sentiment(state: FeedbackState) -> dict:
    prompt = f"""分析以下文本的情绪,只回复 "positive"、"negative" 或 "neutral" 之一。

文本:{state['user_input']}"""

    response = llm.invoke([HumanMessage(content=prompt)])
    sentiment = response.content.strip().lower()

    # 容错处理
    if sentiment not in ["positive", "negative", "neutral"]:
        sentiment = "neutral"

    return {"sentiment": sentiment}

路由函数本身不需要改,它只看 state 里的sentiment字段

节点:负责更新状态

路由函数负责读取状态做出决策


Pydantic结构化输出

如果你想用PydanticLLM输出更稳定可靠

python 复制代码
from pydantic import BaseModel
from langchain_openai import ChatOpenAI

class SentimentOutput(BaseModel):
    sentiment: str     # "positive" / "negative" / "neutral"
    confidence: float  # 0.0 ~ 1.0
    reason: str        # 判断原因

llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(SentimentOutput)

def analyze_sentiment(state: FeedbackState) -> dict:
    result = structured_llm.invoke(
        f"分析情绪:{state['user_input']}"
    )
    return {
        "sentiment": result.sentiment,
        "confidence": result.confidence,
    }

with_structured_outputLLM返回结构化的Pydantic对象,而不是纯文本,这样的路由函数拿到的字段更可靠


条件分支 VS if-else

为什么要用条件分支呢,if-else不也能达到同样的效果吗

理论上可以,但是有问题:

  • 可见性 :使用add_conditional_edges可以让图结构可以被可视化,if-else隐藏在函数内部看不出来
  • 可测试性 :路由函数可以单独测试,不会依赖完整的节点执行,而if-else是嵌入在具体代码中的
  • 可维护性:加一条新分支只需要加一个节点+在映射表里加一列,不需要修改现有逻辑

就是跟java设计模式一个道理,为什么在实现业务功能的时候不用if-else for while等这么直白的操作,就是为了便于后续的的维护、功能拓展、解耦合


总结

  • add_conditional_edges(source, routing_fn, mapping) LangGraph 的条件分支 API
  • 路由函数返回字符串 keymapping 表把 key 映射到节点名
  • 路由函数只读state,不做操作------决策和操作分离
  • 多个分支可以汇入同一个节点Fan-in

并行执行 : Fan-out / Fan- in

在顺序图中是按照节点一个个执行的,但是有些任务天然是可以并行执行的,例如同时获取多个数据源,或者同时做多个纬度的分析

LangGraph支持并行执行:从一个节点出发,可以同时触发多个节点去执行Fan-out,等他们执行完成后,就会汇聚到一个节点中进行处理结果Fan-in


Fan-out / Fan-in模式

复制代码
         START
           |
       [获取输入]
      /    |    \
  [A]    [B]    [C]     <- 并行执行
      \    |    /
       [汇聚节点]
           |
          END

LangGraph里,实现并行是很简单的:就是让多个节点都从一个节点出发

python 复制代码
# 从 "fetch_input" 同时出发到三个节点
# Fan-out
graph.add_edge("fetch_input", "node_a")
graph.add_edge("fetch_input", "node_b")
graph.add_edge("fetch_input", "node_c")

# 三个节点都指向汇聚节点
# Fan-in
graph.add_edge("node_a", "aggregate")
graph.add_edge("node_b", "aggregate")
graph.add_edge("node_c", "aggregate")

LangGraph会自动检测到`node_a node_b node_c可以同时执行,并行运行他们


状态合并:用Annotated + operator.add

并行节点都会更新StateLangGraph需要知道怎么合并这些并行节点的执行结果

对于列表字段,用Annotated + operator.add来追加,这个在前面也讲过

python 复制代码
from typing import TypedDict, Annotated, List
import operator

class ParallelState(TypedDict):
    input: str
    results: Annotated[List[str], operator.add]  # 并行结果追加到列表
    summary: str

每一个并行节点返回执行结果{"result" : ["自己的结果"]}

LangGraph把这些列表拼接起来,不是覆盖,是追加


例子: 篮球运动员综合评估

场景:从三个纬度去分析一个篮球运动员,同时去进行分析:三分表现、防守表现、抢断表现,最后进行汇总返回结果

python 复制代码
from typing import TypedDict, Annotated, List
import operator
from langgraph.graph import StateGraph, END

# ===== State =====
class CricketState(TypedDict):
    player_name: str
    batting_avg: float
    bowling_avg: float
    fielding_rating: float
    analyses: Annotated[List[str], operator.add]  # 三个并行节点的输出
    final_report: str

# ===== 并行节点 =====

def analyze_batting(state: CricketState) -> dict:
    """分析击球表现"""
    avg = state["batting_avg"]

    if avg >= 50:
        level = "世界级"
    elif avg >= 35:
        level = "优秀"
    elif avg >= 20:
        level = "一般"
    else:
        level = "较弱"

    analysis = f"[击球] 平均分 {avg},评级:{level}"
    return {"analyses": [analysis]}

def analyze_bowling(state: CricketState) -> dict:
    """分析投球表现(投球均值越低越好)"""
    avg = state["bowling_avg"]

    if avg <= 20:
        level = "世界级"
    elif avg <= 30:
        level = "优秀"
    elif avg <= 40:
        level = "一般"
    else:
        level = "较弱"

    analysis = f"[投球] 平均分 {avg},评级:{level}"
    return {"analyses": [analysis]}

def analyze_fielding(state: CricketState) -> dict:
    """分析防守表现"""
    rating = state["fielding_rating"]

    if rating >= 8:
        level = "出色"
    elif rating >= 6:
        level = "良好"
    elif rating >= 4:
        level = "一般"
    else:
        level = "需改进"

    analysis = f"[防守] 评分 {rating}/10,评级:{level}"
    return {"analyses": [analysis]}

# ===== 汇聚节点 =====

def aggregate_results(state: CricketState) -> dict:
    """汇聚三个分析结果,生成总报告"""
    name = state["player_name"]
    analyses = state["analyses"]

    report = f"=== {name} 综合评估报告 ===\n"
    for a in analyses:
        report += f"  {a}\n"

    # 计算综合评分(简化逻辑)
    score = (
        min(state["batting_avg"] / 50, 1.0) * 40 +
        max(0, (40 - state["bowling_avg"]) / 40) * 40 +
        state["fielding_rating"] / 10 * 20
    )
    report += f"\n综合得分:{score:.1f} / 100"

    return {"final_report": report}

# ===== 入口节点 =====

def start_node(state: CricketState) -> dict:
    """入口节点:什么都不做,只是作为 Fan-out 的起点"""
    return {}

# ===== 组装图 =====

graph = StateGraph(CricketState)

graph.add_node("start", start_node)
graph.add_node("batting", analyze_batting)
graph.add_node("bowling", analyze_bowling)
graph.add_node("fielding", analyze_fielding)
graph.add_node("aggregate", aggregate_results)

graph.set_entry_point("start")

# Fan-out:start 同时触发三个分析节点
graph.add_edge("start", "batting")
graph.add_edge("start", "bowling")
graph.add_edge("start", "fielding")

# Fan-in:三个节点都汇入 aggregate
graph.add_edge("batting", "aggregate")
graph.add_edge("bowling", "aggregate")
graph.add_edge("fielding", "aggregate")

graph.add_edge("aggregate", END)

app = graph.compile()

运行

python 复制代码
result = app.invoke({
    "player_name": "Virat Kohli",
    "batting_avg": 59.8,
    "bowling_avg": 34.0,
    "fielding_rating": 9.0,
    "analyses": [],
    "final_report": "",
})

print(result["final_report"])

输出

python 复制代码
=== Virat Kohli 综合评估报告 ===
  [击球] 平均分 59.8,评级:世界级
  [防守] 评分 9.0/10,评级:出色
  [投球] 平均分 34.0,评级:优秀

综合得分:82.6 / 100

三个节点的分析顺序可能不同,因为他们是并行执行的,但是analyses列表会把他么全都收集进来


并行+条件分支

Fan-outadd_conditional_edes可以混用,例如:

python 复制代码
# 入口节点之后:根据任务类型分流
graph.add_conditional_edges(
    "start",
    route_by_type,
    {
        "simple": "quick_analysis",
        "complex": "detailed_analysis",
    }
)

# 详细分析再 Fan-out 到多个子节点
graph.add_edge("detailed_analysis", "sub_a")
graph.add_edge("detailed_analysis", "sub_b")
graph.add_edge("sub_a", "merge")
graph.add_edge("sub_b", "merge")

并行执行需要注意的点

1. 并行节点不能互相依赖

如果节点 B 的输入依赖节点 A 的输出,它们就不能并行------这是逻辑上的串行依赖。

2. Annotated 列表的顺序不确定

并行节点完成顺序不固定,收集到 Annotated[List, operator.add] 里的顺序也不固定。如果顺序重要,在汇聚节点里排序。

3. 对于普通(非 Annotated)字段

如果多个并行节点都修改同一个普通字段,最后一个完成的节点会覆盖前面的。一般来说并行节点应该各自负责不同的字段。


总结

  • Fan-out:从一个节点出发,用多个 add_edge 同时触发多个节点
  • Fan-in:多个节点都用 add_edge 指向同一个汇聚节点
  • 并行结果用 Annotated[List[T], operator.add] 收集
  • LangGraph 自动检测并行机会,不需要手动管理线程

Prompt Chaining:分布生成

一次性让 LLM 生成完整的长文内容,效果往往不理想------模型容易跑题,或者生成质量参差不齐。

Prompt Chaining 的思路是:把大任务拆成多个步骤,每个步骤用单独的 prompt,上一步的输出作为下一步的输入。每个节点专注一件事,最终串联出高质量的结果。

其实一个prompt对应每一个小任务在刚开始讲的时候就提到过


为什么要对一个大任务进行拆解

一次性生成 vs 分步生成的区别:

方式 优点 缺点
一次性生成 简单快速 长文质量不稳定,难以干预中间过程
Prompt Chaining 每步可控,中间结果可检查 多次 LLM 调用,耗时更长

分步生成适合需要结构化输出的场景:文章生成、报告撰写、代码生成等。


案例:生成博客文章

python 复制代码
from typing import TypedDict
from langgraph.graph import StateGraph, END

# ===== State =====
class BlogState(TypedDict):
    topic: str
    outline: str
    draft: str
    final_article: str

# ===== 节点(不依赖特定 LLM,方便你替换)=====

def generate_outline(state: BlogState) -> dict:
    """第一步:生成文章大纲"""
    topic = state["topic"]

    # 这里用占位符表示 LLM 调用,下面会展示真实代码
    prompt = f"""为以下主题创建一个清晰的博客文章大纲:

主题:{topic}

请提供:
1. 引言要点
2. 3-4 个主要章节标题
3. 结论要点

大纲:"""

    # outline = llm.invoke(prompt)  # 替换成真实 LLM 调用
    # 示例输出(演示用)
    outline = f"""
## {topic} 完整指南

**引言**:介绍 {topic} 的背景和重要性

**第一章:基础概念**
- 核心定义
- 关键术语

**第二章:实现方式**
- 主流方案对比
- 最佳实践

**第三章:实战案例**
- 具体示例
- 常见问题

**结论**:总结要点,给出建议
""".strip()

    return {"outline": outline}

def expand_content(state: BlogState) -> dict:
    """第二步:根据大纲生成正文草稿"""
    outline = state["outline"]
    topic = state["topic"]

    prompt = f"""根据以下大纲,为主题"{topic}"写一篇详细的博客文章草稿。
每个章节至少写 2-3 段,包含具体示例。

大纲:
{outline}

文章草稿:"""

    # draft = llm.invoke(prompt)  # 替换成真实 LLM 调用
    draft = f"""# {topic} 完整指南

## 引言

{topic} 是现代软件开发中的重要话题...(正文草稿)

## 基础概念

理解 {topic} 首先需要掌握几个核心概念...

## 实现方式

在实际项目中,有多种方式可以实现 {topic}...

## 实战案例

以下是一个典型的 {topic} 应用场景...

## 结论

通过本文的介绍,我们深入了解了 {topic} 的各个方面...
""".strip()

    return {"draft": draft}

def polish_article(state: BlogState) -> dict:
    """第三步:润色优化文章"""
    draft = state["draft"]

    prompt = f"""请对以下文章草稿进行润色,使其:
1. 语言更流畅自然
2. 逻辑更清晰
3. 适合技术博客读者

草稿:
{draft}

润色后的文章:"""

    # final = llm.invoke(prompt)  # 替换成真实 LLM 调用
    final = draft + "\n\n---\n*(已润色优化)*"

    return {"final_article": final}

# ===== 组装图 =====

graph = StateGraph(BlogState)

graph.add_node("outline", generate_outline)
graph.add_node("expand", expand_content)
graph.add_node("polish", polish_article)

graph.set_entry_point("outline")
graph.add_edge("outline", "expand")
graph.add_edge("expand", "polish")
graph.add_edge("polish", END)

app = graph.compile()

运行

python 复制代码
result = app.invoke({
    "topic": "LangGraph 入门指南",
    "outline": "",
    "draft": "",
    "final_article": "",
})

print("=== 大纲 ===")
print(result["outline"])
print("\n=== 最终文章 ===")
print(result["final_article"])

使用stream来观察每一步

python 复制代码
for step in app.stream({
    "topic": "LangGraph 入门指南",
    "outline": "",
    "draft": "",
    "final_article": "",
}):
    node_name = list(step.keys())[0]
    print(f"\n--- [{node_name}] 完成 ---")
    if node_name == "outline":
        print(step["outline"]["outline"][:200])
    elif node_name == "expand":
        print(step["expand"]["draft"][:200])
    elif node_name == "polish":
        print("文章已润色完成")

接入真实LLM

使用OpenAI

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)

def generate_outline(state: BlogState) -> dict:
    topic = state["topic"]
    prompt = f"为主题'{topic}'创建博客大纲(3-4个章节):"

    response = llm.invoke([HumanMessage(content=prompt)])
    return {"outline": response.content}

使用HuggingFace(Mistral)

python 复制代码
import os
from langchain_huggingface import HuggingFaceEndpoint

# 需要 HuggingFace API token
llm = HuggingFaceEndpoint(
    repo_id="mistralai/Mistral-7B-Instruct-v0.2",
    huggingfacehub_api_token=os.environ["HUGGINGFACEHUB_API_TOKEN"],
    task="text-generation",
    max_new_tokens=512,
    temperature=0.7,
)

def generate_outline(state: BlogState) -> dict:
    topic = state["topic"]
    prompt = f"[INST] 为主题'{topic}'创建博客大纲(3-4个章节):[/INST]"

    response = llm.invoke(prompt)
    return {"outline": response}

两者替换

LangGraph里的LLM调用只在节点函数里,切换模型只需要修改节点内部------图的结构完全不变 。这是Prompt Chaining模式的一个好处:执行逻辑和模型选择解耦。


中间结果的自我排查

分步生成的另一个优势:可以在节点之间检查中间结果,决定是否继续。

python 复制代码
def check_outline_quality(state: BlogState) -> str:
    """检查大纲质量,决定是直接展开还是重新生成"""
    outline = state["outline"]

    # 简单检查:大纲是否包含足够的章节
    if outline.count("##") >= 3:
        return "expand"  # 质量够,继续展开
    else:
        return "regenerate_outline"  # 质量不够,重新生成

graph.add_conditional_edges(
    "outline",
    check_outline_quality,
    {
        "expand": "expand",
        "regenerate_outline": "outline",  # 循环回去重新生成
    }
)

这就把 Prompt Chaining 和条件分支结合起来了,形成一个可以自我修正的生成循环


总结

Prompt Chaining的要点:

  • 把大任务拆成多个小步骤,每步一个节点
  • 上一步的输出存到state,下一步从 state 里读
  • 每个节点的prompt只关注当前步骤,不用一次性解决所有问题
  • 中间结果可以用条件分支检查质量,不满足就重跑
  • LLM只在节点函数里,切换模型不影响图结构

接入LLMOpenAIHuggingFace

在前几篇文章中LLM的调用都是使用占位符来代替,这里就演示一下把真实的LLM接入


安装依赖

bash 复制代码
# OpenAI
pip install langchain-openai

# HuggingFace
pip install langchain-huggingface huggingface_hub

# 两者都需要
pip install langgraph langchain-core

OpenAI方法

基础用法

python 复制代码
import os
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage

# 初始化(建议在节点外部初始化,避免每次调用都重新创建)
llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0,
    api_key=os.environ.get("OPENAI_API_KEY"),
)

def summarize_node(state: dict) -> dict:
    """在节点里调用 ChatOpenAI"""
    text = state["input_text"]

    response = llm.invoke([
        SystemMessage(content="你是一个专业的文本摘要助手。"),
        HumanMessage(content=f"请用 2-3 句话总结以下内容:\n\n{text}"),
    ])

    return {"summary": response.content}

PromptTemplate管理Prompt

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的{role}。"),
    ("human", "{task}"),
])

chain = prompt_template | llm

def analyze_node(state: dict) -> dict:
    response = chain.invoke({
        "role": "代码审查专家",
        "task": f"审查以下代码:\n{state['code']}",
    })
    return {"review": response.content}

结构化输出

python 复制代码
from pydantic import BaseModel, Field
from typing import List

class CodeReview(BaseModel):
    score: int = Field(description="代码质量评分 1-10")
    issues: List[str] = Field(description="发现的问题列表")
    suggestions: List[str] = Field(description="改进建议")

structured_llm = llm.with_structured_output(CodeReview)

def review_node(state: dict) -> dict:
    result: CodeReview = structured_llm.invoke(
        f"审查这段代码并给出评分:\n{state['code']}"
    )
    return {
        "score": result.score,
        "issues": result.issues,
        "suggestions": result.suggestions,
    }

HuggingFace方案

基础用法

python 复制代码
import os
from langchain_huggingface import HuggingFaceEndpoint

llm = HuggingFaceEndpoint(
    repo_id="mistralai/Mistral-7B-Instruct-v0.2",
    huggingfacehub_api_token=os.environ["HUGGINGFACEHUB_API_TOKEN"],
    task="text-generation",
    max_new_tokens=512,
    temperature=0.1,
    do_sample=True,
)

def generate_node(state: dict) -> dict:
    """HuggingFace 节点"""
    topic = state["topic"]

    # Mistral 用 [INST] 标记
    prompt = f"[INST] 用中文简要介绍:{topic} [/INST]"

    response = llm.invoke(prompt)
    return {"output": response}

不同模型的Prompt格式

不同模型有不同的指令格式,调用时要匹配:

ini 复制代码
# Mistral / Mixtral
prompt = f"[INST] {instruction} [/INST]"

# Llama 2
prompt = f"<s>[INST] <<SYS>>\n{system}\n<</SYS>>\n\n{user} [/INST]"

# Llama 3
prompt = f"<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n{user}<|eot_id|><|start_header_id|>assistant<|end_header_id|>"

# ChatML(Qwen, Yi 等)
prompt = f"<|im_start|>system\n{system}<|im_end|>\n<|im_start|>user\n{user}<|im_end|>\n<|im_start|>assistant\n"

ChatHuggingFace统一接口

ChatHuggingFaceHumanMessage/SystemMessage 接口,自动处理prompt格式:

python 复制代码
from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
from langchain_core.messages import HumanMessage, SystemMessage

endpoint = HuggingFaceEndpoint(
    repo_id="mistralai/Mistral-7B-Instruct-v0.2",
    huggingfacehub_api_token=os.environ["HUGGINGFACEHUB_API_TOKEN"],
    task="text-generation",
    max_new_tokens=512,
)

chat_llm = ChatHuggingFace(llm=endpoint)

def chat_node(state: dict) -> dict:
    response = chat_llm.invoke([
        SystemMessage(content="你是一个助手。"),
        HumanMessage(content=state["user_input"]),
    ])
    return {"response": response.content}

完整代码------多步内容生成workflow

用 OpenAI 实现一个完整的三步内容生成流程:

python 复制代码
import os
from typing import TypedDict
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, END

# ===== State =====
class ContentState(TypedDict):
    topic: str
    keywords: str
    outline: str
    article: str

# ===== LLM(只初始化一次)=====
llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0.7,
    api_key=os.environ.get("OPENAI_API_KEY"),
)

# ===== 节点 =====

def extract_keywords(state: ContentState) -> dict:
    """提取关键词"""
    response = llm.invoke([
        HumanMessage(content=f"从主题'{state['topic']}'中提取 5 个核心关键词,用逗号分隔,只输出关键词:")
    ])
    return {"keywords": response.content.strip()}

def create_outline(state: ContentState) -> dict:
    """根据关键词生成大纲"""
    response = llm.invoke([
        HumanMessage(content=(
            f"基于主题'{state['topic']}'和关键词'{state['keywords']}',"
            f"生成一个 4 节的文章大纲,每节一行,格式为:数字. 标题"
        ))
    ])
    return {"outline": response.content.strip()}

def write_article(state: ContentState) -> dict:
    """根据大纲写文章"""
    response = llm.invoke([
        HumanMessage(content=(
            f"根据以下大纲,写一篇关于'{state['topic']}'的短文(约 300 字):\n\n"
            f"{state['outline']}"
        ))
    ])
    return {"article": response.content.strip()}

# ===== 图 =====

graph = StateGraph(ContentState)
graph.add_node("keywords", extract_keywords)
graph.add_node("outline", create_outline)
graph.add_node("write", write_article)

graph.set_entry_point("keywords")
graph.add_edge("keywords", "outline")
graph.add_edge("outline", "write")
graph.add_edge("write", END)

app = graph.compile()

# ===== 运行 =====
if __name__ == "__main__":
    result = app.invoke({
        "topic": "LangGraph 状态图编程",
        "keywords": "",
        "outline": "",
        "article": "",
    })

    print("关键词:", result["keywords"])
    print("\n大纲:\n", result["outline"])
    print("\n文章:\n", result["article"])

在节点里处理LLM错误

python 复制代码
import time
from langchain_core.exceptions import OutputParserException

def robust_llm_node(state: dict) -> dict:
    """带重试的 LLM 节点"""
    max_retries = 3

    for attempt in range(max_retries):
        try:
            response = llm.invoke([HumanMessage(content=state["prompt"])])
            return {"result": response.content}
        except Exception as e:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # 指数退避
                continue
            else:
                return {"result": f"错误:{str(e)}", "error": True}

环境变量管理

python 复制代码
# 推荐用 .env 文件 + python-dotenv
from dotenv import load_dotenv
load_dotenv()

# .env 文件内容:
# OPENAI_API_KEY=sk-...
# HUGGINGFACEHUB_API_TOKEN=hf_...

import os
openai_key = os.environ["OPENAI_API_KEY"]
hf_token = os.environ["HUGGINGFACEHUB_API_TOKEN"]

OpenAI vs HuggingFace 选哪个

维度 OpenAI (GPT-4o) HuggingFace (Mistral 等)
质量 更高,特别是复杂推理 中等,小模型差距明显
成本 按 token 计费 推理 API 免费(有限额)
速度 免费 Endpoint 较慢
离线部署 不支持 支持(本地推理)
结构化输出 原生支持 需要自己解析
适合场景 生产环境,高质量需求 学习实验,成本敏感

学习阶段:用 HuggingFace 免费额度跑通流程,不需要花钱。

生产阶段:换 OpenAI/Claude,只改节点里的 LLM 初始化代码,图结构不变。


总结

  • LLM 在节点函数里调用,图结构与模型选择完全解耦
  • OpenAIChatOpenAI + HumanMessage/SystemMessage,支持 with_structured_output
  • HuggingFaceHuggingFaceEndpoint + ChatHuggingFace,注意 prompt 格式
  • LLM 对象在图外部初始化,节点函数闭包引用
  • 生产代码记得加重试和错误处理

到这里,LangGraph 的核心模式都覆盖到了:顺序图、条件分支、并行执行、Prompt Chaining、LLM 集成。


相关推荐
DianSan_ERP2 小时前
抖店订单接口中消费者信息加密解密机制与安全履约全解析
前端·网络·数据库·后端·安全·团队开发·运维开发
爱码小白2 小时前
MySQL运维篇
大数据·数据库·python
wang3zc2 小时前
HTML函数能否用外接显卡坞提升性能_eGPU对HTML函数帮助【汇总】
jvm·数据库·python
難釋懷2 小时前
Redis网络模型-Redis是单线程的吗?为什么使用单线程
网络·数据库·redis
森旺电子2 小时前
嵌入式计算题 栈
网络
2301_781571422 小时前
mysql如何配置自增ID预留_mysql innodb_autoinc_lock_mode参数
jvm·数据库·python
晚风烟火2 小时前
从“落地实践”和“应试通关”两个维度,拆解每一章到底要掌握什么
java
解决问题no解决代码问题2 小时前
Quartz 1.6.5
数据库·servlet·oracle
顶点多余2 小时前
传输层协议Tcp详解----上
网络·tcp/ip·udp