Skip to content
Embabel 文档版本 0.3.0-SNAPSHOT 源快照 a751fa26 更新于 2026-09-29

06 · 流程(Flow)

调用智能体(Invoking an Agent)

Agent 既可以通过编程方式调用,也可以通过用户输入调用。

关于编程式调用的细节,见《调用 Embabel 智能体》(/reference/invoking)。 编程式调用通常涉及结构化类型,而不是用户输入。

在用户输入的场景下,会由一个 LLM 通过 Autonomy 类来选择合适的 Agent。 具体行为取决于配置:

  • Closed 模式:LLM 根据用户输入以及系统中可用的 Agent 来选择 Agent。
  • Open 模式:LLM 根据用户输入选择目标,然后从当前世界状态中组装出一个能够实现该目标的 Agent。

智能体流程(Agent Process Flow)

当 Agent 被调用时,Embabel 会创建一个带有唯一标识符的 AgentProcess,由它管理完整的执行生命周期。

AgentProcess 生命周期

AgentProcess 在执行过程中维护状态,并可以在多种状态之间迁移。

进程状态:

  • NOT_STARTED:进程尚未开始
  • RUNNING:进程正在执行,没有已知问题
  • COMPLETED:进程已成功完成
  • FAILED:进程已失败,无法继续
  • TERMINATED:进程被提前终止策略杀死
  • KILLED:进程被用户或平台杀死
  • STUCK:进程无法制定出可推进的计划(可能是临时状态)
  • WAITING:进程正在等待用户输入或外部事件
  • PAUSED:进程因调度策略而暂停

进程执行方法:

  • tick():执行下一个单步,并在某个 action 完成时返回
  • run():尽可能执行进程,直到完成、失败或进入等待状态

这些方法不由用户代码直接调用,而是由框架管理,以控制执行流。

每个 AgentProcess 维护以下信息:

  • Unique ID:用于追踪与引用的持久标识符
  • History:所有已执行 action 的记录,含时间信息
  • Goal:进程试图达成的目标
  • Failure Info:发生失败时的详细信息
  • Parent ID:嵌套执行时指向父进程的引用

规划(Planning)

每次 action 执行之后都会进行一次规划,采用的是 GOAP(Goal-Oriented Action Planning,目标导向动作规划)。 规划过程如下:

  1. 分析当前状态(Analyze Current State):检查当前黑板(blackboard)的内容与世界状态。
  2. 识别可用动作(Identify Available Actions):根据前置条件(preconditions)找出所有可执行的 action。
  3. 搜索动作序列(Search for Action Sequences):使用 A* 算法寻找达成目标的最优路径。
  4. 选择最优计划(Select Optimal Plan):根据成本与成功概率选择最佳的 action 序列。
  5. 执行下一个动作(Execute Next Action):执行计划中的第一个 action,然后重新规划。

这就形成了一个动态的 OODA 循环:

  • Observe(观察):检查当前黑板状态与 action 结果。
  • Orient(判断):理解自上一个规划周期以来发生了什么变化。
  • Decide(决策):根据新信息制定或更新计划。
  • Act(行动):执行计划中的下一个 action。

这种持续重规划的方式让 Agent 能够:

  • 适应意料之外的 action 结果;
  • 应对条件不断变化的动态环境;
  • 从部分失败中恢复;
  • 利用新出现的机会。

黑板(Blackboard)

黑板是一个共享内存系统,用于在 Agent 进程执行期间维护状态。它实现了 黑板架构模式(一种基于知识的系统方法)。

大多数情况下,用户代码不需要直接与黑板交互,因为它由框架管理。例如: action 的输入来自黑板,action 的输出会自动加入黑板,而 condition 则依据黑板内容进行评估。

关键特征:

  • Central Repository(中央仓库):存放所有领域对象、中间结果与进程状态。
  • Type-Based Access(按类型访问):对象按其类型进行索引与检索。
  • Ordered Storage(有序存储):对象保持添加顺序,默认取用最近添加的那个。
  • Immutable Objects(不可变对象):对象一旦加入便不可修改,但可以添加新版本。
  • Condition Tracking(条件追踪):维护规划系统所用的布尔条件。

