主题
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,目标导向动作规划)。 规划过程如下:
- 分析当前状态(Analyze Current State):检查当前黑板(blackboard)的内容与世界状态。
- 识别可用动作(Identify Available Actions):根据前置条件(preconditions)找出所有可执行的 action。
- 搜索动作序列(Search for Action Sequences):使用 A* 算法寻找达成目标的最优路径。
- 选择最优计划(Select Optimal Plan):根据成本与成功概率选择最佳的 action 序列。
- 执行下一个动作(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); // ③注记:
- 添加对象:当 action 方法返回对象时,对象会自动加入黑板,因此通常不需要调用这个 API。 也可以手动添加:使用
+=运算符(仅 Kotlin),或带可选 key 的add/set方法。 - 条件:条件通常由
@Condition方法计算得出,因此通常也不需要通过 API 去检查或设置。 - 隐藏对象:阻止某个对象在后续规划周期中被考虑。例如,该对象可能是一条已经被处理过的命令。 它仍会保留在黑板历史中,但不再参与规划,也无法通过黑板 API 访问。
数据流:
- 输入处理(Input Processing):初始的用户输入加入黑板。
- 动作执行(Action Execution):每个 action 从黑板读取输入,并把结果写回。
- 状态演进(State Evolution):黑板不断累积表示演化状态的对象。
- 规划输入(Planning Input):当前黑板状态用于下一个规划周期。
- 结果提取(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 的实现。
默认实现只在内存中工作,因此无法在服务器重启后保留数据。