非线性工作流:分支与动态路由

目录

5.2.1  非线性工作流核心概念

1. LangGraph非线性工作流核心价值

2. 非线性工作流关键技术

3. DeepSeek LLM适配

4. 路由策略

5.2.2  实战案例:使用条件边路由到节点

1. 案例场景:智能客服问题处理系统

2. 基础组件定义

3. 核心节点实现

4. 构建LangGraph工作流


LangGraph开发AI Agent实践(人工智能技术丛书)【行情 报价 价格 评测】-京东

LangGraph是LangChain生态中用于构建有状态、多参与者、非线性LLM工作流的库。它特别适合需要分支(branching)、循环(looping)或动态路由(dynamic routing)的复杂Agent编排场景。其核心思想是将工作流建模为有向图(Directed Graph),其中节点是Agent(或工具),是条件路由逻辑。

5.2.1  非线性工作流核心概念

1. LangGraph非线性工作流核心价值

LangGraph是LangChain生态中的有状态图工作流框架,专为解决LLM应用中的非线性逻辑设计。相比传统线性流水线,它支持:

  • 分支(并行/条件执行)。
  • 循环(结果校验重试)。
  • 动态路由(根据输入/中间结果切换执行路径)。
  • 状态持久化(跨节点共享上下文)。
2. 非线性工作流关键技术
  • 条件分支:根据输入特征分流执行,如问题分类(事实问答、创意写作、代码生成)。
  • 动态路由 :基于中间结果动态切换执行路径,如答案质量校验(合格则直接输出,否则重新生成)。
  • 并行分支:多任务并发执行,实现多维度内容分析(如情感分析、关键词提取、实体识别)。
  • 循环节点:当结果不满足条件时自动重试,适用于生成内容合规性校验与格式修正。
3. DeepSeek LLM适配

DeepSeek系列模型(如deepseek-chat、deepseek-coder)支持中文优化、长上下文处理,通过LangChain的DeepSeek封装可无缝集成到LangGraph中,核心优势如下:

  • 中文理解准确率高。
  • 代码/逻辑推理能力强。
  • 支持工具调用与函数执行。
4. 路由策略

LangGraph的动态路由通过条件边(conditional edges)实现:

  • 定义路由函数:接收当前状态,返回目标节点名称。
  • 支持多分支路由:根据状态变量(如query_type、is_complete)动态切换路径。
  • 循环路由:通过END与节点名称的条件判断,实现结果不满足时的重试逻辑。

from typing import Literal

from langgraph.graph import END, StateGraph

5.2.2  实战案例:使用条件边路由到节点

1. 案例场景:智能客服问题处理系统

系统需求说明如下:

  • 接收用户咨询,先分类(订单问题/产品咨询/投诉建议)。
  • 订单问题:查询订单状态(模拟工具调用)。
  • 产品咨询:生成产品说明(直接LLM响应)。
  • 投诉建议:记录内容并转人工(并行执行两个任务)。
  • 结果校验:若回答不完整,则重新调用对应节点。
2. 基础组件定义

1)初始化DeepSeek LLM

from dotenv import load_dotenv

from langchain_openai import ChatOpenAI

import os

# 加载环境变量

load_dotenv()

# 初始化qwen-plus  LLM(指定聊天模型)

llm = ChatOpenAI(

    model="qwen-plus",

    api_key=os.getenv("DASHSCOPE_API_KEY"),  # .env 读取

    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",

    temperature=0

)

2)定义工作流状态

LangGraph通过StateGraph管理状态,状态变量需明确类型:

from typing import TypedDict, Optional

from langgraph.graph import StateGraph, END

# 定义工作流状态结构

class SupportState(TypedDict):

    user_query: str  # 用户原始查询

    query_type: Optional[str]  # 问题类型(订单/产品/投诉)

    order_id: Optional[str]  # 订单号(仅订单问题需要)

    response: Optional[str]  # 最终响应

    is_complete: bool  # 响应是否完整(用于动态路由)

3. 核心节点实现

节点是工作流的执行单元,每个节点接收State并返回更新后的State。

1)问题分类节点(路由入口)

根据用户查询判断问题类型,为动态路由提供依据:

