如何用LangGraph实现复杂报销审批流?

来源:网站建设作者:小团团头衔:草根站长
导读:本期聚焦于小团团创作的《如何用LangGraph实现复杂报销审批流?》,敬请观看详情。报销审批流程一旦涉及多级主管、金额阈值、预算校验和财务复核,纯if-else的代码就会迅速膨胀,改一个节点经常牵动全局。LangGraph为这类多步骤、有状态的业务流提供了图状态机方案:每个审批步骤被定义为图节点,节点之间的跳转条件通过条件边动态决定,整个流程的状态可以持久化保存,支持人工回退和断点续批。实现时先定义TypedDict状态结构,包含申请单、当前审批层级、历史记录和流转状态;然后创建StateGraph实例,注册主管、总监、财务等节点,设置入口节点和条件路由函数;最后调用compile方法生成可执行应用。遇到金额超标、发票缺失等异常情况,可以直接通过状态更新和条件边把流程拉回上一节点,而不必重写整段逻辑。相比传统工作流引擎,LangGraph的代码更贴近Python生态,调试和测试也更直观。

报销审批不是一个简单的线性流程。金额超过一定阈值需要总监加签,某些费用类型要财务预审,发票不完整时还要退回申请人补充材料。传统做法通常用if-else或数据库状态字段硬编码这些规则,结果就是分支越来越多,代码越来越难维护。LangGraph提供了一种基于状态图的实现思路:把审批环节抽象为节点,把环节之间的流动规则抽象为边,把当前流转位置和历史记录放进同一个状态对象里。下面用LangGraph构建一个可运行的报销审批流案例。

一、定义报销状态与审批节点

报销审批的核心状态需要覆盖申请单内容、当前审批层级、审批历史以及最终状态。LangGraph的状态通常用一个TypedDict定义,这样既保留了Python的类型提示,又方便节点函数返回部分更新。状态中的列表字段需要特别处理:如果多个节点都要往历史记录里追加内容,可以使用Annotated类型加上operator.add,LangGraph会自动执行累加而不是覆盖。下面的ExpenseState包含申请单对象、当前阶段、阶段结果、历史记录以及整体状态。

from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
import operator
from dataclasses import dataclass

@dataclass
class ExpenseRequest:
    applicant: str
    amount: float
    category: str
    invoice_attached: bool

class ExpenseState(TypedDict):
    request: ExpenseRequest
    current_stage: str
    stage_result: str
    history: Annotated[list, operator.add]
    status: str

审批节点函数只负责处理自己这一层的业务规则,并通过返回值更新状态。主管节点检查金额是否超过一万,超过则把当前阶段改为总监审批,否则直接进入财务审批。总监节点处理更高金额的加签逻辑,财务节点则检查发票附件是否齐全。每个节点返回的字典只需要包含发生变化的字段,LangGraph会自动合并到全局状态中。这种局部更新机制避免了在节点之间传递大量无关数据,也让单元测试变得简单。

def supervisor_review(state: ExpenseState):
    req = state["request"]
    if req.amount > 10000:
        return {"history": ["supervisor_pass"], "current_stage": "director_review"}
    return {"history": ["supervisor_pass"], "current_stage": "finance_review"}

def director_review(state: ExpenseState):
    req = state["request"]
    if req.amount > 50000:
        return {"history": ["director_pass"], "current_stage": "finance_review", "stage_result": "approve"}
    return {"history": ["director_pass"], "current_stage": "finance_review", "stage_result": "approve"}

def finance_review(state: ExpenseState):
    req = state["request"]
    if not req.invoice_attached:
        return {"history": ["finance_reject"], "status": "returned", "current_stage": "END"}
    return {"history": ["finance_pass"], "status": "approved", "current_stage": "END"}

把节点职责拆分开之后,业务规则的调整只影响对应节点函数。例如新增预算校验环节,只需要写一个budget_check函数并把条件边接到合适位置,不需要改动其他审批节点。这种低耦合设计对复杂报销流程尤其重要,因为实际业务中审批规则经常随公司政策变化。

二、构建状态图与条件路由

状态图StateGraph是LangGraph的核心,它把节点注册到图里,并通过入口节点和条件边决定执行顺序。创建图实例后,先添加主管、总监、财务三个节点,再设置入口节点为主管。条件边的作用是根据当前状态动态选择下一个节点,例如主管审批完成后,根据current_stage字段决定去总监还是财务。条件边的映射字典键表示节点返回的当前阶段值,值表示要跳转的目标节点名称。

