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

371 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent Runner 插件化最终实现计划
本文档面向实现 agent用来把当前 PoC 分支直接推进到最终架构。这个分支不按线上渐进发布节奏处理,因此可以接受一次性破坏内部 runner 实现和 Pipeline AI 配置结构;但最终必须提供历史配置迁移。
## 1. 最终状态
LangBot 最终只保留 Agent Runner 的宿主能力:
- 发现 runner`AgentRunnerRegistry`
- 选择 runnerPipeline 配置和未来事件绑定配置
- 构造上下文:`AgentRunContext`
- 裁剪资源:模型、工具、知识库、文件、存储、平台能力
- 调度执行:`AgentRunOrchestrator`
- 归一结果:`AgentRunResult` -> 当前 Pipeline 的 `Message` / `MessageChunk`
- 隔离错误:插件异常、协议错误、超时、结果过大不能破坏主流程
- 迁移旧配置:把旧内置 runner 配置迁到官方 AgentRunner 插件配置
LangBot 不再长期维护内置业务 runner 分支。`local-agent`、Dify、n8n、Coze、DashScope、Langflow、Tbox 等都迁到官方 AgentRunner 插件。
## 2. 高层架构
```text
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 模块
建议新增:
```text
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
```python
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 id`plugin:{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_message``query.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
核心入口:
```python
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. 配置模型直接切换
目标格式:
```json
{
"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.id``ai.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 预留,不执行平台动作。