Files
LangBot/docs/agent-runner-pluginization/README.md
2026-05-19 10:15:00 +08:00

22 KiB
Raw Blame History

Agent Runner 插件化设计

1. 背景

当前 feat/agent-runner-plugin 分支已经验证了一个最小路径SDK 增加 AgentRunner 组件LangBot 在 Pipeline 的 runner 配置项中动态列出插件提供的 runner并在 ChatMessageHandler 中通过 plugin_connector.run_agent() 调用插件实现。

这个方向能把内置 RequestRunner 之外的 Agent 实现放到插件中,但它仍然沿用“私聊/群聊消息进入 Pipeline再由 runner 产出回复”的旧模型。它没有解决后续 Agent 需要面对复杂上下文的问题,也没有为 EBA 计划里的事件驱动能力留下足够清晰的扩展面。

本设计只聚焦 Agent Runner 插件化。EBA 文档中的事件体系、平台 API、事件路由只作为接口预留和未来兼容参考不纳入本阶段实现范围。

1.1 当前实现状态

当前实现已经不是早期 PoC

  • LangBot 已有 AgentRunnerRegistryAgentRunOrchestratorAgentRunContextBuilderAgentResourceBuilderAgentResultNormalizer
  • ChatMessageHandler 主路径已经委托给 orchestrator不再直接解析插件 runner 或实例化 wrapper。
  • Pipeline metadata 已经从 registry 动态生成插件 runner 选项和配置 stage。
  • SDK 已有 Protocol v1 的 AgentRunContextAgentRunResult、capabilities、permissions、AgentRunAPIProxy
  • RequestRunner 文件仍保留,当前作为迁移基准和回退分析材料;最终 parity 完成后再移除或隔离。

当前仍在收尾的重点不是“能不能调用插件 runner”而是

  • 宿主侧通用能力是否足够,让插件 runner 获得旧内置 runner 隐式拥有的上下文。
  • local-agent 官方插件是否能在对外行为上对齐旧内置 local-agent。
  • 权限裁剪、timeout、错误隔离和端到端 parity 测试是否完整。

2. 目标与非目标

目标:

  • 将 Agent Runner 从 LangBot 内置 runner 列表中解耦,允许插件提供新的 Agent 执行器。
  • 保持当前聊天 Pipeline 可用,并允许现有消息场景选择插件 Agent Runner。
  • 设计新的 Agent 上下文模型,使 runner 不只依赖 query.messagesuser_message,还能承载事件、会话、资源、工具、知识库、平台能力和业务状态。
  • SDK 提供稳定的 AgentRunner 组件接口、上下文实体、返回实体、配置 schema 和运行期 API。
  • LangBot 负责 runner 发现、配置装配、权限校验、运行调度、流式结果转换、错误隔离和兼容层。
  • 为未来 EBA 的非消息事件接入预留 eventactorsubjectplatform_capabilities 等上下文字段。
  • 现有内置 RequestRunner 最终强制迁移为插件形态,由 LangBot 通过同一套插件化 runner 协议调用。

非目标:

  • 不在本阶段实现 EBA EventBus、EventRouter、平台多事件监听或统一平台 API。
  • 不改变现有 Pipeline 的阶段链和私聊/群聊入口。
  • 不引入插件内自定义长驻调度器Agent Runner 仍由 LangBot 显式调用。

3. 当前实现剩余问题

以下是当前实现仍需要收敛的点:

  • AgentRunContext 需要持续补齐宿主处理后的有效上下文,例如有效 prompt、结构化输入、runtime metadata、params/state。
  • AgentRunAPIProxy 需要通过 run_id/query_id 保留旧 runner 隐式拥有的 Query 语义,例如工具调用上下文、知识库检索 settings、模型 extra args、remove-think。
  • AgentResourceBuilder 应按 manifest + Pipeline 绑定 + runner config schema 通用裁剪资源,不能只为 local-agent 写死。
  • local-agent 插件需要对齐旧内置 runner 的外部行为,包括 prompt preprocessing、多模态、fallback、tool loop、RAG、rerank、流式/非流式选择。
  • timeout/deadline、取消、插件无输出、结果过大等运行保护还需要更完整的端到端验证。

