Files
LangBot/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md
huanghuoguoguo 5aaa422250 feat(agent-runner): integrate AgentRunner Protocol v1 with plugin system
Phase 0 integration complete - verified minimal loop with local-agent stub runner.

Changes:
- Add AgentRunOrchestrator for plugin-based agent execution
- Add AgentResultNormalizer for Protocol v1 result conversion
- Add AgentRunnerDescriptor for runner ID parsing (plugin:author/name/runner)
- Update chat handler to use new orchestrator instead of direct runner lookup
- Add plugin handler methods for list_agent_runners and run_agent
- Add connector methods for AgentRunner protocol forwarding
- Update pipeline API to include runner options in metadata
- Add integration docs and implementation plan

Integration verified:
- Runner: plugin:langbot/local-agent/default
- Input: "你好"
- Output: [stub] Echo: 你好
- Date: 2026-05-10 10:09

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 11:05:27 +08:00

12 KiB
Raw Blame History

Agent Runner 插件化最终实现计划

本文档面向实现 agent用来把当前 PoC 分支直接推进到最终架构。这个分支不按线上渐进发布节奏处理,因此可以接受一次性破坏内部 runner 实现和 Pipeline AI 配置结构;但最终必须提供历史配置迁移。

1. 最终状态

LangBot 最终只保留 Agent Runner 的宿主能力:

  • 发现 runnerAgentRunnerRegistry
  • 选择 runnerPipeline 配置和未来事件绑定配置
  • 构造上下文:AgentRunContext
  • 裁剪资源:模型、工具、知识库、文件、存储、平台能力
  • 调度执行:AgentRunOrchestrator
  • 归一结果:AgentRunResult -> 当前 Pipeline 的 Message / MessageChunk
  • 隔离错误:插件异常、协议错误、超时、结果过大不能破坏主流程
  • 迁移旧配置:把旧内置 runner 配置迁到官方 AgentRunner 插件配置

LangBot 不再长期维护内置业务 runner 分支。local-agent、Dify、n8n、Coze、DashScope、Langflow、Tbox 等都迁到官方 AgentRunner 插件。

2. 高层架构

Pipeline MessageProcessor / future EventRouter
        |
        v
AgentRunOrchestrator
        |
        +--> AgentRunnerRegistry
        |       +--> plugin runtime LIST_AGENT_RUNNERS
        |       +--> descriptor cache / validation
        |
        +--> AgentRunContextBuilder
        +--> AgentResourceBuilder
        +--> AgentResultNormalizer
        |
        v
PluginRuntimeConnector.run_agent()
        |
        v
SDK Runtime RUN_AGENT -> plugin AgentRunner.run()

关键约束:

  • ChatMessageHandler 不解析 plugin:*,不实例化 wrapper不知道 runner 组件细节。
  • PipelineService.get_pipeline_metadata() 不直接访问插件 runtime而是读取 registry。
  • RequestRunner 只作为迁移参考,不作为最终运行路径。
  • EBA 只做字段预留,不在本轮实现 EventBus、EventRouter、平台动作执行。

3. 新增 LangBot 模块

建议新增:

src/langbot/pkg/agent/
  __init__.py
  runner/
    __init__.py
    descriptor.py
    errors.py
    id.py
    registry.py
    context_builder.py
    resource_builder.py
    orchestrator.py
    result_normalizer.py
    config_migration.py

3.1 descriptor.py

定义 LangBot 内部使用的 descriptor

class AgentRunnerDescriptor(BaseModel):
    id: str
    source: Literal["plugin"]
    label: dict[str, str]
    description: dict[str, str] | None = None
    plugin_author: str
    plugin_name: str
    runner_name: str
    plugin_version: str | None = None
    protocol_version: str = "1"
    config_schema: list[dict[str, Any]] = []
    capabilities: dict[str, bool] = {}
    permissions: dict[str, list[str]] = {}
    raw_manifest: dict[str, Any] = {}

source == "builtin" 不作为最终目标。如果实现阶段需要临时 adapter必须标记为测试过渡代码并在官方插件跑通后删除。

3.2 id.py

统一 runner id 解析和生成:

  • 插件 runner idplugin:{author}/{plugin_name}/{runner_name}
  • parse_runner_id(id) 返回结构化对象
  • 禁止业务代码手写字符串 split
  • PoC 已存在的 plugin:author/name/runner 继续作为合法 id

3.3 registry.py

