之前使用了langchain实现了一个rag项目,当流程不复杂,分支少,并且是线性流程的时候,langchain比较合适,但是往往agent都是越做越复杂的,当流程分支开始变多,需要和用户进行交互,agent需要进行自我评估输出并重试,路由,多agent协同工作时,用langchain来编排简直太费劲,就需要用到langgraph。
我认为langgraph就是用代码的方式来画流程图,里面有三个我觉得重要的点
全局状态(state)#
就像是一个全局的记事本,在节点中流通,你可以自己去定义这个state如何更新
添加节点(add nodes)#
在langgraph中,节点就是流程图中的框,也就是具体的执行单元,本质就是一个你自己定义的函数,入参是上个节点的出参,你的返回又是下一个节点的入参。每个节点只负责一件具体的事情,比如生成一段文案,调用websearch查询一个问题,或者是对上一个节点的输出进行判定或者格式化。
PYTHON
左右滑动查看完整代码
workflow.add_node("agent", agent_function) #就长这样编排连线 (add edges)#
添加了节点之后,数据怎么流动?就是通过edge去编排,相当于是给方框画箭头了。
LangGraph 提供了两种极其重要的连线方式:
- 普通的边
就按顺序执行,执行完方框 A,必须直接进入方框 B
PYTHON
左右滑动查看完整代码
workflow.add_edge("node_A", "node_B")- 条件边
流程图中的菱形, 根据方框 A 的输出结果进行判断。如果结果合格,走箭头 1 进入方框 B;如果结果不合 格,走箭头 2 重新回到方框 A(这就实现了 LangChain 做不到的循环/重试机制),其实不是做不到,写一堆if else 嵌套也能做到,但是可读性拓展性都不强,不考虑这样做
PYTHON
左右滑动查看完整代码
def check_approval_status(state: GraphState) -> str:
"""这是一个纯逻辑判断函数,决定接下来走哪条路"""
if state["is_approved"] == True:
return "approved" # 合格
else:
return "rejected"
workflow.add_conditional_edges(
"review_node", # 起点:审批节点执行完后触发
check_approval_status, # 决策者:调用上面定义的判断函数
{
"approved": "publish_node", # 如果函数返回 "approved",发布这段代码 publish_node 是下一个分支节点
"rejected": "generate_node" # 如果函数返回 "rejected",(回退循环,重新生成) generate_node 是 review_node 的上一个节点
}
)dify可视化配置节点#
这里没有使用llm,写死的检索结果
所以分支会这样走
把节点返回的变量值改为no
PYTHON
左右滑动查看完整代码
def main(query: str) -> dict:
# 对应 LangGraph 的 retrieve 节点。这里不接知识库,只写死一段模拟文档。
context = '标题路径:宫保鸡丁 > 计算\n内容:莴笋 = 约 250g;油泼辣子 = 5g。'
return {'context': context, 'has_context': 'no'}
为了方便理解,做了一个简单的dify 工作流
yml配置#
YAML
左右滑动查看完整代码
app:
description: 用来理解 LangGraph 的条件分支;不连接真实 LLM。
icon: 🧭
icon_background: '#EFF1F5'
mode: workflow
name: LangGraph 条件分支学习 Demo
use_icon_as_answer_icon: false
dependencies: []
kind: app
version: 0.1.5
workflow:
conversation_variables: []
environment_variables: []
features:
file_upload:
image:
enabled: false
number_limits: 3
transfer_methods:
- local_file
opening_statement: ''
retriever_resource:
enabled: false
sensitive_word_avoidance:
enabled: false
speech_to_text:
enabled: false
suggested_questions: []
suggested_questions_after_answer:
enabled: false
text_to_speech:
enabled: false
graph:
edges:
- data:
sourceType: start
targetType: code
id: start-rewrite
source: start
sourceHandle: source
target: rewrite
targetHandle: target
type: custom
zIndex: 0
- data:
sourceType: code
targetType: code
id: rewrite-retrieve
source: rewrite
sourceHandle: source
target: retrieve
targetHandle: target
type: custom
zIndex: 0
- data:
sourceType: code
targetType: if-else
id: retrieve-check-context
source: retrieve
sourceHandle: source
target: check_context
targetHandle: target
type: custom
zIndex: 0
- data:
sourceType: if-else
targetType: code
id: check-context-answer
source: check_context
sourceHandle: 'true'
target: answer
targetHandle: target
type: custom
zIndex: 0
- data:
sourceType: if-else
targetType: code
id: check-context-fallback
source: check_context
sourceHandle: 'false'
target: fallback_answer
targetHandle: target
type: custom
zIndex: 0
- data:
sourceType: code
targetType: end
id: answer-end
source: answer
sourceHandle: source
target: end
targetHandle: target
type: custom
zIndex: 0
- data:
sourceType: code
targetType: end
id: fallback-end
source: fallback_answer
sourceHandle: source
target: fallback_end
targetHandle: target
type: custom
zIndex: 0
nodes:
- data:
desc: 输入问题。
selected: false
title: Start
type: start
variables:
- label: question
max_length: 256
required: true
type: paragraph
variable: question
height: 90
id: start
position:
x: 30
y: 260
positionAbsolute:
x: 30
y: 260
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
code: "def main(question: str) -> dict:\n # 对应 LangGraph 的 rewrite_query 节点。\n return {'query': question + ' 配料 用量'}\n"
code_language: python3
desc: 模拟查询改写。
outputs:
query:
type: string
selected: false
title: rewrite
type: code
variables:
- value_selector:
- start
- question
variable: question
height: 54
id: rewrite
position:
x: 330
y: 260
positionAbsolute:
x: 330
y: 260
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
code: "def main(query: str) -> dict:\n # 对应 LangGraph 的 retrieve 节点。这里不接知识库,只写死一段模拟文档。\n # 想测试兜底路径,把 has_context 改成 'no' 即可。\n context = '标题路径:宫保鸡丁 > 计算\\n内容:莴笋 = 约 250g;油泼辣子 = 5g。'\n return {'context': context, 'has_context': 'yes'}\n"
code_language: python3
desc: 模拟检索。
outputs:
context:
type: string
has_context:
type: string
selected: false
title: retrieve
type: code
variables:
- value_selector:
- rewrite
- query
variable: query
height: 54
id: retrieve
position:
x: 630
y: 260
positionAbsolute:
x: 630
y: 260
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
conditions:
- comparison_operator: is
id: condition-has-context
value: 'yes'
variable_selector:
- retrieve
- has_context
desc: 对应 LangGraph 的条件边:上下文存在就回答,否则走兜底。
logical_operator: and
selected: false
title: check_context
type: if-else
height: 126
id: check_context
position:
x: 930
y: 260
positionAbsolute:
x: 930
y: 260
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
code: "def main(question: str, context: str) -> dict:\n # 正常回答分支。\n return {'result': '根据模拟上下文回答:莴笋用量是约 250g。\\n\\n问题:' + question + '\\n\\n上下文:\\n' + context}\n"
code_language: python3
desc: 模拟正常回答。
outputs:
result:
type: string
selected: false
title: answer
type: code
variables:
- value_selector:
- start
- question
variable: question
- value_selector:
- retrieve
- context
variable: context
height: 54
id: answer
position:
x: 1230
y: 180
positionAbsolute:
x: 1230
y: 180
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
code: "def main(question: str) -> dict:\n # 兜底回答分支。\n return {'result': '没有检索到足够上下文,无法回答:' + question}\n"
code_language: python3
desc: 模拟兜底回答。
outputs:
result:
type: string
selected: false
title: fallback_answer
type: code
variables:
- value_selector:
- start
- question
variable: question
height: 54
id: fallback_answer
position:
x: 1230
y: 360
positionAbsolute:
x: 1230
y: 360
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
desc: 输出正常回答结果。
outputs:
- value_selector:
- answer
- result
variable: output
selected: false
title: End
type: end
height: 90
id: end
position:
x: 1530
y: 180
positionAbsolute:
x: 1530
y: 180
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
desc: 输出兜底回答结果。
outputs:
- value_selector:
- fallback_answer
- result
variable: output
selected: false
title: Fallback End
type: end
height: 90
id: fallback_end
position:
x: 1530
y: 360
positionAbsolute:
x: 1530
y: 360
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
viewport:
x: 0
y: 0
zoom: 0.8
hash: ''