Skip to content

LangGraph 术语表

LangGraph 的核心是将代理工作流程建模为图。您通过以下三个关键组件定义代理的行为:

  1. 状态:一个共享数据结构,代表您应用程序当前的快照。它可以是任何 Python 类型,但通常是 TypedDict 或 Pydantic BaseModel

  2. 节点:编码您代理逻辑的 Python 函数。它们接收当前的 状态 作为输入,执行某些计算或副作用,并返回更新后的 状态

  3. :根据当前的 状态 决定下一个要执行的 节点 的 Python 函数。它们可以是条件分支或固定转换。

通过组合 节点,您可以创建复杂的循环工作流程,随着时间的推移演化 状态。然而,LangGraph 的真正力量来源于它如何管理该 状态。需要强调的是:节点 只不过是 Python 函数 - 它们可以包含一个 LLM 或者只是普通的 Python 代码。

简而言之:节点完成工作,边指示下一步该做什么

LangGraph 的底层图算法使用 消息传递 来定义一个通用程序。当一个节点完成其操作时,它沿着一个或多个边发送消息到其他节点。这些接收节点随后执行它们的函数,将所得消息传递给下一组节点,过程继续进行。受到 Google 的 Pregel 系统的启发,程序在离散的“超级步骤”中进行。

可以将超级步骤视为对图节点的单次迭代。在同一超级步骤中并行运行的节点属于同一超级步骤,而顺序运行的节点则归属于不同的超级步骤。在图执行开始时,所有节点都处于 未激活 状态。当节点在其任何入边(或“通道”)上接收到新消息(状态)时,该节点变为 激活。然后,该活跃节点运行其函数并做出更新响应。在每个超级步骤的末尾,没有入消息的节点投票 停顿,将自己标记为 未激活。当所有节点均为 未激活 且没有消息在传输时,图执行终止。

状态图

StateGraph 类是主要的图类。该类由用户定义的 状态 对象参数化。

消息图

MessageGraph 类是一种特殊类型的图。MessageGraph状态 仅仅是消息列表。这个类很少使用,除非用于聊天机器人,因为大多数应用需要的 状态 比消息列表更复杂。

编译你的图

要构建图,您首先定义 状态,然后添加 节点,最后编译它。究竟什么是编译图,为什么需要它?

编译是一个相当简单的步骤。它对图的结构进行一些基本检查(没有孤立节点等)。这也是您可以指定运行时参数的地方,如 检查点断点。您只需调用 .compile 方法即可编译图:

graph = graph_builder.compile(...)

必须 在使用图之前编译它。

状态

定义图时,您首先需要定义图的 状态状态 由图的 模式 和指定如何应用状态更新的 reducer 函数 组成。状态 的模式将是图中所有 节点 的输入模式,可以是 TypedDictPydantic 模型。所有 节点 将释放更新到 状态,然后使用指定的 reducer 函数应用这些更新。

模式

指定图模式的主要文档化方法是使用 TypedDict。不过,我们也支持 使用 Pydantic BaseModel 作为您的图状态,以添加 默认值 和额外的数据验证。

默认情况下,图将具有相同的输入和输出模式。如果您想更改此设置,您也可以直接指定明确的输入和输出模式。当您有很多键时,这非常有用,其中一些键明确用于输入,其他则用于输出。请参阅 此笔记本 了解如何使用。

多种模式

通常,所有图节点都使用单一模式进行通信。这意味着它们将读取和写入相同的状态通道。但是,存在一些情况,我们希望对此有更多控制:

  • 内部节点可以传递在图的输入/输出中不需要的信息。
  • 我们可能还希望为图使用不同的输入/输出模式。例如,输出可能仅包含一个相关的输出键。

可以在图内为内部节点通信写入私有状态通道。我们只需定义一个私有模式,PrivateState。有关更多细节,请参阅 此笔记本

还可以为图定义明确的输入和输出模式。在这些情况下,我们定义一个“内部”模式,包含与图操作相关的 所有 键。但我们也定义 inputoutput 模式,它们是“内部”模式的子集,以限制图的输入和输出。有关更多细节,请参阅 此笔记本

让我们看一个例子:

class InputState(TypedDict):
    user_input: str