graph = StateGraph(ExpenseState)
graph.add_node("supervisor", supervisor_review)
graph.add_node("director", director_review)
graph.add_node("finance", finance_review)
graph.set_entry_point("supervisor")
graph.add_conditional_edges(
    "supervisor",
    lambda state: state["current_stage"],
    {
        "director_review": "director",
        "finance_review": "finance",
        "END": END
    }
)
graph.add_conditional_edges(
    "director",
    lambda state: state["current_stage"],
    {
        "finance_review": "finance",
        "END": END
    }
)
graph.add_conditional_edges(
    "finance",
    lambda state: state["current_stage"],
    {
        "END": END
    }
)
memory = MemorySaver()
app = graph.compile(checkpointer=memory)

条件边的路由函数非常简单,只需要返回状态中的current_stage字段。这样做的好处是节点内部只负责业务判断并更新状态,图结构负责根据状态决定走向,职责分离清晰。编译图时传入MemorySaver检查点,LangGraph会在每个节点执行后自动保存状态快照,为后续持久化和人工介入打下基础。

request = ExpenseRequest("张三", 12000, "差旅", True)
result = app.invoke({
    "request": request,
    "current_stage": "supervisor",
    "history": [],
    "status": "pending"
})
print(result["status"])

运行invoke方法会从入口节点开始依次执行,直到遇到END。上面的例子中金额为12000,超过一万,因此会先经过主管节点,然后进入总监节点,最后到达财务节点并批准。整个流程的路径由状态自动控制,不需要额外编写控制逻辑。

三、持久化状态与人工回退

MemorySaver检查点保证了每次节点执行后的状态都被保存下来。在实际报销系统中,人工审批往往不是瞬间完成的,可能需要等待主管登录系统点击同意或驳回。LangGraph支持断点续批:先把流程运行到需要人工处理的节点,暂停并存储状态。之后前端调用get_state获取当前状态,展示给审批人,审批人操作后再调用update_state更新状态并继续执行。线程ID用来隔离不同的报销单,每个线程ID对应一个独立的审批会话。

def finance_review_with_return(state: ExpenseState):
    req = state["request"]
    if not req.invoice_attached:
        return {
            "history": ["finance_return"],
            "status": "returned",
            "current_stage": "supervisor",
            "return_to": "supervisor"
        }
    return {"history": ["finance_pass"], "status": "approved", "current_stage": "END"}

上面的节点在检测到发票缺失时,不是直接结束流程,而是把当前阶段改回supervisor,并设置一个return_to字段。图结构里可以为supervisor添加条件边,如果return_to为supervisor则重新进入主管节点,形成回退闭环。这种回退机制适用于需要补充材料或重新审核的场景,避免流程中断后无法继续。

config = {"configurable": {"thread_id": "expense-001"}}
current = app.get_state(config)
print(current.values)

app.update_state(config, {
    "request": request,
    "current_stage": "finance_review",
    "history": [],
    "status": "pending"
})
app.invoke(None, config)

get_state可以获取当前线程的状态快照,update_state允许外部系统修改状态并从中断点继续执行。在报销审批系统中,前端在审批人提交意见后调用update_state,把审批结果写回状态,然后让图继续往下走。这种设计天然支持人工审批、超时自动提醒和流程扭转,比单纯用数据库状态字段加定时任务灵活得多。

四、异常处理与可视化调试

复杂审批流最大的问题之一就是执行路径不透明。LangGraph提供了draw_mermaid方法,可以直接输出Mermaid格式的图描述代码。把这份代码粘贴到支持Mermaid的编辑器里,就能看到完整的节点和边结构。调试复杂条件分支时,先可视化再定位问题,效率比打印日志高很多。如果流程出现递归异常或节点未处理当前阶段,LangGraph会抛出GraphRecursionError,捕获该异常即可触发人工介入或告警。

from langgraph.errors import GraphRecursionError

try:
    final_state = app.invoke({
        "request": request,
        "current_stage": "supervisor",
        "history": [],
        "status": "pending"
    })
except GraphRecursionError as e:
    print("流程执行异常,需要人工介入")

mermaid_code = app.get_graph().draw_mermaid()
print(mermaid_code)

异常处理并不是让流程崩溃,而是把它当作一种业务信号。例如某个节点返回了未在条件边映射中定义的current_stage值,说明业务规则有遗漏,需要在条件边里补充对应映射。借助LangGraph的类型系统和状态检查点,可以快速定位是哪个节点返回了异常值。相比传统工作流引擎的黑盒行为,LangGraph的调试信息更加直观。

LangGraph在复杂报销审批流中的价值不在于消灭所有if-else,而在于把这些条件判断收敛到状态节点和条件边中,让流程结构可以被阅读、测试和修改。当审批规则继续变化,比如增加预算冻结判断、批量审批或并行会签时,只需要在图里增加节点和边,不需要重构整个流程代码。这种可演进的架构对长期维护这类业务系统非常有帮助。

LangGraph报销审批流状态图修改时间:2026-08-26 13:40:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。