def classify_query(state: SupportState) -> SupportState:

    """分类用户查询:订单问题/产品咨询/投诉建议"""

    user_query = state["user_query"]

   

    # 调用qwen-plus 进行分类

    prompt = f"""

    请将用户查询分类为以下三类:订单问题、产品咨询、投诉建议

    分类规则:

    - 订单问题:包含订单号、查询物流、修改订单、取消订单等关键词

    - 产品咨询:包含产品功能、使用方法、规格参数、兼容性等关键词

    - 投诉建议:包含投诉、不满、建议、改进等关键词

   

    用户查询:{user_query}

    仅返回分类结果,无须额外说明。

    """

   

    query_type = llm.invoke(prompt).strip()

    print(f"问题分类结果:{query_type}")

   

    # 提取订单号(仅订单问题)

    order_id = None

    if query_type == "订单问题":

        extract_prompt = f"""

        从用户查询中提取订单号(通常为6~12位数字或字母+数字组合),

        若未提及订单号,则返回""

       

        用户查询:{user_query}

        仅返回订单号或""

        """

        order_id = llm.invoke(extract_prompt).strip()

        print(f"提取订单号:{order_id}")

   

    return {

        **state,

        "query_type": query_type,

        "order_id": order_id,

        "is_complete": False  # 初始响应未完成

    }

2)分支处理节点

(1)订单问题处理(模拟工具调用):

def handle_order(state: SupportState) -> SupportState:

    """处理订单问题:查询订单状态(模拟工具调用)"""

    order_id = state["order_id"]

    user_query = state["user_query"]

   

    if order_id == "":

        response = "为了帮你查询订单状态,请提供你的订单号(6~12位数字或字母+数字组合)。"

    else:

        # 模拟调用订单查询API

        mock_order_status = f"订单{order_id}当前状态:已发货,物流单号:SF123456789,预计明日送达。"

        response = f"您好!{mock_order_status} 若有其他需求,请随时告知。"

   

    print(f"订单问题响应:{response}")

    return {**state, "response": response}

(2)产品咨询处理(直接LLM生成):

def handle_product(state: SupportState) -> SupportState:

    """处理产品咨询:生成产品说明"""

    user_query = state["user_query"]

   

    prompt = f"""

    作为产品顾问,请详细解答用户的产品咨询,语言通俗易懂,结构清晰。

   

    用户咨询:{user_query}

    回答要求:

    1. 先明确核心问题

    2. 分点说明(不超过3点)

    3. 结尾提供进一步帮助引导

    """

   

    response = llm.invoke(prompt)

    print(f"产品咨询响应:{response}")

    return {**state, "response": response}

(3)投诉建议处理(并行执行):

def handle_complaint_record(state: SupportState) -> SupportState:

    """处理投诉建议:记录投诉内容(并行节点1"""

    user_query = state["user_query"]

    # 模拟写入数据库

    print(f"已记录投诉建议:{user_query}")

    return {**state, "complaint_recorded": True}

def handle_complaint_forward(state: SupportState) -> SupportState:

    """处理投诉建议:转人工处理(并行节点2"""

    user_query = state["user_query"]

    response = f"您好!您的反馈已收到,我们将在24小时内安排专属客服与您联系。感谢您的支持!"

    print(f"投诉转人工响应:{response}")

    return {**state, "response": response}

3)结果校验节点(动态路由判断)

def validate_response(state: SupportState) -> SupportState:

    """校验响应是否完整,决定是否重试"""

    response = state["response"]

    user_query = state["user_query"]

   

    prompt = f"""

    请判断以下响应是否完整解答了用户的查询:

    1. 若响应直接回答了核心问题,或提供了明确的下一步操作指引(如要求补充信息),则判定为完整,返回"完整"

    2. 若响应模糊、未解答核心问题、遗漏关键信息,则判定为不完整,返回"不完整"

   

    用户查询:{user_query}

    系统响应:{response}

    仅返回"完整""不完整"

    """

   

    validation_result = llm.invoke(prompt).strip()

    print(f"响应校验结果:{validation_result}")

   

    return {

        **state,

        "is_complete": validation_result == "完整"

    }

4. 构建LangGraph工作流

1)初始化图并添加节点

# 初始化状态图

graph = StateGraph(SupportState)

# 添加核心节点

graph.add_node("classify_query", classify_query)  # 问题分类

graph.add_node("handle_order", handle_order)  # 订单处理