4. 总体架构

建议引入三层结构:

Pipeline / future Event Router
        |
        v
AgentRunnerRegistry
        | discovers built-in runners and plugin runners
        v
AgentRunOrchestrator
        | builds context, validates permissions, invokes runner
        v
Built-in RequestRunner adapter / Plugin AgentRunner component
        |
        v
AgentRunResult stream

4.1 AgentRunnerRegistry

职责:

  • 从内置 runner 和插件运行时收集 runner manifest。
  • 输出统一的 AgentRunnerDescriptor,而不是散落在 UI metadata 中的字符串 option。
  • 对插件 runner manifest 做基础校验:组件类型、配置 schema、权限声明、协议版本。
  • 提供缓存和刷新机制,插件安装、卸载、重启后刷新。

建议结构:

class AgentRunnerDescriptor(BaseModel):
    id: str                     # builtin:local-agent 或 plugin:author/name/runner
    source: Literal["builtin", "plugin"]
    label: I18nObject
    description: I18nObject | None = None
    config_schema: list[DynamicFormItemSchema] = []
    capabilities: AgentRunnerCapabilities
    plugin: PluginRef | None = None
    protocol_version: str = "1"

4.2 AgentRunOrchestrator

职责:

  • 根据 pipeline 配置选择 runner。
  • 将当前 query 或未来事件输入转换为 AgentRunRequest
  • 注入可用工具、模型、知识库、会话、权限、平台能力摘要。
  • 统一处理超时、异常、流式返回、取消、中断和 telemetry。
  • 将插件返回的 AgentRunResult 转换回当前 Pipeline 能消费的 Message / MessageChunk

LangBot 当前 ChatMessageHandler 里的插件 wrapper 应下沉到 orchestrator避免消息处理器知道插件 runner 的细节。

5. SDK 设计

5.1 AgentRunner 组件

SDK 保留当前分支新增的组件方向,但需要补齐能力声明:

class AgentRunner(BaseComponent):
    __kind__ = "AgentRunner"
    __protocol_version__ = "1"

    @classmethod
    def get_capabilities(cls) -> AgentRunnerCapabilities:
        return AgentRunnerCapabilities()

    @classmethod
    def get_config_schema(cls) -> list[dict]:
        return []

    async def run(self, ctx: AgentRunContext) -> AsyncGenerator[AgentRunResult, None]:
        ...

一个插件可以声明多个 AgentRunner 组件。每个 runner 使用独立的 component manifest、配置 schema、能力声明和权限声明LangBot 侧以 plugin:author/name/runner 作为稳定 ID 区分。插件包可以因此同时提供多个执行策略,例如通用聊天 runner、客服 runner、工单 runner而不需要拆成多个插件。

get_capabilities() 用来告诉 LangBot 这个 runner 是否支持:

  • streaming
  • tool_calling
  • knowledge_retrieval
  • multimodal_input
  • event_context
  • platform_api
  • interrupt
  • stateful_session

本阶段可以先实现 streamingtool_callingknowledge_retrieval 三项,其他字段只作为声明和预留。

5.2 上下文模型

当前 AgentRunContext 应升级为更通用的运行上下文:

class AgentRunContext(BaseModel):
    run_id: str
    trigger: AgentTrigger
    conversation: ConversationContext | None = None
    event: AgentEventContext | None = None
    actor: ActorContext | None = None
    subject: SubjectContext | None = None
    prompt: list[Message] = []
    messages: list[Message] = []
    input: AgentInput
    params: dict[str, Any] = {}
    resources: AgentResources
    state: AgentRunState = AgentRunState()
    runtime: AgentRuntimeContext
    config: dict[str, Any] = {}