class OutputState(TypedDict):
    graph_output: str

class OverallState(TypedDict):
    foo: str
    user_input: str
    graph_output: str

class PrivateState(TypedDict):
    bar: str

def node_1(state: InputState) -> OverallState:
    # 写入 OverallState
    return {"foo": state["user_input"] + " name"}

def node_2(state: OverallState) -> PrivateState:
    # 读取 OverallState,写入 PrivateState
    return {"bar": state["foo"] + " is"}

def node_3(state: PrivateState) -> OutputState:
    # 读取 PrivateState,写入 OutputState
    return {"graph_output": state["bar"] + " Lance"}

builder = StateGraph(OverallState, input=InputState, output=OutputState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_node("node_3", node_3)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
builder.add_edge("node_2", "node_3")
builder.add_edge("node_3", END)

graph = builder.compile()
graph.invoke({"user_input": "My"})
{'graph_output': 'My name is Lance'}

有两个微妙且重要的点需要注意:

  1. 我们将 state: InputState 传递为 node_1 的输入模式。但是,我们写入 foo,这是 OverallState 中的一个通道。我们如何能够写入一个未包含在输入模式中的状态通道?这是因为一个节点 可以写入图状态中的任何状态通道。图状态是初始化时定义的状态通道的联合,包括 OverallState 以及过滤的 InputStateOutputState

  2. 我们使用 StateGraph(OverallState, input=InputState, output=OutputState) 初始化图。那么,我们如何能够在 node_2 中写入 PrivateState?图如何获得对该模式的访问权限如果它没有在 StateGraph 初始化中传递?我们可以做到这一点,因为 节点也可以声明额外的状态通道,只要状态模式定义存在。在这种情况下,定义了 PrivateState 模式,因此我们可以将 bar 添加为图中的新状态通道并写入它。

Reducers

Reducer 是理解节点更新如何应用于 状态 的关键。状态 中的每个键都有自己独立的 reducer 函数。如果没有明确指定 reducer 函数,则默认假设所有对此键的更新都应覆盖它。Reducer 有几种不同类型,从默认类型的 reducer 开始:

默认 Reducer

以下两个示例展示了如何使用默认 reducer:

示例 A:

from typing_extensions import TypedDict

class State(TypedDict):
    foo: int
    bar: list[str]

在这个示例中,没有为任何键指定 reducer 函数。假设图的输入是 {"foo": 1, "bar": ["hi"]}。然后假设第一个 节点 返回 {"foo": 2}。这被视为对状态的更新。请注意,节点 不需要返回整个 状态 模式 - 只需一个更新。应用此更新后,状态 将变为 {"foo": 2, "bar": ["hi"]}。如果第二个节点返回 {"bar": ["bye"]},那么 状态 将变为 {"foo": 2, "bar": ["bye"]}

示例 B:

from typing import Annotated
from typing_extensions import TypedDict
from operator import add

class State(TypedDict):
    foo: int
    bar: Annotated[list[str], add]

在这个示例中,我们使用 Annotated 类型为第二个键(bar)指定了一个 reducer 函数(operator.add)。请注意,第一个键保持不变。假设图的输入是 {"foo": 1, "bar": ["hi"]}。随后假设第一个 节点 返回 {"foo": 2}。这被视为对状态的更新。请注意,节点 不需要返回整个 状态 模式 - 只需一个更新。应用此更新后,状态 将变为 {"foo": 2, "bar": ["hi"]}。如果第二个节点返回 {"bar": ["bye"]},那么 状态 将变为 {"foo": 2, "bar": ["hi", "bye"]}。请注意,此处 bar 键是通过将两个列表相加而进行更新。

处理图状态中的消息

为什么使用消息?

大多数现代LLM提供商都有一个聊天模型接口,接受消息列表作为输入。LangChain的ChatModel特别接受一组Message对象作为输入。这些消息有多种形式,例如HumanMessage(用户输入)或AIMessage(LLM响应)。要了解更多关于消息对象的信息,请参阅此处的概念指南。

在图中使用消息

在许多情况下,将先前的对话历史记录存储为消息列表在图的状态中是很有帮助的。为此,我们可以向图状态添加一个键(通道),以存储Message对象的列表,并用reducer函数进行注释(请参阅下面示例中的messages键)。reducer函数对于告诉图如何在每次状态更新时更新状态中的Message对象列表至关重要(例如,当节点发送更新时)。如果不指定reducer,所有状态更新都会用最新提供的值覆盖消息列表。如果您想简单地将消息追加到现有列表中,可以使用operator.add作为reducer。

但是,您可能还希望手动更新图状态中的消息(例如,人工干预)。如果您使用operator.add,您发送到图的手动状态更新将附加到现有消息列表中,而不是更新现有消息。为避免这种情况,您需要一个可以追踪消息ID并覆盖现有消息(如果更新)reducer。为此,您可以使用预构建的add_messages函数。对于全新的消息,它将简单地附加到现有列表,但它还将正确处理现有消息的更新。

序列化

除了跟踪消息ID之外,add_messages函数还将在接收到messages通道的状态更新时,尝试将消息反序列化为LangChain Message对象。有关LangChain序列化/反序列化的更多信息,请见此处。这允许以如下格式发送图输入/状态更新:

# 这是一种支持的格式
{"messages": [HumanMessage(content="message")]}

# 这种格式也是支持的
{"messages": [{"type": "human", "content": "message"}]}

由于状态更新在使用add_messages时始终会被反序列化为LangChain Messages,因此您应该使用点表示法访问消息属性,如state["messages"][-1].content。下面是一个使用add_messages作为其reducer函数的图示例。

from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages
from typing import Annotated
from typing_extensions import TypedDict

class GraphState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

MessagesState

由于在状态中拥有消息列表是如此常见,因此存在一个名为MessagesState的预构建状态,使使用消息变得简单。MessagesState由一个单一的messages键定义,它是一个AnyMessage对象的列表,并使用add_messages reducer。通常,除了消息之外,还会有更多的状态需要跟踪,因此我们看到人们子类化此状态并添加更多字段,例如:

from langgraph.graph import MessagesState

class State(MessagesState):
    documents: list[str]

节点

在LangGraph中,节点通常是Python函数(同步或async),其中**第一个**位置参数是状态,(可选地)**第二个**位置参数是一个“配置”,包含可选的可配置参数(例如thread_id)。

NetworkX类似,您使用add_node方法将这些节点添加到图中:

from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph

builder = StateGraph(dict)


def my_node(state: dict, config: RunnableConfig):
    print("在节点中: ", config["configurable"]["user_id"])
    return {"results": f"你好, {state['input']}!"}


# 第二个参数是可选的
def my_other_node(state: dict):
    return state


builder.add_node("my_node", my_node)
builder.add_node("other_node", my_other_node)
...

在幕后,函数会被转换为RunnableLambda,这为您的函数添加了批处理和异步支持,以及原生跟踪和调试功能。

如果您没有指定名称将节点添加到图中,它将获得一个默认名称,相当于函数名称。

builder.add_node(my_node)
# 然后,您可以通过将其引用为“my_node”来创建与此节点的边

START节点

START节点是一个特殊的节点,代表将用户输入发送到图的节点。引用此节点的主要目的是确定应首先调用哪些节点。

from langgraph.graph import START

graph.add_edge(START, "node_a")

END节点

END节点是一个特殊的节点,表示一个终端节点。当您想表示完成后没有操作的边时,会引用此节点。

from langgraph.graph import END

graph.add_edge("node_a", END)

边定义了逻辑的路由以及图如何决定停止。这是代理工作的重要部分,以及不同节点之间如何相互通信。有几种关键类型的边:

  • 正常边:直接从一个节点到下一个节点。
  • 条件边:调用一个函数以确定要去下一个哪个节点。
  • 入口点:当用户输入到达时,要首先调用哪个节点。
  • 条件入口点:调用一个函数以确定用户输入到达时要调用哪个节点。

一个节点可以有多个出边。如果一个节点有多个出边,**所有**这些目标节点将在下一次超级步骤中并行执行。

正常边

如果您**总是**想从节点A到节点B,可以直接使用add_edge方法。

graph.add_edge("node_a", "node_b")

条件边

如果您想**可选**地路由到一个或多个边(或者可选地终止),可以使用add_conditional_edges方法。此方法接受节点的名称和一个“路由函数”,该函数在节点执行后调用:

graph.add_conditional_edges("node_a", routing_function)

与节点类似,routing_function接受图的当前state并返回一个值。

默认情况下,routing_function的返回值被用作要将状态发送到下一个的节点名称(或节点列表)。所有这些节点将在下一次超级步骤中并行运行。

您可以选择性地提供一个字典,将routing_function的输出映射到下一个节点的名称。

graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})

