跳转到内容

智能体解析

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. AgentSpec — 通过 agent_idAgentSpecRegistry 中查找。如果对带 endpoint 的规格请求直接本地解析,会以 RemoteAgentNotDirectlyRunnable 失败。AgentRuntime 的 root run 会解析为 ResolvedRunPlan;存在 backend factory 时,endpoint-backed agent 使用非本地 execution plan。

  2. ModelSpec — 规格的 model_id 字段(模型注册表 ID,如 "default")通过 ModelRegistry 解析为 ModelSpec,其中携带 provider ID、上游 API 模型名(例如 provider "openai"、上游模型 "gpt-4o"),以及可选的固有能力(上下文窗口、最大输出、modalities、知识截止)和定价。服务端配置 API 直接持久化 ModelSpec,发布 registry 时无需二次转换。

  3. LlmExecutor — 解析得到的 ModelSpec 中的 provider ID 通过 ProviderRegistry 查到一个活的 LlmExecutor 实例。

  4. 重试装饰 — 如果智能体规格包含 RetryConfigKey 配置段,且 max_retries > 0,则执行器会被包装在 RetryingExecutor 装饰器中。

对于 endpoint-backed agent,解析过程会校验 RemoteEndpoint,构建 ResolvedBackendAgent,并把执行交给选中的 ExecutionBackend。这类 root run 会跳过本地 model/provider/plugin/tool 流水线,因为这些决策由远端 backend 拥有。

第二阶段组装插件链并构建执行环境。

AgentSpec.plugin_ids 中列出的插件通过 ID 从 PluginSource 解析。缺失的插件会产生 ResolveError::PluginNotFound

解析用户声明的插件后,流水线会注入运行时必需的默认插件。无论智能体如何配置,这些插件始终存在:

  • LoopActionHandlersPlugin — 注册运行时循环用于处理工具调用、发射事件和管理步骤转换的核心动作处理器。没有此插件,循环无法运行。

  • MaxRoundsPlugin — 强制执行智能体规格上配置的 max_rounds 停止条件。使用规格的 max_rounds 值注入。防止失控循环。

仅当设置了 AgentSpec.context_policy 时才添加以下插件:

  • CompactionPlugin — 管理上下文窗口压缩(当上下文增长过大时对旧消息进行摘要)。使用规格中的 CompactionConfigKey 配置段创建,缺失时回退到默认值。

  • ContextTransformPlugin — 在每次推理请求前应用上下文窗口策略变换(token 计数、截断、提示缓存)。使用 context_policy 值创建。

插件列表确定后,ExecutionEnv::from_plugins() 对每个插件调用其 register() 方法并传入 PluginRegistrar。插件通过注册器声明:

  • 阶段钩子(按阶段回调)
  • 调度动作处理器
  • 效果处理器
  • 请求变换
  • 状态键注册
  • 工具

结果是一个 ExecutionEnv — 见下文 ExecutionEnv

插件可以声明 config_schemas(),返回一组 ConfigSchema 条目。每个条目将一个配置段键与一个 JSON Schema 关联。解析期间,每个声明的 schema 都会针对 AgentSpec.sections 中的对应条目进行验证:

  • 配置段存在 — 针对 JSON Schema 进行验证。失败会产生 ResolveError::InvalidPluginConfig
  • 配置段缺失 — 允许。插件应使用合理的默认值。
  • 配置段存在但无插件认领 — 没有插件为其声明 schema。流水线记录一条警告(可能是配置中的拼写错误)。

第三阶段从所有来源收集工具并生成最终工具集。

工具按以下顺序合并:

  1. 全局工具 — 所有通过构建器在 ToolRegistry 中注册的工具(例如 builder.with_tool("search", search_tool))。

  2. 委托智能体工具 — 对于 AgentSpec.delegates 中的每个智能体 ID,流水线创建一个 AgentTool。如果委托有 endpoint(远程),流水线会选择配置的 ExecutionBackend。当前内置远程 backend 是 A2A;本地委托仍然使用 resolver-backed 本地执行。委托工具需要 a2a feature flag;没有该 flag 时,委托会被静默忽略并记录 warning。

  3. 插件注册的工具 — 插件在 register() 期间声明的工具,存储在 ExecutionEnv.tools 中。