关键点:

  • trigger 标明触发来源。当前消息 Pipeline 使用 message.received,未来 EBA 可使用 group.member_joinedfriend.request_received 等。
  • conversation 承载会话历史、launcher、sender、bot 等聊天语义。
  • event 是未来 EBA 的预留封装,本阶段可以由 query 生成一个最小 message event。
  • actor 表示触发者,subject 表示事件作用对象,例如被邀请用户、被撤回消息、被操作群组。
  • prompt 是宿主处理后的有效 prompt。它来自 LangBot 当前 conversation prompt并且已经过 PromptPreProcessing 等插件事件处理runner 调模型时应优先使用它,而不是重新读取静态 config["prompt"]
  • messages 是历史消息,也已经过宿主 pipeline preprocessing。
  • input 是 runner 的主输入,不再强制等同于纯文本消息;input.contents 必须保留图片、文件等结构化内容。
  • params 是单次运行的公开业务变量,宿主过滤内部变量和敏感变量后提供。
  • resources 列出 LangBot 已授权给 runner 的工具、知识库、模型、文件等。
  • state 是宿主管理的持久 runner-scoped 状态快照。
  • runtime 提供 host 信息、workspace/bot/pipeline 标识、trace id、deadline 等。
  • config 是当前 Pipeline 或未来事件绑定对该 runner id 的绑定配置,替代当前 extra_config

为了兼容现有实现SDK 可提供:

ctx.input.to_text()
ctx.conversation.to_legacy_session()
ctx.to_legacy_query_context()

5.3 返回协议

当前 AgentRunReturn.type 建议规范化为事件流:

class AgentRunResult(BaseModel):
    type: Literal[
        "message.delta",
        "message.completed",
        "tool.call.started",
        "tool.call.completed",
        "state.updated",
        "run.completed",
        "run.failed",
    ]
    data: dict[str, Any] = {}

本阶段 Pipeline 兼容映射:

  • message.delta -> MessageChunk
  • message.completed -> Message
  • run.completed 且带 message -> Message
  • run.failed -> 记录错误并按当前 runner 错误策略返回

action.requested 不进入本阶段的必选协议。它表示“Agent 希望 LangBot 执行一个非文本平台动作”,例如未来 EBA 里编辑消息、通过好友请求、踢人等。当前 Agent Runner 仍作为 Pipeline 的一个 stage 执行,输出只需要覆盖消息流、工具调用状态和运行完成/失败;如果实验性 runner 返回 action.requestedLangBot 只记录 telemetry 并忽略执行。

5.4 LangBotAPIProxy

Agent Runner 插件需要使用 LangBot 能力,但这些能力必须通过显式授权暴露:

  • 模型:invoke_llminvoke_llm_stream、rerank、后续 embedding。
  • 工具:get_tool_detailcall_tool。runner 通过 ctx.resources.tools 获取已授权工具列表,不暴露 unrestricted list_tools
  • 知识:retrieve_knowledge。runner 通过 ctx.resources.knowledge_bases 获取已授权知识库列表,不暴露 unrestricted list_knowledge_bases
  • 存储plugin storage、workspace storage。
  • 文件:配置文件读取、知识文件读取。

SDK 应把这些能力按 capability 分组。LangBot 在调用 runner 前根据 runner manifest、pipeline 配置、插件绑定范围生成 resources,插件不能绕过资源列表调用未授权对象。

宿主 action handler 不应只是把请求转发给 provider/tool/knowledge manager。对 AgentRunner 调用,它还需要通过 run_id/query_id 找回当前 Pipeline Query并自动补齐旧内置 runner 过去直接拥有的上下文,例如:

  • provider 调用的 query
  • model extra_args
  • 输出设置 remove-think
  • 工具调用需要的 Query 上下文
  • 知识库检索的 bot_uuidsender_idsession_name