graph.add_node("handle_product", handle_product)  # 产品咨询处理

graph.add_node("handle_complaint_record", handle_complaint_record)  # 投诉记录

graph.add_node("handle_complaint_forward", handle_complaint_forward)  # 投诉转人工

graph.add_node("validate_response", validate_response)  # 响应校验

2)定义边(路由规则)

(1)初始路由:分类后分流:

# 从分类节点根据query_type分流到对应处理节点

def route_after_classify(state: SupportState) -> str:

    query_type = state["query_type"]

    if query_type == "订单问题":

        return "handle_order"

    elif query_type == "产品咨询":

        return "handle_product"

    elif query_type == "投诉建议":

        return "handle_complaint_record"  # 先执行记录,再并行转人工

    else:

        return "handle_product"  # 默认走产品咨询

# 添加条件边:分类节点 -> 处理节点

graph.add_conditional_edges(

    "classify_query",

    route_after_classify

)

(2)投诉处理并行路由:

# 投诉记录后,并行执行转人工(使用add_edge实现串行,实际并行可通过LangGraph并行节点优化)

graph.add_edge("handle_complaint_record", "handle_complaint_forward")

(3)响应校验与动态重试路由:

# 所有处理节点都指向校验节点

graph.add_edge("handle_order", "validate_response")

graph.add_edge("handle_product", "validate_response")

graph.add_edge("handle_complaint_forward", "validate_response")

# 校验节点的动态路由:完整则结束,不完整则重试对应处理节点

def route_after_validation(state: SupportState) -> str:

    if state["is_complete"]:

        return END  # 流程结束

    else:

        # 不完整则重试原处理节点

        query_type = state["query_type"]

        if query_type == "订单问题":

            return "handle_order"

        elif query_type == "产品咨询":

            return "handle_product"

        elif query_type == "投诉建议":

            return "handle_complaint_forward"

        else:

            return "handle_product"

# 添加条件边:校验节点 -> 结束或重试

graph.add_conditional_edges(

    "validate_response",

    route_after_validation

)

(4)设置入口节点:

graph.set_entry_point("classify_query")

3)编译图并运行

# 编译图(生成可执行工作流)

app = graph.compile()

# 测试运行:输入不同类型的用户查询

def test_workflow(user_query: str):

    print(f"\n{'=' * 60}")

    print(f"处理用户查询:{user_query}")

    print(f"{'=' * 60}")

    # 运行工作流

    result = app.invoke({

        "user_query": user_query,

        "query_type": None,

        "order_id": None,

        "response": None,

        "is_complete": False,

        "retry_count": 0

    })

    print(f"\n最终响应:{result.get('response')}")

    print(f"重试次数:{result.get('retry_count', 0)}")

    print(f"{'=' * 60}")

    return result

# 测试函数,运行所有测试用例

def run_all_tests():

    """运行所有测试用例"""

    test_cases = [

        ("我的订单号是OD123456,请问什么时候发货?", "订单问题"),

        ("你们的智能音箱支持蓝牙5.0吗?怎么连接手机?", "产品咨询"),

        ("我对昨天收到的商品不满意,质量有问题,希望退款或换货。", "投诉建议"),

        ("我的订单什么时候能到?", "订单问题但没提供订单号"),

        ("我想问一下这个产品怎么用", "简短的产品咨询")

    ]

    results = []

    for i, (query, description) in enumerate(test_cases, 1):

        print(f"\n{'#' * 60}")

        print(f"测试 {i}: {description}")

        print(f"查询内容: {query}")

        print(f"{'#' * 60}")

        try:

            result = test_workflow(query)

            results.append((query, description, result))

        except Exception as e:

            print(f"测试失败: {e}")

            import traceback

            traceback.print_exc()

    print(f"\n{'=' * 60}")

    print(f"所有测试完成,共运行 {len(results)} 个测试用例")

    print(f"{'=' * 60}")

# 测试用例

if __name__ == "__main__":

    run_all_tests()

5.2.3  实战案例:使用动态路由路由到节点

【案例5.2】非线性工作流智能客服问题处理系统LangGraph_Non-linear_workflow。

# 完整依赖导入(适配 LangGraph 1.0.5,纯串行非线性工作流)

from dotenv import load_dotenv

from typing import TypedDict, Optional

