智能体解析
当 AgentRuntime 通过 run_to_completion(request) 或 run(request, sink) 执行请求时,请求中的 agent_id 会通过 Resolver 解析为 ResolvedRunPlan。其中的 execution plan 由本地 ResolvedAgent 或非本地 ResolvedBackendAgent 承载。本地 ResolvedAgent 持有 LLM 执行器、工具、插件和执行环境的活引用;非本地执行则由 ExecutionBackend 承载,例如内置 A2A backend。每次调用都会重新解析;本地 agent 环境不会在 run 之间共享。
本地解析是一个纯函数:(RegistrySet, agent_id) -> ResolvedAgent。它按顺序经过三个阶段:
flowchart LR
subgraph Stage1["Stage 1: Lookup"]
A1[Fetch AgentSpec] --> A2[Resolve ModelSpec]
A2 --> A3[Get LlmExecutor]
A3 --> A4{Retry config?}
A4 -- yes --> A5[Wrap in RetryingExecutor]
A4 -- no --> A6[Use executor directly]
end
subgraph Stage2["Stage 2: Plugin Pipeline"]
B1[Resolve plugin IDs] --> B2[Inject default plugins]
B2 --> B3{context_policy set?}
B3 -- yes --> B4[Add CompactionPlugin + ContextTransformPlugin]
B3 -- no --> B5[Skip]
B4 --> B6[Validate config sections]
B5 --> B6
B6 --> B7[Build ExecutionEnv]
end
subgraph Stage3["Stage 3: Tool Pipeline"]
C1[Collect global tools] --> C2[Merge delegate agent tools]
C2 --> C3[Merge plugin-registered tools]
C3 --> C4[Check for ID conflicts]
C4 --> C5[Apply catalog filter]
end
Stage1 --> Stage2 --> Stage3
任何阶段的失败都会产生 ResolveError 并中止流程。流水线永远不会返回部分结果。
阶段 1:查找
Section titled “阶段 1:查找”第一阶段从注册表中获取原始数据:
-
AgentSpec — 通过
agent_id从AgentSpecRegistry中查找。如果对带endpoint的规格请求直接本地解析,会以RemoteAgentNotDirectlyRunnable失败。AgentRuntime的 root run 会解析为ResolvedRunPlan;存在 backend factory 时,endpoint-backed agent 使用非本地 execution plan。 -
ModelSpec — 规格的
model_id字段(模型注册表 ID,如"default")通过ModelRegistry解析为ModelSpec,其中携带 provider ID、上游 API 模型名(例如 provider"openai"、上游模型"gpt-4o"),以及可选的固有能力(上下文窗口、最大输出、modalities、知识截止)和定价。服务端配置 API 直接持久化ModelSpec,发布 registry 时无需二次转换。 -
LlmExecutor — 解析得到的
ModelSpec中的 provider ID 通过ProviderRegistry查到一个活的LlmExecutor实例。 -
重试装饰 — 如果智能体规格包含
RetryConfigKey配置段,且max_retries > 0,则执行器会被包装在RetryingExecutor装饰器中。
对于 endpoint-backed agent,解析过程会校验 RemoteEndpoint,构建 ResolvedBackendAgent,并把执行交给选中的 ExecutionBackend。这类 root run 会跳过本地 model/provider/plugin/tool 流水线,因为这些决策由远端 backend 拥有。
阶段 2:插件流水线
Section titled “阶段 2:插件流水线”第二阶段组装插件链并构建执行环境。
AgentSpec.plugin_ids 中列出的插件通过 ID 从 PluginSource 解析。缺失的插件会产生 ResolveError::PluginNotFound。
默认插件注入
Section titled “默认插件注入”解析用户声明的插件后,流水线会注入运行时必需的默认插件。无论智能体如何配置,这些插件始终存在:
-
LoopActionHandlersPlugin— 注册运行时循环用于处理工具调用、发射事件和管理步骤转换的核心动作处理器。没有此插件,循环无法运行。 -
MaxRoundsPlugin— 强制执行智能体规格上配置的max_rounds停止条件。使用规格的max_rounds值注入。防止失控循环。
仅当设置了 AgentSpec.context_policy 时才添加以下插件:
-
CompactionPlugin— 管理上下文窗口压缩(当上下文增长过大时对旧消息进行摘要)。使用规格中的CompactionConfigKey配置段创建,缺失时回退到默认值。 -
ContextTransformPlugin— 在每次推理请求前应用上下文窗口策略变换(token 计数、截断、提示缓存)。使用context_policy值创建。
构建 ExecutionEnv
Section titled “构建 ExecutionEnv”插件列表确定后,ExecutionEnv::from_plugins() 对每个插件调用其 register() 方法并传入 PluginRegistrar。插件通过注册器声明:
- 阶段钩子(按阶段回调)
- 调度动作处理器
- 效果处理器
- 请求变换
- 状态键注册
- 工具
结果是一个 ExecutionEnv — 见下文 ExecutionEnv。
插件可以声明 config_schemas(),返回一组 ConfigSchema 条目。每个条目将一个配置段键与一个 JSON Schema 关联。解析期间,每个声明的 schema 都会针对 AgentSpec.sections 中的对应条目进行验证:
- 配置段存在 — 针对 JSON Schema 进行验证。失败会产生
ResolveError::InvalidPluginConfig。 - 配置段缺失 — 允许。插件应使用合理的默认值。
- 配置段存在但无插件认领 — 没有插件为其声明 schema。流水线记录一条警告(可能是配置中的拼写错误)。
阶段 3:工具流水线
Section titled “阶段 3:工具流水线”第三阶段从所有来源收集工具并生成最终工具集。
工具按以下顺序合并:
-
全局工具 — 所有通过构建器在
ToolRegistry中注册的工具(例如builder.with_tool("search", search_tool))。 -
委托智能体工具 — 对于
AgentSpec.delegates中的每个智能体 ID,流水线创建一个AgentTool。如果委托有endpoint(远程),流水线会选择配置的ExecutionBackend。当前内置远程 backend 是 A2A;本地委托仍然使用 resolver-backed 本地执行。委托工具需要a2afeature flag;没有该 flag 时,委托会被静默忽略并记录 warning。 -
插件注册的工具 — 插件在
register()期间声明的工具,存储在ExecutionEnv.tools中。
如果插件注册的工具与全局工具具有相同的 ID,解析会以 ResolveError::ToolIdConflict 失败。这是有意为之的 — 静默覆盖会成为难以调试的问题来源。
目录过滤(Catalog filtering)
Section titled “目录过滤(Catalog filtering)”解析器在工具收集之后、插件流水线之前应用目录过滤。匹配规则:
- 计算
allow_set=allowed_tools中的字面量 ∪ 匹配allowed_tool_patterns中任一模式的工具。 - 计算
exclude_set=excluded_tools中的字面量 ∪ 匹配excluded_tool_patterns中任一模式的工具。 - 保留下来的工具 =
allow_set−exclude_set。
“拒绝优先”的优先级让 excluded_tool_patterns 成为一张开放式的安全网 ——
excluded_tool_patterns: ["dangerous-*"] 会排除将来出现的同形工具,而不仅
是当前已注册的。字面量排除则严格基于当下的快照:只会移除被点名的 tool id。
运行时在解析阶段会发出三类 warning:
- 未命中模式 —
*_tool_patterns中的某条模式没有匹配任何已注册工具, 通常是拼写错误或来自另一个 runtime 的过时条目。 - 形如 permission 的目录条目 — 目录中出现含
(...)的条目,很可能本意 是写到sections["permission"]里。 - 孤立的 permission 规则 — permission 规则引用了目录已经过滤掉的工具。
这些 warning 不会阻塞解析,仅作为诊断信号。
ExecutionEnv
Section titled “ExecutionEnv”ExecutionEnv 是插件流水线的每次解析产物。它不是全局或共享的 — 每次 resolve() 调用都会构建一个全新的实例。其内容:
| 字段 | 类型 | 用途 |
|---|---|---|
phase_hooks | HashMap<Phase, Vec<TaggedPhaseHook>> | 在每个阶段边界调用的钩子 |
scheduled_action_handlers | HashMap<String, ScheduledActionHandlerArc> | 用于调度/延迟动作的命名处理器 |
effect_handlers | HashMap<String, EffectHandlerArc> | 用于副作用的命名处理器 |
request_transforms | Vec<TaggedRequestTransform> | LLM 调用前应用于推理请求的变换 |
key_registrations | Vec<KeyRegistration> | 运行开始时安装到状态存储的状态键 |
tools | HashMap<String, Arc<dyn Tool>> | 插件提供的工具(在阶段 3 合并到主工具集) |
plugins | Vec<Arc<dyn Plugin>> | 用于生命周期钩子的插件引用(on_activate/on_deactivate) |
每个 TaggedPhaseHook 和 TaggedRequestTransform 都携带其所属插件的 ID,用于诊断和过滤。
AgentRuntimeBuilder
Section titled “AgentRuntimeBuilder”构建器(AgentRuntimeBuilder)是构造 AgentRuntime 的标准方式。它累积五个注册表:
| 注册表 | 构建器方法 | 用途 |
|---|---|---|
MapAgentSpecRegistry | with_agent_spec() / with_agent_specs() | 智能体定义 |
MapToolRegistry | with_tool() | 全局工具 |
MapModelRegistry | with_model() | 模型 ID 到 ModelSpec(provider + 上游模型 + capabilities + 定价) |
MapProviderRegistry | with_provider() | LLM 执行器实例 |
MapPluginSource | with_plugin() | 插件实例 |
构建器使用延迟错误收集。每个检测到冲突(重复 ID)的 with_* 调用会将 BuildError 推入内部错误列表,而不是返回 Result。第一个收集到的错误在调用 build() 或 build_unchecked() 时浮现。
build() 在构造运行时后对每个已注册的智能体规格执行一次试运行解析。如果任何智能体解析失败(缺失模型、缺失提供者、缺失插件),错误会被收集并作为 BuildError::ValidationFailed 返回。这在启动时而非首次请求时捕获配置错误。
build_unchecked() 跳过此验证。仅在需要延迟解析或智能体将在构造后动态添加时使用。
远程智能体 (A2A)
Section titled “远程智能体 (A2A)”当启用 a2a 功能标志时,构建器支持 with_remote_agents() 来注册远程 A2A 端点。这些端点被包装在 CompositeAgentSpecRegistry 中,该注册表组合了本地和远程智能体来源。远程智能体通过 build_and_discover() 异步发现。
- 架构 — 系统分层与请求序列
- Run 生命周期与阶段 — 解析之后发生什么
- 工具与插件边界 — 何时使用工具 vs 插件
- 设计权衡 — 关键决策的理由