6. LangBot 设计

6.1 runner 发现

在 LangBot 增加 AgentRunnerRegistry

  • 内置 runner 由 runner_module.preregistered_runners 注册为 builtin:*
  • 插件 runner 通过 PluginRuntimeConnector.list_agent_runners() 获取。
  • manifest 中必须包含 metadataspec.configspec.capabilities
  • 发现失败只影响对应插件 runner不影响 Pipeline metadata 返回。

当前 PipelineService.get_pipeline_metadata() 可以继续作为 UI 入口,但应改为读取 registry而不是直接拼插件列表。

6.2 配置模型与绑定位置

当前阶段 runner 配置仍跟 Pipeline 绑定,并且仍然作为 Pipeline 的一个 stage 执行。也就是说Bot 收到私聊/群聊消息后仍按现有 Pipeline 流转,只是在 AI runner stage 中选择插件化 Agent Runner。

这里的“绑定配置”不代表插件实例。插件安装后由插件 runtime 维护插件本身的运行实例LangBot 不会因为多个 Pipeline 选择同一个 runner id 而创建多个插件实例或 runner 实例。不同 Pipeline 可以保存不同的 runner_config[id],调用时 LangBot 只把当前绑定配置放进 AgentRunContext.config 转发给同一个插件 runner。

插件 runner 应按无状态执行单元设计。需要跨请求保存的 conversation id、外部平台状态或用户状态应通过明确授权的 plugin storage、workspace storage、外部服务或 context runtime state 管理,不能隐式依赖 per-pipeline 插件对象状态。

后续 EBA EventRouter 落地后,同一套 AgentRunnerDescriptorAgentRunOrchestrator 需要支持直接与 Bot 的事件触发器绑定。届时 Bot event handler 可以绕过完整 Pipeline直接选择某个 Agent Runner 处理 message.receivedgroup.member_joinedfriend.request_received 等事件。

Pipeline AI 配置建议从:

{
  "runner": {
    "runner": "local-agent"
  },
  "local-agent": {}
}

演进为:

{
  "runner": {
    "id": "plugin:author/name/runner"
  },
  "runner_config": {
    "plugin:author/name/runner": {}
  }
}

为了兼容现有配置:

  • 读取时同时支持 runner.runnerrunner.id
  • 写入时可以先继续写 runner.runner,等前端完成迁移后再切到 runner.id
  • 旧的内置 runner config key 保持可用。

6.3 运行调度

ChatMessageHandler 不应直接构造 PluginAgentRunnerWrapper。建议路径:

ChatMessageHandler
  -> AgentRunOrchestrator.run_from_query(query)
      -> resolve runner descriptor
      -> build AgentRunContext
      -> invoke built-in adapter or plugin connector
      -> normalize AgentRunResult stream

内置 RequestRunner 可以由 adapter 包一层,统一成 AgentRunnerDescriptor,但不要求现在改写内置 runner。

6.4 插件调用协议

LangBot 到 SDK runtime 需要以下 action

  • LIST_AGENT_RUNNERS
  • RUN_AGENT

RUN_AGENT 输入:

{
  "plugin_author": "...",
  "plugin_name": "...",
  "runner_name": "...",
  "context": {}
}

RUN_AGENT 输出为流式 AgentRunResult。LangBot 必须校验每个结果:

  • 未知 type 记录 warning 后忽略。
  • 单次 result 大小限制,避免插件输出过大。
  • message.deltamessage.completed 做 provider message schema 校验。
  • run.failed 进入统一错误处理。

6.5 权限与隔离

插件 runner 的权限不能只靠插件安装即全量开放。建议 manifest 增加:

spec:
  capabilities:
    streaming: true
    tool_calling: true
    knowledge_retrieval: true
  permissions:
    models: ["invoke"]
    tools: ["call"]
    knowledge_bases: ["retrieve"]
    platform_api: []