提示

如果您想在单个函数中结合状态更新和路由,请使用Command而不是条件边。

入口点

入口点是当图开始时运行的第一个节点。您可以使用虚拟START节点的add_edge方法到要执行的第一个节点,指定何处进入图。

from langgraph.graph import START

graph.add_edge(START, "node_a")

条件入口点

条件入口点让您根据自定义逻辑从不同节点开始。您可以使用虚拟START节点的add_conditional_edges方法来实现这一点。

from langgraph.graph import START

graph.add_conditional_edges(START, routing_function)

您可以选择性地提供一个字典,将routing_function的输出映射到下一个节点的名称。

graph.add_conditional_edges(START, routing_function, {True: "node_b", False: "node_c"})

Send

默认情况下,NodesEdges是在时间上预定义的,并在同一个共享状态上操作。然而,可能会出现一些情况,确切的边不能提前知道,或者您可能希望不同版本的State同时存在。一个常见的例子是map-reduce设计模式。在该设计模式中,第一个节点可能生成一个对象列表,您可能希望将其他节点应用于所有这些对象。对象的数量可能在提前不知道(意味着边的数量可能不知道),并且下游Node的输入State应该是不同的(为每个生成的对象提供一个)。

为了支持这种设计模式,LangGraph支持从条件边返回Send对象。Send接受两个参数:第一个是节点的名称,第二个是要传递给该节点的状态。