核心操作:

java
// 向黑板添加对象 ①
blackboard.add(person);
blackboard.set("result", analysis);

// 按类型取回对象
Person person = blackboard.last(Person.class);
List<Person> allPersons = blackboard.all(Person.class);

// 检查条件
blackboard.setCondition("userVerified", true);
boolean verified = blackboard.getCondition("userVerified"); // ②

// 隐藏一个对象
blackboard.hide(somethingWeDontWantToPlanOnLater); // ③

注记:

  1. 添加对象:当 action 方法返回对象时,对象会自动加入黑板,因此通常不需要调用这个 API。 也可以手动添加:使用 += 运算符(仅 Kotlin),或带可选 key 的 add / set 方法。
  2. 条件:条件通常由 @Condition 方法计算得出,因此通常也不需要通过 API 去检查或设置。
  3. 隐藏对象:阻止某个对象在后续规划周期中被考虑。例如,该对象可能是一条已经被处理过的命令。 它仍会保留在黑板历史中,但不再参与规划,也无法通过黑板 API 访问。

数据流:

  1. 输入处理(Input Processing):初始的用户输入加入黑板。
  2. 动作执行(Action Execution):每个 action 从黑板读取输入,并把结果写回。
  3. 状态演进(State Evolution):黑板不断累积表示演化状态的对象。
  4. 规划输入(Planning Input):当前黑板状态用于下一个规划周期。
  5. 结果提取(Result Extraction):完成时从黑板取出最终结果。

黑板带来以下能力:

  • 松耦合(Loose Coupling):action 之间不需要直接相互引用。
  • 灵活的数据流(Flexible Data Flow):action 可以消费任何类型正确的可用数据。
  • 状态持久化(State Persistence):完整的执行历史得以保留。
  • 调试支持(Debugging Support):可以完整观察状态演进,便于排障。

绑定(Binding)

默认情况下,黑板中的条目按类型匹配。当同一类型存在多个候选对象时,会提供最近添加的那个。 也可以为黑板条目指定一个具体的名称。

下面是在 action 方法中显式绑定的例子:

java
@Action
public Person extractPerson(UserInput userInput, OperationContext context) {
    PersonImpl maybeAPerson = context.promptRunner()
            .withLlm(LlmOptions.fromModel(OpenAiModels.GPT_41))
            .createObjectIfPossible(
                """
                Create a person from this user input, extracting their name:
                %s""".formatted(userInput.getContent()),
                PersonImpl.class
            );
    if (maybeAPerson != null) {
        context.bind("user", maybeAPerson); // ①
    }
    return maybeAPerson;
}

① 显式绑定到黑板。通常并不需要,因为 action 方法的返回值会自动绑定。

下面的例子要求黑板中存在一个名为 thingOne 的 Thing:

java
@Action
public Whatever doWithThing(
        @RequireNameMatch Thing thingOne) { // ①

① 参数上的 @RequireNameMatch 注解表示:该参数应同时按类型和名称匹配。多个参数都可以这样标注。

下面的例子使用 @Action.outputBinding,让一个 thingOne 被绑定到黑板,从而满足上一个例子的要求:

java
@Action(outputBinding = "thingOne")
public Thing bindThing1() { ...

当按类型路由 flow 时,名称并不重要;供参考,默认名称是 'it'。

上下文(Context)

Embabel 提供了一种存储更长期状态的方式:com.embabel.agent.core.Context。

黑板绑定于某个特定的 Agent 进程,而 Context 可以跨多个进程持久存在。

Context 通过唯一的 contextId 字符串标识。启动 Agent 进程时,可以在 ProcessOptions 中指定 contextId。 这样会把指定 Context 中存储的数据填充到该进程的黑板中。

Context 的持久化取决于 com.embabel.agent.spi.ContextRepository 的实现。

默认实现只在内存中工作,因此无法在服务器重启后保留数据。

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。