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、事件路由只作为接口预留和未来兼容参考,不纳入本阶段实现范围。
2. 目标与非目标
目标:
- 将 Agent Runner 从 LangBot 内置 runner 列表中解耦,允许插件提供新的 Agent 执行器。
- 保持当前聊天 Pipeline 可用,并允许现有消息场景选择插件 Agent Runner。
- 设计新的 Agent 上下文模型,使 runner 不只依赖
query.messages和user_message,还能承载事件、会话、资源、工具、知识库、平台能力和业务状态。 - SDK 提供稳定的
AgentRunner组件接口、上下文实体、返回实体、配置 schema 和运行期 API。 - LangBot 负责 runner 发现、配置装配、权限校验、运行调度、流式结果转换、错误隔离和兼容层。
- 为未来 EBA 的非消息事件接入预留
event、actor、subject、platform_capabilities等上下文字段。 - 现有内置
RequestRunner最终强制迁移为插件形态,由 LangBot 通过同一套插件化 runner 协议调用。
非目标:
- 不在本阶段实现 EBA EventBus、EventRouter、平台多事件监听或统一平台 API。
- 不改变现有 Pipeline 的阶段链和私聊/群聊入口。
- 不引入插件内自定义长驻调度器;Agent Runner 仍由 LangBot 显式调用。
3. 当前分支问题
当前分支的实现可以作为 PoC,但需要调整:
AgentRunContext仍是 query 视角,字段包括query_id、session、messages、user_message、use_funcs、extra_config,对非消息事件和复杂任务上下文表达不足。- runner 标识使用
plugin:author/plugin_name/runner_name字符串拼接,缺少结构化 ID、版本、能力和权限信息。 - LangBot 在
PipelineService.get_pipeline_metadata()中直接把插件配置 schema 拼进 AI metadata,缺少缓存、失败隔离和 schema 兼容验证。 ChatMessageHandler内部直接解析插件 runner 名称并调用 wrapper,调度逻辑和消息处理逻辑耦合。- SDK 的
AgentRunner.run()只接受单一上下文,没有生命周期 hooks、能力声明、配置 schema 分层和运行结果协议版本。 - 工具调用、知识检索、LLM 调用目前依赖零散 proxy action,缺少 Agent 运行期明确的 capability set。
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 是否支持:
streamingtool_callingknowledge_retrievalmultimodal_inputevent_contextplatform_apiinterruptstateful_session
本阶段可以先实现 streaming、tool_calling、knowledge_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
messages: list[Message] = []
input: AgentInput
resources: AgentResources
runtime: AgentRuntimeContext
config: dict[str, Any] = {}
关键点:
trigger标明触发来源。当前消息 Pipeline 使用message.received,未来 EBA 可使用group.member_joined、friend.request_received等。conversation承载会话历史、launcher、sender、bot 等聊天语义。event是未来 EBA 的预留封装,本阶段可以由 query 生成一个最小 message event。actor表示触发者,subject表示事件作用对象,例如被邀请用户、被撤回消息、被操作群组。input是 runner 的主输入,不再强制等同于纯文本消息。resources列出 LangBot 已授权给 runner 的工具、知识库、模型、文件等。runtime提供 host 信息、workspace/bot/pipeline 标识、trace id、deadline 等。config是当前 runner 的实例配置,替代当前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->MessageChunkmessage.completed->Messagerun.completed且带message->Messagerun.failed-> 记录错误并按当前 runner 错误策略返回
action.requested 不进入本阶段的必选协议。它表示“Agent 希望 LangBot 执行一个非文本平台动作”,例如未来 EBA 里编辑消息、通过好友请求、踢人等。当前 Agent Runner 仍作为 Pipeline 的一个 stage 执行,输出只需要覆盖消息流、工具调用状态和运行完成/失败;如果实验性 runner 返回 action.requested,LangBot 只记录 telemetry 并忽略执行。
5.4 LangBotAPIProxy
Agent Runner 插件需要使用 LangBot 能力,但这些能力必须通过显式授权暴露:
- 模型:
invoke_llm、invoke_llm_stream、embedding。 - 工具:
list_tools、get_tool_detail、call_tool。 - 知识:
list_knowledge_bases、retrieve_knowledge。 - 存储:plugin storage、workspace storage。
- 文件:配置文件读取、知识文件读取。
SDK 应把这些能力按 capability 分组。LangBot 在调用 runner 前根据 runner manifest、pipeline 配置、插件绑定范围生成 resources,插件不能绕过资源列表调用未授权对象。
6. LangBot 设计
6.1 runner 发现
在 LangBot 增加 AgentRunnerRegistry:
- 内置 runner 由
runner_module.preregistered_runners注册为builtin:*。 - 插件 runner 通过
PluginRuntimeConnector.list_agent_runners()获取。 - manifest 中必须包含
metadata、spec.config、spec.capabilities。 - 发现失败只影响对应插件 runner,不影响 Pipeline metadata 返回。
当前 PipelineService.get_pipeline_metadata() 可以继续作为 UI 入口,但应改为读取 registry,而不是直接拼插件列表。
6.2 配置模型与绑定位置
当前阶段 runner 配置仍跟 Pipeline 绑定,并且仍然作为 Pipeline 的一个 stage 执行。也就是说,Bot 收到私聊/群聊消息后仍按现有 Pipeline 流转,只是在 AI runner stage 中选择插件化 Agent Runner。
后续 EBA EventRouter 落地后,同一套 AgentRunnerDescriptor 和 AgentRunOrchestrator 需要支持直接与 Bot 的事件触发器绑定。届时 Bot event handler 可以绕过完整 Pipeline,直接选择某个 Agent Runner 处理 message.received、group.member_joined、friend.request_received 等事件。
Pipeline AI 配置建议从:
{
"runner": {
"runner": "local-agent"
},
"local-agent": {}
}
演进为:
{
"runner": {
"id": "plugin:author/name/runner"
},
"runner_config": {
"plugin:author/name/runner": {}
}
}
为了兼容现有配置:
- 读取时同时支持
runner.runner和runner.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_RUNNERSRUN_AGENT
RUN_AGENT 输入:
{
"plugin_author": "...",
"plugin_name": "...",
"runner_name": "...",
"context": {}
}
RUN_AGENT 输出为流式 AgentRunResult。LangBot 必须校验每个结果:
- 未知
type记录 warning 后忽略。 - 单次 result 大小限制,避免插件输出过大。
message.delta和message.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 绑定的扩展范围。
- 用户在 runner 配置中选择的资源范围。
最终写入 ctx.resources,并在 proxy action 里再次校验。
7. 与 EBA 的边界
本阶段只使用 EBA 文档中的以下思想:
- 统一事件命名,例如当前消息 query 可映射为
message.received。 - Agent 不应假设输入一定是用户文本消息。
- Agent 返回不应只限于文本回复,未来可表达动作请求。
- 插件 SDK 的事件和 API 应向后兼容。
本阶段不实现:
- EventBus。
- EventRouter。
- 新平台适配器目录结构。
- 群组、好友、Bot 状态等非消息事件监听。
- 统一平台 API 的实际执行。
因此文档和代码命名应避免把当前任务称为 EBA 实现。推荐使用 agent-runner-pluginization、AgentRunContext、AgentRunResult 等命名。
8. 分阶段落地
Phase 1:整理当前分支
- 保留 SDK
AgentRunner组件。 - 调整
AgentRunContext/AgentRunReturn为协议 v1 的命名和字段。 - LangBot 增加
AgentRunnerRegistry和AgentRunOrchestrator。 ChatMessageHandler改为调用 orchestrator。- Pipeline metadata 从 registry 读取 runner 列表。
Phase 2:能力和权限
- runner manifest 增加
capabilities、permissions、config_schema。 - LangBot 对工具、知识库、模型资源做注入和裁剪。
- proxy action 做二次校验。
- 增加超时、取消、错误隔离和 telemetry。
Phase 3:内置 runner 插件化迁移
- 兼容当前
plugin:author/name/runner字符串 ID。 - 兼容
runner.runner配置键。 - 提供从旧 runner 配置到
runner.id/runner_config的迁移。 - 将所有内置
RequestRunner强制迁移为内置插件或官方插件包。 - 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.py:runner 发现、调用、异常隔离。src/langbot_plugin/api/proxies/langbot_api.py:补齐 Agent 运行期需要的 host capability proxy。
10. 验收标准
- Pipeline 可以选择一个插件提供的 Agent Runner。
- 插件 runner 能收到结构化上下文,并能流式返回消息。
- 插件 runner 只能看到 LangBot 注入的工具、知识库、模型资源。
- 插件 runner 异常不会中断插件 runtime 或 Pipeline 主流程。
- 旧 Pipeline 配置和旧内置 runner 正常工作。
- 文档明确区分“Agent Runner 插件化”和“未来 EBA 架构”。
11. 已确认决策
- 插件可以声明多个
AgentRunner组件,每个组件独立暴露 manifest、配置 schema、能力和权限。 - 本阶段不把
action.requested作为必须实现的运行结果。它只是为未来 EBA 平台动作预留的返回类型;当前 Pipeline stage 中如收到该类型,只记录 telemetry,不执行动作。 - 当前 runner 配置先跟 Pipeline 绑定,仍然在 Pipeline 的 AI runner stage 中执行;后续需要支持直接与 Bot 的事件触发器绑定。
- 内置
RequestRunner最终强制迁移为插件形态,避免长期保留“内置 runner 分支”和“插件 runner 分支”两套执行体系。