如果插件注册的工具与全局工具具有相同的 ID,解析会以 ResolveError::ToolIdConflict 失败。这是有意为之的 — 静默覆盖会成为难以调试的问题来源。

解析器在工具收集之后、插件流水线之前应用目录过滤。匹配规则:

  1. 计算 allow_set = allowed_tools 中的字面量 ∪ 匹配 allowed_tool_patterns 中任一模式的工具。
  2. 计算 exclude_set = excluded_tools 中的字面量 ∪ 匹配 excluded_tool_patterns 中任一模式的工具。
  3. 保留下来的工具 = allow_setexclude_set

“拒绝优先”的优先级让 excluded_tool_patterns 成为一张开放式的安全网 —— excluded_tool_patterns: ["dangerous-*"] 会排除将来出现的同形工具,而不仅 是当前已注册的。字面量排除则严格基于当下的快照:只会移除被点名的 tool id。

运行时在解析阶段会发出三类 warning:

  • 未命中模式*_tool_patterns 中的某条模式没有匹配任何已注册工具, 通常是拼写错误或来自另一个 runtime 的过时条目。
  • 形如 permission 的目录条目 — 目录中出现含 (...) 的条目,很可能本意 是写到 sections["permission"] 里。
  • 孤立的 permission 规则 — permission 规则引用了目录已经过滤掉的工具。

这些 warning 不会阻塞解析,仅作为诊断信号。

ExecutionEnv 是插件流水线的每次解析产物。它不是全局或共享的 — 每次 resolve() 调用都会构建一个全新的实例。其内容:

字段类型用途
phase_hooksHashMap<Phase, Vec<TaggedPhaseHook>>在每个阶段边界调用的钩子
scheduled_action_handlersHashMap<String, ScheduledActionHandlerArc>用于调度/延迟动作的命名处理器
effect_handlersHashMap<String, EffectHandlerArc>用于副作用的命名处理器
request_transformsVec<TaggedRequestTransform>LLM 调用前应用于推理请求的变换
key_registrationsVec<KeyRegistration>运行开始时安装到状态存储的状态键
toolsHashMap<String, Arc<dyn Tool>>插件提供的工具(在阶段 3 合并到主工具集)
pluginsVec<Arc<dyn Plugin>>用于生命周期钩子的插件引用(on_activate/on_deactivate

每个 TaggedPhaseHookTaggedRequestTransform 都携带其所属插件的 ID,用于诊断和过滤。

构建器(AgentRuntimeBuilder)是构造 AgentRuntime 的标准方式。它累积五个注册表:

注册表构建器方法用途
MapAgentSpecRegistrywith_agent_spec() / with_agent_specs()智能体定义
MapToolRegistrywith_tool()全局工具
MapModelRegistrywith_model()模型 ID 到 ModelSpec(provider + 上游模型 + capabilities + 定价)
MapProviderRegistrywith_provider()LLM 执行器实例
MapPluginSourcewith_plugin()插件实例

构建器使用延迟错误收集。每个检测到冲突(重复 ID)的 with_* 调用会将 BuildError 推入内部错误列表,而不是返回 Result。第一个收集到的错误在调用 build()build_unchecked() 时浮现。

build() 在构造运行时后对每个已注册的智能体规格执行一次试运行解析。如果任何智能体解析失败(缺失模型、缺失提供者、缺失插件),错误会被收集并作为 BuildError::ValidationFailed 返回。这在启动时而非首次请求时捕获配置错误。

build_unchecked() 跳过此验证。仅在需要延迟解析或智能体将在构造后动态添加时使用。

当启用 a2a 功能标志时,构建器支持 with_remote_agents() 来注册远程 A2A 端点。这些端点被包装在 CompositeAgentSpecRegistry 中,该注册表组合了本地和远程智能体来源。远程智能体通过 build_and_discover() 异步发现。