def continue_to_jokes(state: OverallState):
    return [Send("generate_joke", {"subject": s}) for s in state['subjects']]

graph.add_conditional_edges("node_a", continue_to_jokes)

Command

将控制流(边)和状态更新(节点)结合起来可能是有用的。例如,您可能希望在同一个节点中同时执行状态更新和决定下一个要去的节点。LangGraph通过从节点函数返回Command对象提供了一种方法:

def my_node(state: State) -> Command[Literal["my_other_node"]]:
    return Command(
        # 状态更新
        update={"foo": "bar"},
        # 控制流
        goto="my_other_node"
    )

使用 Command 你也可以实现动态控制流行为(与 条件边 相同):

def my_node(state: State) -> Command[Literal["my_other_node"]]:
    if state["foo"] == "bar":
        return Command(update={"foo": "baz"}, goto="my_other_node")

重要

当在你节点函数中返回 Command 时,必须添加返回类型注解,列出节点路由到的节点名称,例如 Command[Literal["my_other_node"]]。这是图形渲染所必需的,并告诉 LangGraph my_node 可以导航到 my_other_node

查看 这个如何做的指南,了解如何使用 Command 的从头到尾示例。

何时应使用 Command 而不是条件边?

当你需要 同时 更新图形状态 路由到不同的节点时,请使用 Command。例如,在实现 多代理交接 时,将重要信息传递给不同的代理。

使用 条件边 在节点之间条件性路由,而不更新状态。

在工具内部使用

一个常见用例是从工具内部更新图形状态。例如,在客户支持应用程序中,你可能想根据客户的账户号码或 ID 在对话开始时查找客户信息。要从工具更新图形状态,可以从工具返回 Command(update={"my_custom_key": "foo", "messages": [...]})

@tool
def lookup_user_info(tool_call_id: Annotated[str, InjectedToolCallId], config: RunnableConfig):
    """使用此方法查找用户信息,更好地帮助他们解决问题。"""
    user_info = get_user_info(config.get("configurable", {}).get("user_id"))
    return Command(
        update={
            # 更新状态键
            "user_info": user_info,
            # 更新消息历史
            "messages": [ToolMessage("成功查找用户信息", tool_call_id=tool_call_id)]
        }
    )

重要