LangBot 执行前做三层裁剪:

  • 插件 manifest 声明的权限。
  • Pipeline 或 Bot 绑定的扩展范围。
  • 用户在 Pipeline runner 绑定配置中选择的资源范围。

最终写入 ctx.resources,并在 proxy action 里再次校验。

7. 与 EBA 的边界

本阶段只使用 EBA 文档中的以下思想:

  • 统一事件命名,例如当前消息 query 可映射为 message.received
  • Agent 不应假设输入一定是用户文本消息。
  • Agent 返回不应只限于文本回复,未来可表达动作请求。
  • 插件 SDK 的事件和 API 应向后兼容。

本阶段不实现:

  • EventBus。
  • EventRouter。
  • 新平台适配器目录结构。
  • 群组、好友、Bot 状态等非消息事件监听。
  • 统一平台 API 的实际执行。

因此文档和代码命名应避免把当前任务称为 EBA 实现。推荐使用 agent-runner-pluginizationAgentRunContextAgentRunResult 等命名。

7.1 现在必须预留的事件适配方式

后续消息撤回、群成员加入、新好友申请等事件不要再走“伪造一条用户文本消息”的方式接入 AgentRunner。正确方向是让未来 EventRouter 构造同一份 AgentRunContext,然后复用当前 AgentRunOrchestrator 的 registry、resource builder、result normalizer 和插件调用协议。

当前先固定这些公共协议约束:

  • 顶层 ctx.event.event_type 使用稳定协议名,不暴露 SDK 类名或平台原始事件名。
  • 平台原始事件名、平台 payload、适配器细节放进 ctx.event.event_data
  • ctx.input.text 可以为空runner 不能假设所有触发都是一段用户文本。
  • ctx.actor 表示触发动作的主体,ctx.subject 表示被操作或被关注的对象。
  • 需要平台动作时runner 只能返回 action.requested;当前阶段只记录,真正执行等统一平台 API 和权限模型落地。

已预留的事件类型:

event_type actor subject input
message.received 发消息的人 当前消息 文本、图片、文件等消息内容
message.recalled 撤回操作者,未知时为系统 被撤回消息 通常为空,原消息摘要放 event_data
group.member_joined 新成员或邀请人,按平台 payload 标明 群/成员关系 通常为空,可把欢迎上下文放 event_data
friend.request_received 申请人 好友申请 验证消息或申请理由

未来 EventRouter 的最小调用链应是:

Platform Adapter
  -> EventRouter normalize platform payload
  -> resolve event binding: event_type + bot/workspace/scope -> runner id + config
  -> AgentRunOrchestrator.run_from_event(event_request)
  -> AgentRunContextBuilder.build_context_from_event(event_request)
  -> PluginRuntimeConnector.run_agent()

run_from_event() 不能重新实现一套 runner 调用逻辑,只能复用当前 run_from_query() 已经使用的 registry、资源裁剪、session registry、状态更新和结果归一化能力。这样 Pipeline 消息入口和 EBA 非消息入口不会分裂成两套协议。

8. 分阶段落地

Phase 1整理当前分支

  • 保留 SDK AgentRunner 组件。
  • 调整 AgentRunContext / AgentRunReturn 为协议 v1 的命名和字段。
  • LangBot 增加 AgentRunnerRegistryAgentRunOrchestrator
  • ChatMessageHandler 改为调用 orchestrator。
  • Pipeline metadata 从 registry 读取 runner 列表。

Phase 2能力和权限

  • runner manifest 增加 capabilitiespermissionsconfig_schema
  • LangBot 对工具、知识库、模型资源做注入和裁剪。
  • proxy action 做二次校验。
  • 增加超时、取消、错误隔离和 telemetry。

