mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 14:57:15 +00:00
feat(runner): unify plugin execution across agents and event processors
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ LangBot 主进程 │
|
||||
│ │
|
||||
│ AgentRunner ──> SDK call_tool / scoped MCP bridge │
|
||||
│ Runner ──> SDK call_tool / scoped MCP bridge │
|
||||
│ │ │ │
|
||||
│ └────────────────> ToolManager ──> NativeToolLoader │
|
||||
│ │ │ │ │
|
||||
@@ -87,8 +87,8 @@
|
||||
**核心设计原则**:
|
||||
- Box Runtime 作为独立进程运行,通过 Action RPC 与 LangBot 主进程通信,两者复用 SDK 的 IO 层(Handler → Connection → Controller)
|
||||
- 一个 session_id 对应一个容器/沙箱实例。同一 session 内可并存多条 mount 与多个 managed process
|
||||
- AgentRunner 无权指定 session scope。SDK/Python `call_tool` 与 scoped MCP bridge 都发出同一个 `PluginToRuntimeAction.CALL_TOOL`,最终由 Host 的 ToolManager 执行,并使用当前 run 保存的同一个 execution Query
|
||||
- Box 内托管的 stdio MCP server 使用独立的长期 `mcp-shared` session;它不是 AgentRunner 本次事件的 sandbox session(详见 [box-session-scope.md](./box-session-scope.md))
|
||||
- Runner 无权指定 session scope。SDK/Python `call_tool` 与 scoped MCP bridge 都发出同一个 `PluginToRuntimeAction.CALL_TOOL`,最终由 Host 的 ToolManager 执行,并使用当前 run 保存的同一个 execution Query
|
||||
- Box 内托管的 stdio MCP server 使用独立的长期 `mcp-shared` session;它不是 Runner 本次事件的 sandbox session(详见 [box-session-scope.md](./box-session-scope.md))
|
||||
|
||||
---
|
||||
|
||||
@@ -140,7 +140,7 @@ BoxService
|
||||
|
||||
**输出截断**: 默认 4000 字符上限,保留前 60% + 后 40%,中间插入 `[...truncated...]`。
|
||||
|
||||
**Session 所有权**: `resolve_box_session_id(query)` 只接受 Host 已确定的私有 scope 或 Query launcher/session identity,并输出 `lb-box-` + 64 位小写 SHA-256 十六进制摘要(固定 71 个 ASCII 字符)。哈希输入是 canonical JSON,包含 instance、workspace、bot、platform adapter、target type/id 与 thread;原始用户、群组、conversation 或 event id 不会出现在 Box session id 中。相同 Host scope 稳定复用,不同 target/thread/workspace/bot/adapter/instance 相互隔离;缺少可用 identity 时 fail closed。Pipeline、Agent 或 AgentRunner 配置都不能覆盖该规则。
|
||||
**Session 所有权**: `resolve_box_session_id(query)` 只接受 Host 已确定的私有 scope 或 Query launcher/session identity,并输出 `lb-box-` + 64 位小写 SHA-256 十六进制摘要(固定 71 个 ASCII 字符)。哈希输入是 canonical JSON,包含 instance、workspace、bot、platform adapter、target type/id 与 thread;原始用户、群组、conversation 或 event id 不会出现在 Box session id 中。相同 Host scope 稳定复用,不同 target/thread/workspace/bot/adapter/instance 相互隔离;缺少可用 identity 时 fail closed。Pipeline、Agent 或 Runner 配置都不能覆盖该规则。
|
||||
|
||||
**Skill 挂载合并**: `execute_tool()` 调用时,`build_skill_extra_mounts(query)` 会把当前 pipeline-bound 的所有 skill 的 `package_root` 作为 `extra_mounts` 加入 BoxSpec,挂在 `/workspace/.skills/<name>`。LLM 通过 `activate` 工具显式激活某个 skill 后,工具调用才允许引用这个 skill 的虚拟路径。
|
||||
|
||||
@@ -421,7 +421,7 @@ ToolManager.initialize()
|
||||
3. 若 skill 是 Python 项目(有 `requirements.txt` 或 `pyproject.toml`),命令会被 venv bootstrap 包裹(在 skill 挂载点内创建 `.venv`)
|
||||
4. 调用 `box_service.execute_tool()` → 走 Host 从当前事件生成的 session_id 与已组装好的 `extra_mounts`,**不再为每 skill 起独立 session**
|
||||
|
||||
AgentRunner 可以直接通过 SDK/Python `AgentRunAPIProxy.call_tool` 调用这些工具,也可以让外部 harness 通过 SDK-owned scoped MCP bridge 回调。两条入口都发送 `PluginToRuntimeAction.CALL_TOOL`,共享同一个 run authorization、Host session 中保存的 execution Query、ToolManager 与 `resolve_box_session_id(query)` 规则;Runner 不能提交自定义 Box session id。Pipeline run 保存原 Query;纯 EBA run 由 Host 构造 `pipeline_config=None`、`pipeline_uuid=None` 的最小 Query。
|
||||
Runner 可以直接通过 SDK/Python `RunnerAPIProxy.call_tool` 调用这些工具,也可以让外部 harness 通过 SDK-owned scoped MCP bridge 回调。两条入口都发送 `PluginToRuntimeAction.CALL_TOOL`,共享同一个 run authorization、Host session 中保存的 execution Query、ToolManager 与 `resolve_box_session_id(query)` 规则;Runner 不能提交自定义 Box session id。Pipeline run 保存原 Query;纯 EBA run 由 Host 构造 `pipeline_config=None`、`pipeline_uuid=None` 的最小 Query。
|
||||
|
||||
### 4.3 MCP-in-Box (`mcp_stdio.py`, 354 行)
|
||||
|
||||
@@ -442,7 +442,7 @@ initialize()
|
||||
|
||||
每条 MCP server 是同一 session 中的一个 managed process,独立的 `process_id`、独立 attach URL,互不阻塞。
|
||||
|
||||
这里的 `mcp-shared` 只承载 LangBot 管理的 stdio MCP server 进程。AgentRunner 的 scoped MCP bridge 是回调 Host 工具的协议入口,不会把事件运行的 exec/read/write 改到 `mcp-shared`。
|
||||
这里的 `mcp-shared` 只承载 LangBot 管理的 stdio MCP server 进程。Runner 的 scoped MCP bridge 是回调 Host 工具的协议入口,不会把事件运行的 exec/read/write 改到 `mcp-shared`。
|
||||
|
||||
---
|
||||
|
||||
@@ -582,7 +582,7 @@ volumes:
|
||||
|
||||
### Session scope
|
||||
|
||||
Pipeline 与 AgentRunner 配置不再暴露 sandbox session 模板。Host 将当前平台会话/事件 scope 规范化后哈希成固定长度的 `lb-box-<sha256>`;相同 scope 稳定复用,不同 scope 隔离,缺少 identity 时拒绝执行。SDK/Python 与 scoped MCP bridge 的工具调用遵守同一规则。详见 [box-session-scope.md](./box-session-scope.md)。
|
||||
Pipeline 与 Runner 配置不再暴露 sandbox session 模板。Host 将当前平台会话/事件 scope 规范化后哈希成固定长度的 `lb-box-<sha256>`;相同 scope 稳定复用,不同 scope 隔离,缺少 identity 时拒绝执行。SDK/Python 与 scoped MCP bridge 的工具调用遵守同一规则。详见 [box-session-scope.md](./box-session-scope.md)。
|
||||
|
||||
### REST API
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
## 1. Decision
|
||||
|
||||
The LangBot Host owns the Box session used by an event run. A Pipeline, Agent,
|
||||
or AgentRunner cannot choose a global, per-user, per-conversation, or per-query
|
||||
or Runner cannot choose a global, per-user, per-conversation, or per-query
|
||||
sandbox mode.
|
||||
|
||||
`BoxService.resolve_box_session_id(query)` always returns this shape:
|
||||
@@ -81,7 +81,7 @@ Host scope or launcher/session identity is also rejected. There is no
|
||||
|
||||
## 3. Host execution Query
|
||||
|
||||
AgentRunner callbacks need a Host-owned Query view because model/tool loaders
|
||||
Runner callbacks need a Host-owned Query view because model/tool loaders
|
||||
already consume that type. The Query is internal and is never exposed as a
|
||||
Runner-controlled object.
|
||||
|
||||
@@ -96,11 +96,11 @@ Runner-controlled object.
|
||||
This gives Pipeline and pure EBA execution the same Host tool path without
|
||||
inventing a fake Pipeline for an independent Agent.
|
||||
|
||||
## 4. AgentRunner callback paths
|
||||
## 4. Runner callback paths
|
||||
|
||||
AgentRunner implementations may use either callback transport:
|
||||
Runner implementations may use either callback transport:
|
||||
|
||||
1. SDK/Python runners call `AgentRunAPIProxy.call_tool`.
|
||||
1. SDK/Python runners call `RunnerAPIProxy.call_tool`.
|
||||
2. External harnesses call the SDK-owned scoped MCP bridge.
|
||||
|
||||
Both transports emit the same `PluginToRuntimeAction.CALL_TOOL`. The Host then
|
||||
@@ -108,8 +108,8 @@ validates the same run authorization, restores the same execution Query, and
|
||||
dispatches to the same ToolManager and BoxService.
|
||||
|
||||
```text
|
||||
AgentRunner
|
||||
+-- AgentRunAPIProxy.call_tool --------+
|
||||
Runner
|
||||
+-- RunnerAPIProxy.call_tool --------+
|
||||
| |
|
||||
+-- SDK-owned scoped MCP bridge -------+--> PluginToRuntimeAction.CALL_TOOL
|
||||
--> run authorization
|
||||
@@ -119,7 +119,7 @@ AgentRunner
|
||||
--> lb-box-<sha256>
|
||||
```
|
||||
|
||||
An AgentRunner is not required to use MCP. Local Python runners can use the SDK
|
||||
An Runner is not required to use MCP. Local Python runners can use the SDK
|
||||
directly; code-agent harnesses can use the bridge. The transports do not define
|
||||
different authorization or sandbox semantics.
|
||||
|
||||
@@ -143,16 +143,16 @@ This is separate from the scoped MCP bridge above:
|
||||
|
||||
| Path | Purpose | Session rule |
|
||||
| --- | --- | --- |
|
||||
| AgentRunner scoped MCP bridge | Call authorized Host tools for one active run | Host-owned `lb-box-<sha256>` from the run execution Query |
|
||||
| Runner scoped MCP bridge | Call authorized Host tools for one active run | Host-owned `lb-box-<sha256>` from the run execution Query |
|
||||
| MCP-in-Box stdio server | Keep configured MCP server processes running | Dedicated persistent `mcp-shared` session |
|
||||
|
||||
Calling a sandbox tool through the AgentRunner bridge never redirects the run
|
||||
Calling a sandbox tool through the Runner bridge never redirects the run
|
||||
workspace into `mcp-shared`. Conversely, an MCP server's managed-process
|
||||
lifecycle does not inherit the current event scope.
|
||||
|
||||
## 7. Configuration and compatibility
|
||||
|
||||
There is no Box session scope field in Pipeline metadata, AgentRunner config,
|
||||
There is no Box session scope field in Pipeline metadata, Runner config,
|
||||
or the public Pipeline/Runner API. Operators configure the Box subsystem itself
|
||||
(`box.enabled`, backend/runtime settings, profiles, mount allowlists, quotas,
|
||||
and workspace roots), not per-Runner session templates.
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
| Backend selection | 良好 | 显式 backend 优先级、local 探测顺序、配置变更触发 reselect |
|
||||
| MCP Box 集成 | 良好 | config model、路径重写、payload、shared-session 多 process |
|
||||
| Native tool loader | 良好 | 6 工具(exec/read/write/edit/glob/grep)、路径穿越拦截 |
|
||||
| AgentRunner 工具入口 | 良好 | SDK proxy 与 MCP bridge 都映射到 `PluginToRuntimeAction.CALL_TOOL`;Host action 测试覆盖 run-scoped execution Query 与纯 EBA native exec |
|
||||
| Runner 工具入口 | 良好 | SDK proxy 与 MCP bridge 都映射到 `PluginToRuntimeAction.CALL_TOOL`;Host action 测试覆盖 run-scoped execution Query 与纯 EBA native exec |
|
||||
| Skill 系统 | 良好 | 加载、Tool Call 激活、marker、路径解析、authoring CRUD、HTTP service |
|
||||
|
||||
---
|
||||
|
||||
@@ -3,24 +3,24 @@
|
||||
> 更新日期: 2026-06-29
|
||||
> 分支: `mcp_resources`
|
||||
> PR: langbot-app/LangBot#2215
|
||||
> 主题: MCP Resources 在 LangBot 中的产品价值、AgentRunner 集成方式与后续架构方向
|
||||
> 主题: MCP Resources 在 LangBot 中的产品价值、Runner 集成方式与后续架构方向
|
||||
|
||||
## 结论
|
||||
|
||||
PR #2215 对 LangBot 有明确价值:它补齐了 MCP 协议中 Resources 这一重要能力,让 MCP server 不再只暴露 tools,也可以暴露文档、代码片段、配置、日志、图片等上下文资源。管理端可以发现和预览资源,Agent 也可以通过当前实现按需列出和读取资源。
|
||||
|
||||
但当前 AgentRunner 层的接入方式更接近一个可用的第一阶段方案,而不是最终架构。现在 MCP Resources 被包装成两个 synthetic tools:
|
||||
但当前 Runner 层的接入方式更接近一个可用的第一阶段方案,而不是最终架构。现在 MCP Resources 被包装成两个 synthetic tools:
|
||||
|
||||
- `langbot_mcp_list_resources`
|
||||
- `langbot_mcp_read_resource`
|
||||
|
||||
这让模型可以通过 function calling 主动探索资源,落地成本低,也复用了已有 `ToolManager` / `LocalAgentRunner` 的工具调用链路。不过从 MCP 规范和主流实现来看,Resources 更适合作为一种一等上下文来源,而不是长期隐藏在工具列表里。
|
||||
这让模型可以通过 function calling 主动探索资源,落地成本低,也复用了已有 `ToolManager` / `LocalRunner` 的工具调用链路。不过从 MCP 规范和主流实现来看,Resources 更适合作为一种一等上下文来源,而不是长期隐藏在工具列表里。
|
||||
|
||||
建议保留当前 synthetic tools 作为探索能力,同时把后续主线设计调整为:MCP Resources 是 pipeline / conversation / message 级别可选择、可固定、可审计的上下文输入。
|
||||
|
||||
## 当前实现判断
|
||||
|
||||
当前 AgentRunner 集成路径如下:
|
||||
当前 Runner 集成路径如下:
|
||||
|
||||
```text
|
||||
Pipeline 绑定 MCP server
|
||||
@@ -28,7 +28,7 @@ Pipeline 绑定 MCP server
|
||||
-> Preproc 为 local-agent 加载工具
|
||||
-> ToolManager.get_all_tools()
|
||||
-> MCPLoader 注入 synthetic resource tools
|
||||
-> LocalAgentRunner 将工具 schema 传给模型
|
||||
-> LocalRunner 将工具 schema 传给模型
|
||||
-> 模型发起 list/read tool call
|
||||
-> ToolManager.execute_func_call()
|
||||
-> MCPLoader 调 MCP session.list_resources/read_resource
|
||||
@@ -178,7 +178,7 @@ LangBot 后续应支持模板发现、参数填写、实例化和绑定。否则
|
||||
- 支持 resource templates。
|
||||
- 支持资源订阅更新。
|
||||
- 支持 chunk、summary、RAG 化接入。
|
||||
- 为 DifyAgentRunner、LocalAgentRunner 等不同 runner 定义统一资源上下文接口。
|
||||
- 为 DifyRunner、LocalRunner 等不同 runner 定义统一资源上下文接口。
|
||||
|
||||
## 最终建议
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ Anthropic、Google 和 LiteLLM 的官方文档域名在本次环境中被浏览
|
||||
- `LLMModel.extra_args` 是 JSON 字段,Web 端已有通用高级参数编辑器。
|
||||
- `LiteLLMRequester` 会按“模型级 `extra_args`,再调用级 `extra_args`”的顺序合并参数。
|
||||
- LiteLLM 已统一处理多个 Provider 的 `reasoning_effort`、`thinking` 和返回的 `reasoning_content`。
|
||||
- `LocalAgentRunner` 的非流式、流式、工具调用和 fallback 路径都经过 `RuntimeProvider.invoke_llm*()`。
|
||||
- `LocalRunner` 的非流式、流式、工具调用和 fallback 路径都经过 `RuntimeProvider.invoke_llm*()`。
|
||||
- `remove-think` 已能控制 `<think>` 或独立 reasoning 内容是否进入展示文本。
|
||||
- Gemini 工具调用所需的 `provider_specific_fields` / thought signature 已有保留逻辑和单元测试。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user