大家好,今天给大家来分享一下在langgraph中的command概念,前面几章的内容我大多都是讲langgraph中的概念和一些低代码平台相结合起来进行理解,但是从本章可能不会这样来写,因为在框架中有的概念是低代码平台当中没有的,不过我还是会使用比较容易理解的词藻来向各位分享今天要说的内容。 

       正式开始之前,我们先讲一个东西,有使用过低代码平台的朋友都清楚,每一个节点的连接都是需要通过我们手动的去用线来连接的,当我们把这些实际的线来抽象对比到langgraph中来,我们就知道在langgraph中是用edges这个东西。我今天要分享的这个内容和它有一定的关联,它具有同样的功能,同时在这个功能的基础上还另外的一些能力,下面我们来看一下:

       Command是一个用于统一控制图执行(state更新和router导航)的核心原语。怎么理解这个概念呢?Command的英文本意是“命令”,其实这里就可以把它理解成一种“指令”,它支持你在一个函数(节点或者工具)执行结束之后,同时的完成两项操作:修改状态(Update)和决定下一步去哪里(Goto)。各位可以在脑子里面想一下,在以往,这两项操作我们是不是要单独的步骤才能做到,第一是state的更新:节点函数直接返回状态的更新字典;第二是路由导航:通过add_conditional_edges 定义独立的路由函数来确定下一步。Command支持同时处理这两项操作,目的就是使得函数体内根据逻辑动态决策,同时代码也更加紧凑且易于围护,这里我们要从本质上搞清楚,使用langgraph这个框架本质上还是我们写代码来开发功能,不是像低代码那样界面友好,既然是用代码来开发我们就必须要提高代码的质量。

       搞清楚这个“命令”是解决什么问题之后,我们来拆解一下里面具体的一些参数和应用场景。

Command 接受四个主要参数:

update:用于更新图的状态(相当于节点正常返回的更新字典)。
goto:指定下一步跳转的节点名称(类似于条件边的作用)。
graph:当处于子图(Subgraph)时,用于指定跳转到父图中的节点(如 Command.PARENT)。
resume:用于在中断(Interrupt)后恢复执行,通过该参数提供中断点所需的输入值。

我们完全可以从参数的字面意思去理解他们的含义,这里我们不过多的去赘述了,重点讲一下后面两个参数graph/resume以及它的应用场景。graph怎么来理解呢?这里可以想一下这个场景,在低代码平台中,我们基本上也会有一个“parent graph(父图,这里可以理解为一个主工作流)”,这个父图中可能会有一些节点使用到其他一些子流程,我们把它叫做“Subgraph(实际上就是一个子工作流)”,我们在父图中怎么用子图的流程呢,一般平台是可以直接引用的,它已经封装好了。那么在langgraph这个框架中我们怎么做同样的操作呢,graph这个参数就提供了这样的能力,当流程在子图中时,可以通过这个参数回到父图的流程当中。

       resume这个参数就更加重要了,必须要理解它,说这个参数之前,我们先简单说一下Human-in-the-loop(人机协同),这个其实是一个Agent开发中老生常谈的问题了,就是在遇到敏感操作时,工作流程会进行中断interrupt(),然后我们需要通过人机协同的方式获取同意,然后才能继续后面的操作。那这里面是怎么实现的呢,后面我会分享到这个内容,这里我们重点先来看resume这个参数,resume的中文译就是“恢复”,意思就是当图执行到interrupu()被暂停等待外部输入时,可以用Command(resume=...) 将外部值传入,作为interrupt()的返回值恢复程序执行。

       接下来我们看一下具体的应用场景:

首先,组合更新和路由,当节点执行完逻辑后,根据结果更新状态并跳转到特定节点:

from langgraph.types import Command
from typing import Literal

def my_node(state: State) -> Command[Literal["next_node"]]:
    # 既修改状态,又控制流程
    return Command(
        update={"foo": "bar"}, 
        goto="next_node"
    )

这里还需要提一下一个注意事项:

  • 类型注解:当你从节点返回 Command 时,必须使用 Command[Literal["node_name"]] 进行类型标注。这不仅是 Python 类型检查的需要,LangGraph 也依赖它来构建图的路由信息。
  • 避免混合使用:对于同一个节点,要么使用 Command 进行动态路由,要么使用传统的 add_edge 或 add_conditional_edges 进行静态/动态路由,不要两者混用,否则会导致执行逻辑难以调试。
  • 路由优先:如果同时存在静态边和 Command 路由,两者可能会同时生效导致并行执行。

然后这里再举一个resume的例子:

from typing import TypedDict

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt


class State(TypedDict):
    messages: list[dict]


def human_review(state: State):
    # Pauses the graph and waits for a value
    answer = interrupt("Do you approve?")
    return {"messages": [{"role": "user", "content": answer}]}


graph = (
    StateGraph(State)
    .add_node("human_review", human_review)
    .add_edge(START, "human_review")
    .add_edge("human_review", END)
    .compile(checkpointer=InMemorySaver())
)

config = {"configurable": {"thread_id": "graph-api-resume"}}

# First run - hits the interrupt and pauses
stream = graph.stream_events({"messages": []}, config, version="v3")
_ = stream.output  # drive the stream to completion
print(stream.interrupts)

# Resume with a value - the interrupt() call returns "yes"
resumed = graph.stream_events(Command(resume="yes"), config, version="v3")
final = resumed.output

上面就是一个关于恢复的小demo,再没有接触到相关Human-in-the-loop中的相关概念时可能不太好理解上面的代码,但是没事,这个阶段我们先搞清楚他的作用是什么就可以,后续我们会系统的来拆解它。

       好的,上面就是我在学习对应概念的过程中个人的一些理解,我也尽量用一些易于理解的例子来举例,如果文章中有不对之处,欢迎各位朋友再评论区指正,感谢各位,谢谢!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