职责:

  • 调用 ap.plugin_connector.list_agent_runners(bound_plugins=None) 拉取插件 runner
  • 校验 manifest
    • kind == AgentRunner
    • metadata.name 存在
    • metadata.label 存在
    • spec.protocol_version 兼容,默认 1
    • spec.config 是 list默认空
    • spec.capabilities 是 dict默认空
    • spec.permissions 是 dict默认空
  • 输出 AgentRunnerDescriptor
  • 缓存 discovery 结果,提供 refresh()
  • 单个插件 manifest 失败只记录 warning不影响其它 runner

刷新触发点:

  • 插件安装、卸载、升级、重启后
  • Pipeline metadata 请求时发现缓存为空
  • 可选 TTL优先保证正确性

3.4 context_builder.py

把当前 Pipeline query 直接转换成 SDK v1 AgentRunContext

当前消息 Pipeline 的最小字段:

  • run_id: 新 UUID不使用 query id 作为全局 run id
  • trigger.type: message.received
  • conversation: launcher、sender、bot、pipeline、历史消息
  • event: message event envelope 子集
  • actor: sender
  • subject: 当前消息或 launcher
  • messages: query.messages
  • input: 从 query.user_messagequery.message_chain 构造
  • resources: 由 resource_builder 注入
  • runtime: host/version/workspace/bot/pipeline/query/trace/deadline
  • config: 当前 runner id 对应的实例配置

保留 SDK legacy helper 是 SDK 的责任LangBot 不再构造 PoC 的 query_id/session/messages/user_message/extra_config 上下文。

3.5 resource_builder.py

执行前做三层裁剪:

  1. runner manifest 声明的 spec.permissions
  2. Pipeline 的 extensions_preferences
  3. runner 实例配置中选择的资源范围

输出写入 ctx.resources,至少覆盖:

  • models可调用模型 UUID、类型、能力摘要
  • tools可见工具 manifest使用当前 bound plugins / MCP server 范围
  • knowledge_bases可检索知识库列表
  • storageplugin storage / workspace storage 权限摘要
  • files允许读取的配置文件、知识文件摘要
  • platform_capabilities本阶段只声明不执行平台动作

注意:旧的 unrestricted proxy action 必须在 Phase 2 被二次校验,不能只靠 context 声明。

3.6 result_normalizer.py

只接受 SDK v1 result

  • message.delta
  • message.completed
  • tool.call.started
  • tool.call.completed
  • state.updated
  • run.completed
  • run.failed
  • action.requested 允许实验性返回,但本阶段只记录 telemetry不执行

映射:

  • message.delta.data.chunk -> provider_message.MessageChunk
  • message.completed.data.message -> provider_message.Message
  • run.completed.data.message -> provider_message.Message
  • run.failed -> 抛出受控异常,让 ChatMessageHandler 使用现有错误策略
  • 工具和状态事件默认不 yield 到 Pipeline只记录 debug/telemetry

防护:

  • 未知 type warning 后忽略
  • 单 result 序列化大小限制
  • provider message schema 校验失败转 run.failed
  • 插件没有输出任何消息时,按 runner failed 处理

3.7 orchestrator.py

核心入口:

async def run_from_query(query: pipeline_query.Query) -> AsyncGenerator[Message | MessageChunk, None]:
    runner_id = resolve_runner_id(query.pipeline_config)
    descriptor = await registry.get(runner_id, bound_plugins=query.variables.get("_pipeline_bound_plugins"))
    ctx = await context_builder.from_query(query, descriptor)
    async for raw in plugin_connector.run_agent(...):
        async for message in result_normalizer.normalize(raw):
            yield message

必须覆盖:

  • runner id 不存在
  • 插件系统关闭
  • runner 不在 bound plugins 范围内
  • 插件 runtime 断连
  • runner 协议版本不兼容
  • run 超时
  • task cancellation

4. 配置模型直接切换

目标格式:

{
  "ai": {
    "runner": {
      "id": "plugin:langbot/local-agent/default",
      "expire-time": 0
    },
    "runner_config": {
      "plugin:langbot/local-agent/default": {}
    }
  }
}

兼容读取:

  • 优先读 ai.runner.id
  • 没有 id 时读旧 ai.runner.runner
  • 旧内置 runner 名通过迁移表映射:
    • local-agent -> plugin:langbot/local-agent/default
    • dify-service-api -> plugin:langbot/dify-agent/default
    • n8n-service-api -> plugin:langbot/n8n-agent/default
    • coze-api -> plugin:langbot/coze-agent/default
    • dashscope-app-api -> plugin:langbot/dashscope-agent/default
    • langflow-api -> plugin:langbot/langflow-agent/default
    • tbox-app-api -> plugin:langbot/tbox-agent/default

写入策略:

  • 新 UI 只写 ai.runner.idai.runner_config
  • 后端 update 接口接受旧字段,但保存时归一成新格式
  • migration 最后统一落库

5. 需要修改的 LangBot 范围