当从工具返回 Command 时,您必须在 Command.update 中包含 messages(或用于消息历史的任何状态键),且 messages 中的消息列表必须包含 ToolMessage。这是使结果消息历史有效所必需的(LLM 提供商要求带有工具调用的 AI 消息后跟工具结果消息)。

如果你使用通过 Command 更新状态的工具,我们建议使用预构建的 ToolNode,它会自动处理返回 Command 对象的工具,并将其传播到图形状态。如果你正在编写调用工具的自定义节点,你需要手动传播工具返回的 Command 对象作为节点的更新。

人机交互

Command 是人机交互工作流的重要部分:当使用 interrupt() 收集用户输入时,Command 被用于提供输入并通过 Command(resume="用户输入") 恢复执行。有关更多信息,请查看 这个概念指南

持久性

LangGraph 提供了内置的持久性,支持你的代理状态,使用 检查点。检查点在每个超级步骤保存图形状态的快照,允许随时恢复。这使得人机交互、内存管理和容错支持等功能成为可能。你甚至可以在图形执行后直接使用适当的 getupdate 方法操作图形状态。有关更多详细信息,请参阅 持久性概念指南

线程

LangGraph 中的线程表示你的图形与用户之间的单个会话或对话。在使用检查点时,单个对话中的回合(甚至单个图形执行中的步骤)由唯一的线程 ID 组织。

存储

LangGraph 通过 BaseStore 接口提供内置的文档存储。与通过线程 ID 保存状态的检查点不同,存储使用自定义命名空间来组织数据。这使得跨线程持久性成为可能,允许代理维护长期记忆、从过去的交互中学习并随时间累积知识。常见用例包括存储用户档案、构建知识库和在所有线程中管理全局偏好。

图形迁移

LangGraph 可以轻松处理图形定义(节点、边和状态)的迁移,即使在使用检查点跟踪状态时也是如此。

  • 对于图形末端的线程(即未中断的)你可以更改图形的整个拓扑(即所有节点和边,移除、添加、重命名等)
  • 对于当前被打断的线程,我们支持所有拓扑更改,除了重命名/移除节点(因为该线程现在可能会进入一个已不存在的节点)——如果这成为障碍,请联系以便我们优先处理解决方案。
  • 对于修改状态,我们完全向后和向前兼容添加和移除键
  • 被重命名的状态键在现有线程中会失去其保存的状态
  • 状态键的类型以不兼容的方式变化可能会在包含更改之前状态的线程中造成问题——如果这是一个障碍,请联系以便我们优先处理解决方案。

配置

创建图形时,你还可以标记图形的某些部分为可配置。这通常用于轻松切换模型或系统提示。这样,你可以创建一个单一的“认知架构”(图形),但有多个不同的实例。

创建图形时,可以选择性地指定 config_schema

class ConfigSchema(TypedDict):
    llm: str

graph = StateGraph(State, config_schema=ConfigSchema)

然后你可以使用 configurable 配置字段将此配置传递给图形。

config = {"configurable": {"llm": "anthropic"}}

graph.invoke(inputs, config=config)

然后你可以在节点内部访问和使用此配置:

def node_a(state, config):
    llm_type = config.get("configurable", {}).get("llm", "openai")
    llm = get_llm(llm_type)
    ...

参见 这个指南 获取关于配置的完整说明。

递归限制

递归限制设置图形在单次执行期间可以执行的最大 超级步骤 数量。一旦达到限制,LangGraph 将引发 GraphRecursionError。默认情况下,此值设置为 25 步。递归限制可以在运行时对任何图形进行设置,并通过配置字典传递给 .invoke/.stream。重要的是,recursion_limit 是一个独立的 config 键,不应像所有其他用户定义的配置那样在 configurable 键内传递。请参见以下示例:

graph.invoke(inputs, config={"recursion_limit": 5, "configurable":{"llm": "anthropic"}})

阅读 这个如何做的指南 了解更多关于递归限制如何工作的信息。

interrupt

使用 interrupt 函数在特定点 暂停 图形,以收集用户输入。interrupt 函数向客户端传递中断信息,允许开发人员收集用户输入、验证图形状态或在恢复执行之前进行决策。

from langgraph.types import interrupt