from langgraph.graph import StateGraph, END  # LangGraph 1.0.5

import os

import requests

# ==================== 1. API 调用(本地模拟,确保无网络问题) ===================

load_dotenv()

api_key = os.getenv("DEEPSEEK_API_KEY")

def call_deepseek(prompt: str) -> str:

    """本地模拟 API 响应,非线性流程核心逻辑不变"""

    if "分类" in prompt:

        return "订单问题" if "订单" in prompt or "OD" in prompt else "产品咨询" if "产品" in prompt else "投诉建议"

    elif "提取订单号" in prompt:

        return "OD123456" if "OD123456" in prompt else ""

    elif "智能音箱" in prompt and "蓝牙" in prompt:

        return "1. 支持蓝牙5.02. 手机搜设备配对;3. 兼容主流系统"

    elif "校验" in prompt:

        return "不完整" if "请提供订单号" in prompt else "完整"

    return "系统暂时无法服务"

# ====================== 2. 状态定义(精简,仅保留核心控制字段) =================

class SupportState(TypedDict):

    user_query: str

    query_type: Optional[str]

    order_id: Optional[str]

    response: Optional[str]

    retry_count: int

    current_node: str  # 唯一控制字段:指定当前要执行的节点

    is_complete: bool

# ============= 3. 核心节点(每个节点仅处理自身逻辑,不依赖边关系分支) ==============

def classify_query(state: SupportState) -> SupportState:

    """分类节点:仅处理分类,更新下一个节点"""

    print(f"[节点执行] 分类查询:{state['user_query']}")

    query_type = call_deepseek(f"分类用户查询:{state['user_query']}")

    # 非线性分支:根据分类指定下一个节点

    next_node = "extract_order_id" if query_type == "订单问题" else "handle_product" if query_type == "产品咨询" else "handle_complaint"

    return {

        **state,

        "query_type": query_type,

        "current_node": next_node,  # 明确下一个要执行的节点

        "is_complete": False

    }

def extract_order_id(state: SupportState) -> SupportState:

    """提取订单号节点:条件跳转"""

    print(f"[节点执行] 提取订单号")

    order_id = call_deepseek(f"提取订单号:{state['user_query']}")

    # 条件逻辑:有订单号→处理订单,无→提示补充

    next_node = "handle_order" if order_id != "" else "prompt_order_id"

    return {

        **state,

        "order_id": order_id,

        "current_node": next_node,

        "retry_count": state["retry_count"] + 1

    }

def prompt_order_id(state: SupportState) -> SupportState:

    """提示补充订单号节点:触发重试"""

    print(f"[节点执行] 提示补充订单号")

    response = "请提供6~12位订单号(如OD123456"

    # 非线性循环:提示后跳转校验,校验不通过则重试

    return {

        **state,

        "response": response,

        "current_node": "validate_response"

    }

def handle_order(state: SupportState) -> SupportState:

    """处理订单节点:生成响应"""

    print(f"[节点执行] 处理订单:{state['order_id']}")

    response = f"订单{state['order_id']}已发货,物流SF123456789,明日送达"

    return {**state, "response": response, "current_node": "validate_response"}

def handle_product(state: SupportState) -> SupportState:

    """处理产品咨询节点:生成响应"""

    print(f"[节点执行] 处理产品咨询")

    response = call_deepseek(f"解答产品咨询:{state['user_query']}")

    return {**state, "response": response, "current_node": "validate_response"}

def handle_complaint(state: SupportState) -> SupportState:

    """处理投诉节点:生成响应"""

    print(f"[节点执行] 处理投诉")

    response = "投诉已记录,24小时内联系你"

    return {**state, "response": response, "current_node": "validate_response"}

def validate_response(state: SupportState) -> SupportState:

    """校验节点:非线性核心(终止/重试)"""

    print(f"[节点执行] 校验响应,重试次数:{state['retry_count']}")

    validation = call_deepseek(f"校验响应:{state['response']}")

    # 终止条件:响应完整或重试3

    if validation == "完整" or state["retry_count"] >= 3:

        return {**state, "is_complete": True, "current_node": "END"}

    # 重试逻辑:根据问题类型跳转回对应节点

    retry_node = "extract_order_id" if state["query_type"] == "订单问题" else "handle_product" if state["query_type"] == "产品咨询" else "handle_complaint"

    return {**state, "current_node": retry_node, "retry_count": state["retry_count"] + 1}