必须修改:

  • src/langbot/pkg/core/app.py
    • 增加 agent_runner_registry / agent_run_orchestrator 属性
  • src/langbot/pkg/core/stages/build_app.py
    • 初始化 Agent 子系统
  • src/langbot/pkg/pipeline/process/handlers/chat.py
    • 删除 PluginAgentRunnerWrapper
    • 删除内置 runner 查找逻辑
    • 调用 orchestrator
  • src/langbot/pkg/api/http/service/pipeline.py
    • metadata 从 registry 生成
  • src/langbot/pkg/plugin/connector.py
    • list_agent_runners() / run_agent() 增加协议校验和 bound plugin 参数
  • src/langbot/pkg/plugin/handler.py
    • proxy action 二次权限校验
  • src/langbot/pkg/pipeline/preproc/preproc.py
    • 不再只为 local-agent 构造工具、知识库、模型
    • 对所有 agent runner 保留 multimodal input
  • src/langbot/pkg/pipeline/pipelinemgr.py
    • runner name 监控改读 runner.id
  • src/langbot/templates/metadata/pipeline/ai.yaml
    • runner 字段从 runner 迁到 id
  • src/langbot/templates/default-pipeline-config.json
    • 默认 runner 改为官方 local-agent 插件 id
  • web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx
    • 当前 runner 改读 ai.runner.id
    • runner 配置区改写入 ai.runner_config[id]

最终删除或停用:

  • src/langbot/pkg/provider/runner.py 的业务注册路径
  • src/langbot/pkg/provider/runners/* 的运行入口

可以暂时保留文件作为官方插件迁移参考,但不应被运行时引用。

6. 实现顺序

Step 1接入新版 SDK

  • 更新 LangBot 依赖到包含 SDK v1 AgentRunner 协议的版本
  • 删除 LangBot 中对旧 AgentRunReturn 类型名的依赖
  • 确认 langbot_plugin 的本地 editable / lockfile 指向正确 SDK

Step 2Agent 子系统骨架

  • 新增 descriptor/id/errors
  • 新增 registry先只 list plugin runner
  • 为 registry 加单测,使用 fake connector

Step 3Pipeline metadata 切 registry

  • get_pipeline_metadata() 只通过 registry 输出 runner option
  • 插件 runner config stage 从 descriptor.config_schema 生成
  • schema 错误不影响 metadata 返回

Step 4Orchestrator 替换 ChatMessageHandler

  • 新增 context builder / result normalizer / orchestrator
  • chat.py 删除 wrapper 和 runner 查找
  • 维持现有流式卡片和 resp_messages 行为

Step 5新配置读写

  • 后端 resolve runner id 支持新旧配置
  • 前端表单改 runner.id + runner_config
  • 默认配置改官方 local-agent 插件 id

Step 6权限和资源裁剪

  • resource builder 根据 manifest / pipeline / instance config 裁剪
  • proxy action 校验 resource scope
  • 禁止插件用 unrestricted API 访问未授权知识库、工具、模型

Step 7删除内置 runner 运行分支

  • 官方插件 ready 后移除内置 runner registry
  • 删除或隔离 provider runners 的运行引用
  • 测试旧 runner 名只能通过 migration 映射到插件 id

Step 8历史配置迁移

  • 写 persistence migration
  • 更新 default pipeline config
  • 对已存在 Pipeline 执行旧字段到新字段迁移
  • 对监控/日志里的 runner 字段改用新 id

7. 测试要求

单测:

  • runner id parse / format
  • registry manifest 校验、失败隔离、bound plugins 过滤
  • context builder 从 query 生成完整 v1 context
  • resource builder 三层裁剪
  • result normalizer 对每种 result type 的映射
  • 旧配置 resolve 和 migration

集成测试:

  • fake AgentRunner 插件可被 Pipeline 选择
  • streaming 输出仍能更新 message card
  • 插件异常返回用户可理解错误,不中断 runtime
  • runner 不在 bound plugins 时不可执行
  • 未授权工具 / 知识库 / 模型 proxy 调用被拒绝
  • local-agent Pipeline 配置迁到官方插件 id

8. 验收标准

  • LangBot Pipeline 可以选择插件 AgentRunner 并完成非流式和流式回复。
  • ChatMessageHandler 不包含插件 runner 解析和 wrapper。
  • PipelineService 不直接拼插件 runner metadata。
  • 所有 runner 配置使用 ai.runner.id + ai.runner_config
  • 旧内置 runner 不再作为 LangBot 内部运行分支执行。
  • 插件只能访问 ctx.resources 授权的模型、工具、知识库和文件。
  • EBA 相关字段只作为 context/result 预留,不执行平台动作。