def human_approval_node(state: State):
    ...
    answer = interrupt(
        # 此值将发送给客户端。
        # 它可以是任何 JSON 可序列化的值。
        {"question": "是否可以继续?"},
    )
    ...

通过将 Command 对象传递到图形,resume 键设置为 interrupt 函数返回的值,来恢复图形的执行。

阅读更多关于如何在 人机交互概念指南 中使用 interrupt 进行 人机交互 工作流的信息。

断点

断点在特定点暂停图形执行,并允许逐步执行。断点由 LangGraph 的 持久性层 提供支持,该层在每个图形步骤后保存状态。断点也可以用于启用 人机交互 工作流,尽管我们建议使用 interrupt 函数 作为此目的。

阅读关于断点的更多信息,请参见 断点概念指南

子图

子图是作为另一个图中的 节点 使用的 。这无非就是将封装这一古老概念应用于 LangGraph。使用子图的一些原因包括:

  • 构建 多代理系统

  • 当你想在多个图中重用一组节点(可能共享一些状态)时,可以首先在子图中定义它们,然后在多个父图中使用它们

  • 当你希望不同团队独立工作于图的不同部分时,可以将每个部分定义为子图,只要遵循子图接口(输入和输出模式),父图就可以在不了解子图的任何细节的情况下构建

有两种方法可以将子图添加到父图中:

  • 添加一个带有编译子图的节点:当父图和子图共享状态键且不需要在输入或输出转换状态时,这很有用。
builder.add_node("subgraph", subgraph_builder.compile())
  • 添加一个带有调用子图的函数的节点:当父图和子图具有不同的状态模式且需要在调用子图之前或之后转换状态时,这很有用。
    子图 = 子图构建器.compile()
    
    def 调用子图(状态: 状态):
        return 子图.invoke({"子图键": 状态["父键"]})
    
    构建器.add_node("子图", 调用子图)
    

让我们来看看每种情况的例子。

作为一个编译图

创建子图节点的最简单方法是直接使用编译的子图。这样做时,**重要**的是父图和子图的state schemas至少共享一个关键字以便进行通信。如果您的图和子图没有任何共享的关键字,您应该编写一个调用子图的函数

注意

如果您向子图节点传递额外的关键字(即,除了共享的关键字之外),它们将被子图节点忽略。同样,如果您从子图返回额外的关键字,它们也会被父图忽略。

from langgraph.graph import StateGraph
from typing import TypedDict

class State(TypedDict):
    foo: str

class SubgraphState(TypedDict):
    foo: str  # 注意这个关键字与父图状态共享
    bar: str

# 定义子图
def subgraph_node(state: SubgraphState):
    # 注意这个子图节点可以通过共享的“foo”关键字与父图通信
    return {"foo": state["foo"] + "bar"}

subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node)
...
subgraph = subgraph_builder.compile()

# 定义父图
builder = StateGraph(State)
builder.add_node("subgraph", subgraph)
...
graph = builder.compile()

作为一个函数

您可能希望定义一个具有完全不同模式的子图。在这种情况下,您可以创建一个调用子图的节点函数。这个函数需要在调用子图之前将输入(父)状态转换为子图状态,并在从节点返回状态更新之前将结果转换回父状态。

class State(TypedDict):
    foo: str

class SubgraphState(TypedDict):
    # 注意这些关键字与父图状态没有共享
    bar: str
    baz: str

# 定义子图
def subgraph_node(state: SubgraphState):
    return {"bar": state["bar"] + "baz"}

subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node)
...
subgraph = subgraph_builder.compile()

# 定义父图
def node(state: State):
    # 将状态转换为子图状态
    response = subgraph.invoke({"bar": state["foo"]})
    # 将响应转换回父状态
    return {"foo": response["bar"]}

builder = StateGraph(State)
# 注意我们使用的是`node`函数,而不是已编译的子图
builder.add_node(node)
...
graph = builder.compile()

可视化

能够可视化图形通常是很好的,特别是当它们变得越来越复杂时。LangGraph 提供了几种内置的方法来可视化图形。有关更多信息,请查看本指南

流式处理

LangGraph 是专为支持流式处理而构建的,包括在执行过程中从图节点流式更新、从 LLM 调用中流式传递令牌等。有关更多信息,请参见本概念指南

优云智算