返回文章列表

langgraph是什么

5 min read

之前使用了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 提供了两种极其重要的连线方式:

  1. 普通的边

就按顺序执行,执行完方框 A,必须直接进入方框 B

PYTHON
左右滑动查看完整代码
workflow.add_edge("node_A", "node_B")
  1. 条件边

流程图中的菱形, 根据方框 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: ''