Phase 3内置 runner 插件化迁移

  • 兼容当前 plugin:author/name/runner 字符串 ID。
  • 兼容 runner.runner 配置键。
  • 提供从旧 runner 配置到 runner.id / runner_config 的迁移。
  • 将所有内置 RequestRunner 强制迁移为官方插件包。
  • 迁移期间旧 RequestRunner 文件可以保留作为 parity 基准;主聊天路径不应继续依赖它们。
  • LangBot 最终只保留插件 runtime、registry、orchestrator 和兼容迁移逻辑,不再维护独立的内置 runner 执行分支。

Phase 4为 EBA 接入做预留

  • AgentRunContext.event 支持 EBA 文档定义的事件 envelope 子集。
  • AgentRunResult.action.requested 仍只记录,不执行;真正执行平台动作需要等统一平台 API 和事件触发器权限模型完成。
  • 等 EBA EventRouter 落地后,由 EventRouter 直接调用 orchestrator。

9. 需要修改的代码范围

LangBot

  • src/langbot/pkg/pipeline/process/handlers/chat.py:移除插件 runner 解析细节,改为 orchestrator。
  • src/langbot/pkg/api/http/service/pipeline.py:从 registry 获取 runner metadata。
  • src/langbot/pkg/plugin/connector.py:保留 list_agent_runners() / run_agent(),增加协议校验。
  • src/langbot/pkg/plugin/handler.py:整理 Agent 运行期可调用的 proxy action。
  • 新增 src/langbot/pkg/provider/agent_runner/src/langbot/pkg/agent/runner/registry、orchestrator、context builder、result normalizer。

SDK

  • src/langbot_plugin/api/definition/components/agent_runner/runner.py:补 capabilities、config schema、协议版本。
  • src/langbot_plugin/api/entities/builtin/agent_runner/context.py:升级上下文和返回协议。
  • src/langbot_plugin/runtime/io/handlers/control.py:保留 LIST_AGENT_RUNNERS / RUN_AGENT
  • src/langbot_plugin/runtime/plugin/mgr.pyrunner 发现、调用、异常隔离。
  • src/langbot_plugin/api/proxies/langbot_api.py:补齐 Agent 运行期需要的 host capability proxy。

10. 验收标准

  • Pipeline 可以选择一个插件提供的 Agent Runner。
  • 插件 runner 能收到结构化上下文,并能流式返回消息。
  • 插件 runner 只能看到 LangBot 注入的工具、知识库、模型资源。
  • 插件 runner 异常不会中断插件 runtime 或 Pipeline 主流程。
  • 旧 Pipeline 配置和旧内置 runner 正常工作。
  • 官方 local-agent 插件在外部行为上对齐旧内置 local-agent有效 prompt、历史消息、结构化输入、RAG、rerank、工具循环、模型 fallback、streaming/non-streaming。
  • 文档明确区分“Agent Runner 插件化”和“未来 EBA 架构”。

11. 已确认决策

  • 插件可以声明多个 AgentRunner 组件,每个组件独立暴露 manifest、配置 schema、能力和权限。
  • 本阶段不把 action.requested 作为必须实现的运行结果。它只是为未来 EBA 平台动作预留的返回类型;当前 Pipeline stage 中如收到该类型,只记录 telemetry不执行动作。
  • 当前 runner 配置先跟 Pipeline 绑定,仍然在 Pipeline 的 AI runner stage 中执行;后续需要支持直接与 Bot 的事件触发器绑定。
  • Pipeline/Event 绑定只保存 runner id 和绑定配置,不创建插件实例或 runner 实例;插件 runner 按无状态转发调用处理,跨请求状态必须显式存储。
  • 内置 RequestRunner 最终强制迁移为插件形态,避免长期保留“内置 runner 分支”和“插件 runner 分支”两套执行体系。

12. QA 验收

Phase 1 收尾进入 agent QA 时,使用 PHASE1_QA_ACCEPTANCE_MATRIX.md 作为验收标准。该矩阵只验收 Agent Runner 插件化 parity不验收 EBA EventBus、EventRouter 或平台动作执行。