# ================ 4. 动态路由节点(核心修复:单一入口,串行执行) ===================

def dynamic_router(state: SupportState) -> SupportState:

    """动态路由:根据 current_node 执行对应节点逻辑,避免多节点并发"""

    current_node = state["current_node"]

    print(f"[路由执行] 跳转至节点:{current_node}")

   

    # 核心:路由节点内部调用目标节点,而非通过边关系触发

    if current_node == "classify_query":

        return classify_query(state)

    elif current_node == "extract_order_id":

        return extract_order_id(state)

    elif current_node == "prompt_order_id":

        return prompt_order_id(state)

    elif current_node == "handle_order":

        return handle_order(state)

    elif current_node == "handle_product":

        return handle_product(state)

    elif current_node == "handle_complaint":

        return handle_complaint(state)

    elif current_node == "validate_response":

        return validate_response(state)

    elif current_node == "END":

        return {**state, "is_complete": True}

    else:

        return {**state, "current_node": "classify_query"}

# ====================== 5. 工作流构建(极简边关系,无并发) ======================

graph = StateGraph(SupportState)

# 仅添加 1 个路由节点(所有逻辑都在路由内执行)

graph.add_node("dynamic_router", dynamic_router)

# 边关系:仅路由节点 结束节点,完全规避多节点并发

graph.set_entry_point("dynamic_router")

graph.add_edge("dynamic_router", END)

# 编译工作流

app = graph.compile()

# ================== 6. 测试非线性工作流(串行执行,无并发错误) ==================

def test_workflow(user_query: str):

    print(f"\n{'='*70}")

    print(f"处理用户查询:{user_query}")

    print(f"{'='*70}")

   

    initial_state: SupportState = {

        "user_query": user_query,

        "query_type": None,

        "order_id": None,

        "response": None,

        "retry_count": 0,

        "current_node": "classify_query",  # 初始节点

        "is_complete": False

    }

   

    try:

        # 执行工作流(循环直到流程完成)

        result = initial_state

        while not result["is_complete"]:

            result = app.invoke(result)

            # 避免无限循环(双重保障)

            if result["retry_count"] > 5:

                result["is_complete"] = True

                result["response"] = "系统繁忙,请稍后再试"

       

        print(f"\n【最终响应】:{result['response']}")

        print(f"【流程状态】:完成,总重试次数:{result['retry_count']}")

    except Exception as e:

        print(f"\n【运行错误】:{str(e)}")

    finally:

        print(f"{'='*70}\n")

# 测试核心场景

if __name__ == "__main__":

    test_workflow("我的订单号是OD123456,请问什么时候发货?")

    # test_workflow("请问我的订单什么时候发货?")  # 无订单号,触发重试

    # test_workflow("你们的智能音箱支持蓝牙5.0吗?")  # 产品咨询分支

运行输出:

======================================================================

处理用户查询:我的订单号是OD123456,请问什么时候发货?

======================================================================

[路由执行] 跳转至节点:classify_query

[节点执行] 分类查询:我的订单号是OD123456,请问什么时候发货?

[路由执行] 跳转至节点:extract_order_id

[节点执行] 提取订单号

[路由执行] 跳转至节点:handle_order

[节点执行] 处理订单:OD123456

[路由执行] 跳转至节点:validate_response

[节点执行] 校验响应,重试次数:1

【最终响应】:订单OD123456已发货,物流SF123456789,明日送达

【流程状态】:完成,总重试次数:1

======================================================================

该程序非线性工作流的特性如下。

  • 多分支:订单、产品、投诉三个独立分支,根据查询类型动态切换。
  • 条件跳转:订单号有无→不同处理路径;投诉信息完整性→不同响应。
  • 循环重试:响应不完整时自动重试对应分支,最多重试3次。
  • 状态驱动:通过current_node字段控制流程走向,符合非线性工作流定义。

项目扩展说明如下。

  • 非线性逻辑清晰:分支、条件跳转、循环重试的逻辑直观,适合教学演示。
  • 适配LangGraph 1.0.5:仅使用基础功能,读者可直接复现。
  • 可扩展性强:新增节点只需在路由内添加调用逻辑,无须修改边关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值