mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 03:55:55 +00:00
docs(agent-runner): split protocol and context design
This commit is contained in:
308
docs/agent-runner-pluginization/AGENT_CONTEXT_PROTOCOL.md
Normal file
308
docs/agent-runner-pluginization/AGENT_CONTEXT_PROTOCOL.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# Agent-owned Context 协议设计
|
||||
|
||||
本文档描述插件化 AgentRunner 场景下的上下文边界。结论先行:LangBot 不应成为最终 agentic context manager;LangBot 应提供 context substrate,AgentRunner 或其背后的 agent runtime 自己决定如何管理历史、压缩、召回和 KV cache。
|
||||
|
||||
## 1. 设计原则
|
||||
|
||||
### 1.1 Agent 拥有上下文策略
|
||||
|
||||
不同 runner 背后的 runtime 差异很大:
|
||||
|
||||
- 官方 local-agent 可能依赖 LangBot 的模型、工具、知识库和存储。
|
||||
- Claude Code SDK / Codex 类 runtime 可能有自己的 session、transcript、tool loop 和上下文压缩。
|
||||
- Pi Agent SDK 或外部 agent 平台可能只需要当前事件和一个外部 conversation key。
|
||||
|
||||
因此 LangBot 不应强行决定最终传给模型的历史窗口。Host 只提供:
|
||||
|
||||
- 当前事件的完整结构化信息。
|
||||
- 稳定身份和会话引用。
|
||||
- 可授权读取的 history / event / artifact / state API。
|
||||
- payload hard cap 和权限 guardrail。
|
||||
|
||||
### 1.2 不再把 `max-round` 作为目标设计
|
||||
|
||||
旧 `max-round` 是 Pipeline local-agent 时代的兼容配置。它可以在迁移期被读取并转换为某种默认 bootstrap policy,但不应继续作为 AgentRunner 协议的核心概念。
|
||||
|
||||
新协议不应该问“LangBot 每轮裁几轮历史给 agent”,而应该问:
|
||||
|
||||
- 这类 runner 是否自管 context?
|
||||
- 事件到来时 host 应 inline 哪些最小信息?
|
||||
- agent 需要更多上下文时通过什么 API 拉取?
|
||||
- host 如何保证安全、可审计和可分页?
|
||||
|
||||
### 1.3 Host 保存事实源,Agent 管理 working context
|
||||
|
||||
三类数据要分开:
|
||||
|
||||
- `EventLog`: Host 保存原始事件、工具调用、投递结果、错误和系统事件。
|
||||
- `Transcript`: Host 从 EventLog 投影出的对话视图,用于 UI、审计和按需历史读取。
|
||||
- `Working context`: Agent 本轮实际送进模型或 runtime 的上下文,由 AgentRunner 决定。
|
||||
|
||||
LangBot 可以为简单 runner 提供 bootstrap window,但这只是 convenience,不是主架构。
|
||||
|
||||
## 2. Event 到来时传什么
|
||||
|
||||
默认 `AgentRunContext` 应尽量小且稳定:
|
||||
|
||||
```python
|
||||
class AgentRunContext(BaseModel):
|
||||
run_id: str
|
||||
trigger: AgentTrigger
|
||||
event: AgentEventContext
|
||||
conversation: ConversationContext | None
|
||||
actor: ActorContext | None
|
||||
subject: SubjectContext | None
|
||||
input: AgentInput
|
||||
delivery: DeliveryContext
|
||||
resources: AgentResources
|
||||
context: ContextAccess
|
||||
state: AgentRunState
|
||||
runtime: AgentRuntimeContext
|
||||
config: dict[str, Any]
|
||||
```
|
||||
|
||||
默认规则:
|
||||
|
||||
- Host MUST NOT inline full history by default.
|
||||
- Host SHOULD inline only current event / input and context handles.
|
||||
- Runner owns working-context assembly.
|
||||
- Runner MAY use Host history / event / artifact / state / storage APIs when authorized.
|
||||
- Official runners MUST consume Host infrastructure through the same public APIs as third-party runners.
|
||||
|
||||
### 2.1 必须 inline 的内容
|
||||
|
||||
每次 run 必须 inline:
|
||||
|
||||
- 当前 event 的稳定类型、id、时间、source。
|
||||
- 当前输入文本和结构化内容。
|
||||
- 附件 / 文件 / 图片的 metadata 和 artifact ref。
|
||||
- actor、subject、conversation、thread、bot、workspace。
|
||||
- delivery 能力,例如是否支持 streaming、reply target、平台限制。
|
||||
- 已授权资源列表。
|
||||
- context cursors 和可用 API 能力。
|
||||
- runner binding config。
|
||||
|
||||
这些是 agent 决定下一步需要的最低信息。
|
||||
|
||||
### 2.2 默认不 inline 的内容
|
||||
|
||||
默认不要 inline:
|
||||
|
||||
- 完整历史消息。
|
||||
- 大文件全文。
|
||||
- 大工具结果。
|
||||
- 全量知识库内容。
|
||||
- 平台原始 payload 大对象。
|
||||
- 每轮重新生成的大段 summary。
|
||||
|
||||
这些会破坏跨进程序列化成本、泄露范围、KV cache 稳定性,也会迫使 host 替 agent 做 context 策略。
|
||||
|
||||
### 2.3 可选 bootstrap
|
||||
|
||||
根据 runner manifest 可以提供可选 bootstrap:
|
||||
|
||||
```yaml
|
||||
context:
|
||||
bootstrap: none | current_event | recent_tail | summary_tail
|
||||
max_inline_events: 0
|
||||
max_inline_bytes: 0
|
||||
```
|
||||
|
||||
建议默认:
|
||||
|
||||
- 自管 runtime:`bootstrap: current_event`
|
||||
- 简单 HTTP runner:`bootstrap: recent_tail`
|
||||
- 兼容旧 local-agent:迁移期可以把旧 `max-round` 映射为 `recent_tail` 的配置,但不再作为协议字段扩展。
|
||||
|
||||
## 3. ContextAccess
|
||||
|
||||
`ContextAccess` 是 host 交给 agent 的上下文读取入口描述:
|
||||
|
||||
```python
|
||||
class ContextAccess(BaseModel):
|
||||
conversation_id: str | None
|
||||
thread_id: str | None
|
||||
latest_cursor: str | None
|
||||
event_seq: int | None
|
||||
transcript_seq: int | None
|
||||
has_history_before: bool
|
||||
inline_policy: InlineContextPolicy
|
||||
available_apis: ContextAPICapabilities
|
||||
```
|
||||
|
||||
它告诉 agent:
|
||||
|
||||
- 当前事件位于哪条 conversation / thread。
|
||||
- 若需要更多历史,从哪个 cursor 开始拉。
|
||||
- host inline 了什么,没 inline 什么。
|
||||
- 当前 run 有哪些 context API 权限。
|
||||
|
||||
## 4. Agent 如何获取更多上下文
|
||||
|
||||
所有 API 都必须走 `AgentRunAPIProxy`,并由 host 用 `run_id` 校验。
|
||||
|
||||
### 4.1 History API
|
||||
|
||||
```python
|
||||
await api.history.page(
|
||||
conversation_id=ctx.context.conversation_id,
|
||||
before_cursor=ctx.context.latest_cursor,
|
||||
limit=50,
|
||||
direction="backward",
|
||||
include_artifacts=False,
|
||||
)
|
||||
```
|
||||
|
||||
返回:
|
||||
|
||||
```python
|
||||
class HistoryPage(BaseModel):
|
||||
items: list[TranscriptItem]
|
||||
next_cursor: str | None
|
||||
prev_cursor: str | None
|
||||
has_more: bool
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `limit` 有 host hard cap。
|
||||
- 默认只能读当前 conversation / thread。
|
||||
- 跨会话读取必须有 manifest permission + binding policy。
|
||||
- 返回 artifact ref,不默认返回大文件内容。
|
||||
|
||||
### 4.2 Search API
|
||||
|
||||
```python
|
||||
await api.history.search(
|
||||
query="用户之前提到的数据库连接信息",
|
||||
filters={
|
||||
"conversation_id": ctx.context.conversation_id,
|
||||
"event_types": ["message.received"],
|
||||
},
|
||||
top_k=10,
|
||||
)
|
||||
```
|
||||
|
||||
Search 可以先用数据库全文索引,后续再接 embedding recall。它是 host 提供的检索能力,不等于 agent 的长期记忆策略。
|
||||
|
||||
### 4.3 Event API
|
||||
|
||||
```python
|
||||
await api.events.get(event_id)
|
||||
await api.events.page(before_cursor=..., limit=...)
|
||||
```
|
||||
|
||||
Event API 用于读取非消息事件、工具事件、系统事件。Agent 不应把所有事件都当成 user/assistant message。
|
||||
|
||||
### 4.4 Artifact API
|
||||
|
||||
```python
|
||||
await api.artifacts.metadata(artifact_id)
|
||||
await api.artifacts.read_range(artifact_id, offset=0, length=65536)
|
||||
await api.artifacts.open_stream(artifact_id)
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 校验 artifact 所属 conversation / run / binding。
|
||||
- 校验 MIME、大小、过期时间和权限。
|
||||
- 大文件按 range/stream 读取。
|
||||
- 工具大结果也应 artifact 化。
|
||||
|
||||
### 4.5 State API
|
||||
|
||||
```python
|
||||
await api.state.get(scope="conversation", key="external.session_id")
|
||||
await api.state.set(scope="conversation", key="summary.checkpoint", value=...)
|
||||
```
|
||||
|
||||
State 是可选寄宿能力。自管 runtime 可以完全不用;依附 LangBot 的官方 runner 可以使用。
|
||||
|
||||
## 5. Runner manifest 中的上下文声明
|
||||
|
||||
建议增加:
|
||||
|
||||
```yaml
|
||||
context:
|
||||
ownership: self_managed | host_bootstrap | hybrid
|
||||
bootstrap: none | current_event | recent_tail | summary_tail
|
||||
max_inline_events: 0
|
||||
max_inline_bytes: 0
|
||||
supports_history_pull: true
|
||||
supports_history_search: true
|
||||
supports_artifact_pull: true
|
||||
owns_compaction: true
|
||||
wants_static_context_refs: true
|
||||
```
|
||||
|
||||
语义:
|
||||
|
||||
- `self_managed`: Host 不主动 inline 历史,只提供 event 和 handles。
|
||||
- `host_bootstrap`: Host 为简单 runner inline 一个小窗口。
|
||||
- `hybrid`: Host inline summary/tail,runner 仍可按需拉更多。
|
||||
- `owns_compaction`: runner 负责压缩,host 不做语义摘要。
|
||||
- `wants_static_context_refs`: host 用 ref/hash 描述静态内容,减少重复 payload。
|
||||
|
||||
## 6. KV cache 友好的上下文管理
|
||||
|
||||
如果目标是支持 Claude Code SDK、Codex、Pi Agent SDK 等 runtime,必须避免每轮由 LangBot 重组大块 prompt。
|
||||
|
||||
建议:
|
||||
|
||||
- 稳定 session key:`workspace/bot/binding/runner/conversation/thread`。
|
||||
- 静态内容使用 `ref + version/hash`:system prompt、resource manifest、tool schema、platform policy。
|
||||
- 每轮只传 delta:当前 event、artifact refs、少量 runtime metadata。
|
||||
- 历史 append-only:不要每轮改写同一段 history 文本。
|
||||
- Summary checkpoint 稳定:只有压缩发生时产生新 checkpoint,不要每轮微调。
|
||||
- 大文件和工具结果 artifact 化。
|
||||
- Tool/context API schema 稳定,数据通过 API 拉取,而不是塞入 prompt。
|
||||
- 对自管 runtime,优先让它复用自身 session/cache,而不是强制 LangBot 每轮重放 transcript。
|
||||
|
||||
## 7. Host guardrail
|
||||
|
||||
Agent 自管 context 不代表无限制访问。LangBot 仍必须控制:
|
||||
|
||||
- 每次 run 的 active `run_id`。
|
||||
- runner identity。
|
||||
- 当前 binding 的 resource policy。
|
||||
- conversation / actor / subject scope。
|
||||
- page size、artifact read size、API rate limit。
|
||||
- 跨会话读取权限。
|
||||
- 数据脱敏和敏感变量过滤。
|
||||
- 审计日志。
|
||||
|
||||
Host 不负责“最佳上下文策略”,但负责“不越权、不爆内存、不不可审计”。
|
||||
|
||||
## 8. 官方 runner 与业务编排边界
|
||||
|
||||
官方 runner 插件可以选择把状态寄宿在 LangBot,但它们必须和第三方 runner 一样通过公开 Host APIs 消费这些能力。
|
||||
|
||||
LangBot core 不应内置官方 agent 的业务流程:
|
||||
|
||||
- 不内置 prompt 组装策略。
|
||||
- 不内置 tool loop。
|
||||
- 不内置 RAG 编排策略。
|
||||
- 不内置 summary / compaction 策略。
|
||||
- 不内置“local-agent 专用”的状态字段。
|
||||
|
||||
官方 local-agent 应作为“依附 LangBot 基础设施的复杂 runner 参考实现”存在:
|
||||
|
||||
- transcript / history 通过 `api.history.page()` 或 `api.history.search()` 读取。
|
||||
- summary、checkpoint、外部 session id、用户偏好通过 `api.state` 或 `api.storage` 保存。
|
||||
- 图片、文件、工具大结果通过 `api.artifacts` 读取。
|
||||
- 模型、工具、知识库通过 `api.models`、`api.tools`、`api.knowledge` 调用。
|
||||
|
||||
这样 LangBot 保持为通用 agent host,不变成内置 agent 框架。
|
||||
|
||||
## 9. 当前实现需要调整
|
||||
|
||||
当前代码已有 `AgentContextPackager`,它按 legacy `max-round` 裁剪 `query.messages`。目标方向不是继续增强它,而是把它降级为兼容 adapter:
|
||||
|
||||
- `max-round` 迁移为旧 binding 的 bootstrap 配置。
|
||||
- 新 runner 默认不收到历史窗口。
|
||||
- `AgentRunContext` 增加 `context` / cursor / access capabilities。
|
||||
- `AgentRunAPIProxy` 增加 history / events / artifacts API。
|
||||
- Host 增加持久 EventLog / Transcript / ArtifactStore。
|
||||
- local-agent 插件再基于这些 API 决定是否拉历史、怎么压缩、怎么组 prompt。
|
||||
|
||||
这样 LangBot 既能服务依附 host 基础设施的官方 runner,也能服务自带 memory/session/cache 的外部 agent runtime。
|
||||
215
docs/agent-runner-pluginization/EVENT_BASED_AGENT.md
Normal file
215
docs/agent-runner-pluginization/EVENT_BASED_AGENT.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Event Based Agent 预留设计
|
||||
|
||||
本文档描述未来 EBA 接入时,事件如何进入 LangBot、如何触发 AgentRunner,以及如何复用插件化 agent 基础设施。
|
||||
|
||||
本阶段不实现完整 EventBus / EventRouter / Platform API。本阶段要做的是把协议边界设计对,避免当前消息入口继续绑死 Pipeline 和用户文本消息。
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
- 消息、撤回、入群、好友申请、定时任务、API 调用都能抽象为 host event。
|
||||
- EventRouter 可以根据 event type、bot、workspace、conversation、actor、subject 解析 AgentBinding。
|
||||
- AgentRunner 通过同一套 orchestrator 被调用。
|
||||
- 非消息事件不伪造成用户文本消息。
|
||||
- 平台动作执行通过显式 capability / permission / result type 预留,不混入普通文本回复。
|
||||
|
||||
## 2. 事件不是消息
|
||||
|
||||
`message.received` 只是事件的一种。协议不应假设:
|
||||
|
||||
- 一定有用户文本。
|
||||
- 一定有 conversation history。
|
||||
- 一定要返回一条聊天消息。
|
||||
- actor 一定等于 sender。
|
||||
- subject 一定等于当前消息。
|
||||
|
||||
例如:
|
||||
|
||||
| event_type | actor | subject | input |
|
||||
| --- | --- | --- | --- |
|
||||
| `message.received` | 发消息的人 | 当前消息 | 文本、图片、文件等 |
|
||||
| `message.recalled` | 撤回操作者,未知时为系统 | 被撤回消息 | 通常为空 |
|
||||
| `group.member_joined` | 新成员或邀请人 | 群/成员关系 | 通常为空 |
|
||||
| `friend.request_received` | 申请人 | 好友申请 | 验证消息或申请理由 |
|
||||
| `schedule.triggered` | 系统 | 定时任务 | 任务 payload |
|
||||
| `api.invoked` | API caller | API request | request payload |
|
||||
|
||||
## 3. Event Envelope
|
||||
|
||||
建议事件 envelope:
|
||||
|
||||
```python
|
||||
class AgentEventEnvelope(BaseModel):
|
||||
event_id: str
|
||||
event_type: str
|
||||
event_time: int | None
|
||||
source: EventSource
|
||||
workspace_id: str | None
|
||||
bot_id: str | None
|
||||
conversation_id: str | None
|
||||
thread_id: str | None
|
||||
actor: ActorRef | None
|
||||
subject: SubjectRef | None
|
||||
input: AgentInput
|
||||
delivery: DeliveryContext
|
||||
raw_ref: RawEventRef | None
|
||||
metadata: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
顶层字段使用 LangBot 稳定协议名。平台原始事件名和原始 payload 放到 `metadata` 或 `raw_ref`,不直接成为 runner 的稳定依赖。
|
||||
|
||||
## 4. Event Source
|
||||
|
||||
事件来源可以包括:
|
||||
|
||||
- `platform_adapter`: 飞书、QQ、微信、Telegram 等 IM 平台。
|
||||
- `webui`: Debug Chat、控制台操作。
|
||||
- `http_api`: 外部系统调用 LangBot。
|
||||
- `scheduler`: 定时任务。
|
||||
- `system`: runtime、plugin、maintenance 事件。
|
||||
|
||||
同一个 event source 可以产生多个 event type。EventRouter 不应该写死平台 adapter 的类名。
|
||||
|
||||
## 5. Event Binding
|
||||
|
||||
EBA 中,AgentBinding 取代 Pipeline runner 配置成为触发关系:
|
||||
|
||||
```python
|
||||
class AgentBinding(BaseModel):
|
||||
binding_id: str
|
||||
enabled: bool
|
||||
event_types: list[str]
|
||||
scope: BindingScope
|
||||
filters: list[EventFilter]
|
||||
runner_id: str
|
||||
runner_config: dict[str, Any]
|
||||
resource_policy: ResourcePolicy
|
||||
state_policy: StatePolicy
|
||||
delivery_policy: DeliveryPolicy
|
||||
```
|
||||
|
||||
Binding scope 示例:
|
||||
|
||||
- workspace 全局。
|
||||
- bot 级别。
|
||||
- platform channel 级别。
|
||||
- conversation / group / thread 级别。
|
||||
- user / actor 级别。
|
||||
|
||||
旧 Pipeline 可以迁移为 `message.received` 的 binding source,但不是唯一 binding source。
|
||||
|
||||
## 6. EventRouter 调用链
|
||||
|
||||
目标调用链:
|
||||
|
||||
```text
|
||||
Platform Adapter / WebUI / API
|
||||
-> Event Gateway normalize payload
|
||||
-> EventLog append raw event
|
||||
-> EventRouter resolve bindings
|
||||
-> AgentRunOrchestrator.run(event, binding)
|
||||
-> AgentRunContextBuilder.build(event, binding)
|
||||
-> PluginRuntimeConnector.run_agent()
|
||||
-> AgentRunResult stream
|
||||
-> DeliveryController render / platform action
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `run_from_event()` 必须复用现有 orchestrator 能力。
|
||||
- 不能为 EBA 单独实现另一套 plugin runner 调用协议。
|
||||
- 不能让非消息事件绕过 resource authorization。
|
||||
- Delivery 和 platform action 要走统一权限模型。
|
||||
|
||||
## 7. Delivery Context
|
||||
|
||||
Event 不一定回复到当前聊天窗口。需要显式 delivery:
|
||||
|
||||
```python
|
||||
class DeliveryContext(BaseModel):
|
||||
surface: str
|
||||
reply_target: ReplyTarget | None
|
||||
supports_streaming: bool
|
||||
supports_edit: bool
|
||||
supports_reaction: bool
|
||||
max_message_size: int | None
|
||||
platform_capabilities: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
消息事件通常带 reply target。系统事件可能没有默认 reply target,需要 runner 返回 `action.requested` 或由 binding 的 delivery policy 决定投递位置。
|
||||
|
||||
## 8. AgentRunResult 与平台动作
|
||||
|
||||
当前消息路径主要消费:
|
||||
|
||||
- `message.delta`
|
||||
- `message.completed`
|
||||
- `run.completed`
|
||||
- `run.failed`
|
||||
|
||||
EBA 后需要预留:
|
||||
|
||||
- `action.requested`: 请求 host 执行平台动作。
|
||||
- `artifact.created`: runner 生成文件或大结果。
|
||||
- `delivery.requested`: 请求投递到某个 surface。
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "action.requested",
|
||||
"data": {
|
||||
"action": "friend.request.accept",
|
||||
"target": {"platform": "wechat", "request_id": "..."},
|
||||
"reason": "policy matched"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Host 必须校验:
|
||||
|
||||
- runner manifest 是否声明 platform_api capability。
|
||||
- binding 是否授权该 action。
|
||||
- actor / bot / workspace 是否允许。
|
||||
- 是否需要人工审批。
|
||||
|
||||
本阶段如收到 `action.requested`,可以只记录 telemetry,不执行。
|
||||
|
||||
## 9. 与 Context 协议的关系
|
||||
|
||||
EBA 事件进入 AgentRunner 时仍使用 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) 的原则:
|
||||
|
||||
- inline 当前事件。
|
||||
- 大 payload 用 raw/artifact ref。
|
||||
- 不默认 inline 完整 history。
|
||||
- agent 按需通过 API 拉 history/event/artifact/state。
|
||||
- Host 保留 EventLog 和权限 guardrail。
|
||||
|
||||
非消息事件可以被投影进 Transcript,但不能强制伪装为 user message。AgentRunner 可以根据 event type 自己决定是否把它纳入模型上下文。
|
||||
|
||||
## 10. 当前实现与目标差距
|
||||
|
||||
当前已有:
|
||||
|
||||
- `AgentRunOrchestrator`
|
||||
- `AgentRunContextBuilder`
|
||||
- `AgentRunResult` 基础消息流
|
||||
- `ctx.event` 的最小消息事件封装
|
||||
|
||||
仍需要:
|
||||
|
||||
- `AgentEventEnvelope` 独立模型。
|
||||
- EventLog 持久化。
|
||||
- AgentBinding 持久模型。
|
||||
- EventRouter。
|
||||
- DeliveryContext。
|
||||
- platform action permission model。
|
||||
- `run_from_query()` 到 `run(event, binding)` 的迁移。
|
||||
|
||||
## 11. 落地顺序
|
||||
|
||||
1. 先把当前 Pipeline 消息入口适配成 `message.received` event。
|
||||
2. 增加 `AgentBinding` 抽象,先由 Pipeline config 生成。
|
||||
3. `AgentRunContextBuilder` 改为从 event + binding 构造 context。
|
||||
4. 引入 EventLog / Transcript。
|
||||
5. 增加非消息事件的协议测试,不接真实平台。
|
||||
6. 再接入真实 EventRouter 和 platform action。
|
||||
364
docs/agent-runner-pluginization/HOST_SDK_INFRASTRUCTURE.md
Normal file
364
docs/agent-runner-pluginization/HOST_SDK_INFRASTRUCTURE.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# LangBot Host 与 SDK 基础设施设计
|
||||
|
||||
本文档描述 LangBot 和 SDK 为插件化 AgentRunner 共同提供的基础设施。它不以 Pipeline 为中心,也不以官方 local-agent 的实现方式为前提。
|
||||
|
||||
## 1. 目标
|
||||
|
||||
LangBot 要转为 agent host,而不是内置 runner 容器:
|
||||
|
||||
- 接收 IM、WebUI、API 和未来 EventRouter 产生的事件。
|
||||
- 根据事件、bot、workspace、scope 解析应该调用的 agent binding。
|
||||
- 发现、校验和调用插件提供的 AgentRunner。
|
||||
- 为每次 run 提供受限资源、状态、存储、上下文引用和生命周期控制。
|
||||
- 接收 AgentRunner 返回的事件流,并投递到 IM、WebUI 或其他 output surface。
|
||||
|
||||
SDK 要提供稳定协议:
|
||||
|
||||
- `AgentRunner` 组件定义。
|
||||
- runner manifest / capabilities / permissions / config schema。
|
||||
- `AgentRunContext` 输入 envelope。
|
||||
- `AgentRunResult` 输出事件流。
|
||||
- `AgentRunAPIProxy` 运行期受限 API。
|
||||
|
||||
## 2. 非目标
|
||||
|
||||
- 不把 Pipeline 当作长期架构中心。
|
||||
- 不要求所有 AgentRunner 依赖 LangBot 的上下文管理。
|
||||
- 不要求官方 local-agent 的旧行为反向塑造 host 协议。
|
||||
- 不在 host 中实现通用 agentic prompt assembler。
|
||||
- 不强制 runner 使用 LangBot state / storage;LangBot 只提供可选、受控的寄宿能力。
|
||||
|
||||
## 3. 分层架构
|
||||
|
||||
目标结构:
|
||||
|
||||
```text
|
||||
IM / WebUI / API / EventRouter
|
||||
|
|
||||
v
|
||||
Event Gateway
|
||||
|
|
||||
v
|
||||
AgentBindingResolver
|
||||
|
|
||||
v
|
||||
AgentRunOrchestrator
|
||||
|-- AgentRunnerRegistry
|
||||
|-- AgentResourceBuilder
|
||||
|-- AgentContextBuilder
|
||||
|-- AgentRunSessionRegistry
|
||||
|-- AgentStateStore / Storage / EventLog / ArtifactStore
|
||||
v
|
||||
Plugin Runtime / AgentRunner
|
||||
|
|
||||
v
|
||||
AgentRunResult stream
|
||||
|
|
||||
v
|
||||
Delivery / Renderer / Platform API
|
||||
```
|
||||
|
||||
当前 Pipeline 只应接入在 `Event Gateway` 或兼容 adapter 位置。它可以继续产生 `message.received`,但不应继续拥有 runner 选择、上下文裁剪和业务 agent 执行的核心语义。
|
||||
|
||||
## 4. LangBot 侧能力
|
||||
|
||||
### 4.1 Event Gateway
|
||||
|
||||
Event Gateway 负责把入口统一成 host event:
|
||||
|
||||
- IM 平台消息。
|
||||
- WebUI debug chat 消息。
|
||||
- API 触发。
|
||||
- 后续非消息事件,例如入群、撤回、好友申请。
|
||||
|
||||
输出应是稳定 envelope,而不是 Pipeline Query 私有结构:
|
||||
|
||||
```python
|
||||
class AgentEventEnvelope(BaseModel):
|
||||
event_id: str
|
||||
event_type: str
|
||||
event_time: int | None
|
||||
source: str
|
||||
bot_id: str | None
|
||||
workspace_id: str | None
|
||||
conversation_id: str | None
|
||||
thread_id: str | None
|
||||
actor: ActorRef | None
|
||||
subject: SubjectRef | None
|
||||
input: AgentInput
|
||||
delivery: DeliveryContext
|
||||
raw_ref: RawEventRef | None
|
||||
```
|
||||
|
||||
原始平台 payload 可以存为 raw event 或 artifact ref;不要把平台私有字段直接扩散到 AgentRunner 顶层协议。
|
||||
|
||||
### 4.2 Agent Binding
|
||||
|
||||
Agent binding 是“什么事件调用哪个 runner、带什么绑定配置”的持久配置。它替代长期依赖 Pipeline runner config 的角色。
|
||||
|
||||
建议模型:
|
||||
|
||||
```python
|
||||
class AgentBinding(BaseModel):
|
||||
binding_id: str
|
||||
scope: BindingScope
|
||||
event_types: list[str]
|
||||
runner_id: str
|
||||
runner_config: dict[str, Any]
|
||||
resource_policy: ResourcePolicy
|
||||
state_policy: StatePolicy
|
||||
delivery_policy: DeliveryPolicy
|
||||
enabled: bool
|
||||
```
|
||||
|
||||
Pipeline 当前可以被迁移为一种 binding source:
|
||||
|
||||
- 旧 Pipeline AI runner config -> `AgentBinding`
|
||||
- 旧 Pipeline extension preference -> `resource_policy`
|
||||
- 旧 Pipeline output settings -> `delivery_policy`
|
||||
|
||||
但新设计不应再把这些字段命名为 Pipeline 专属概念。
|
||||
|
||||
### 4.3 AgentRunnerRegistry
|
||||
|
||||
Registry 负责收集 runner descriptor:
|
||||
|
||||
- 插件 runtime 提供的 `AgentRunner`。
|
||||
- 可能存在的 host adapter runner。
|
||||
- 开发期本地插件 runner。
|
||||
|
||||
Descriptor 必须包含:
|
||||
|
||||
```python
|
||||
class AgentRunnerDescriptor(BaseModel):
|
||||
id: str
|
||||
source: Literal["plugin", "host_adapter"]
|
||||
label: I18nObject
|
||||
description: I18nObject | None = None
|
||||
capabilities: AgentRunnerCapabilities
|
||||
permissions: AgentRunnerPermissions
|
||||
config_schema: list[DynamicFormItemSchema]
|
||||
plugin: PluginRef | None = None
|
||||
```
|
||||
|
||||
`plugin:author/name/runner` 仍可作为稳定 id 格式。多个 binding 指向同一个 runner id 时,不创建多个插件实例。
|
||||
|
||||
### 4.4 AgentRunOrchestrator
|
||||
|
||||
Orchestrator 是唯一运行入口:
|
||||
|
||||
```text
|
||||
run(event, binding)
|
||||
-> resolve runner descriptor
|
||||
-> build resources
|
||||
-> build context
|
||||
-> register run session
|
||||
-> call plugin runtime
|
||||
-> normalize result stream
|
||||
-> update state
|
||||
-> unregister run session
|
||||
```
|
||||
|
||||
它负责:
|
||||
|
||||
- `run_id` 生成和生命周期。
|
||||
- timeout / deadline / cancellation。
|
||||
- 插件异常隔离。
|
||||
- result schema 校验和大小限制。
|
||||
- state.updated 处理。
|
||||
- delivery backpressure 和 telemetry。
|
||||
|
||||
`run_from_query()` 这类 API 可以保留为兼容 adapter,但内部应转换成 event + binding 后走统一 `run()`。
|
||||
|
||||
### 4.5 Resource Authorization
|
||||
|
||||
LangBot 在每次 run 前生成 `ctx.resources`。资源来自三层约束:
|
||||
|
||||
- runner manifest 声明的 permissions。
|
||||
- binding/resource policy 允许的资源范围。
|
||||
- 当前 event / actor / bot / workspace 的实际权限。
|
||||
|
||||
资源类型包括:
|
||||
|
||||
- models
|
||||
- tools
|
||||
- knowledge bases
|
||||
- files / artifacts
|
||||
- storage
|
||||
- platform capabilities
|
||||
- history / transcript access
|
||||
|
||||
运行期 action 必须再次通过 `run_id` 校验。SDK 侧本地校验只用于开发体验,host 侧校验才是安全边界。
|
||||
|
||||
### 4.6 State 与 Storage
|
||||
|
||||
LangBot 可以提供 host-owned state,让 AgentRunner 把状态寄宿在 LangBot:
|
||||
|
||||
- conversation state
|
||||
- actor state
|
||||
- subject state
|
||||
- runner/binding state
|
||||
- workspace state
|
||||
|
||||
但这不是强制。外部 agent runtime 可以维护自己的 session 和 memory。LangBot 只需要提供:
|
||||
|
||||
- 授权开关。
|
||||
- scope key。
|
||||
- get/set/list/delete API。
|
||||
- 持久化 backend。
|
||||
- 审计和清理策略。
|
||||
|
||||
当前进程内 state store 只能作为过渡实现,不能作为正式生产语义。
|
||||
|
||||
### 4.7 EventLog / Transcript / Artifact
|
||||
|
||||
LangBot 应提供事实源能力:
|
||||
|
||||
- `EventLog`: 保存原始事件、系统事件、工具调用、投递结果、错误。
|
||||
- `Transcript`: 面向对话 UI / agent history 的消息投影。
|
||||
- `ArtifactStore`: 保存大文件、多模态输入、工具大结果、平台附件。
|
||||
|
||||
AgentRunner 可以读取这些能力,但不能被迫使用 LangBot 作为唯一记忆系统。
|
||||
|
||||
## 5. SDK 侧协议
|
||||
|
||||
### 5.1 AgentRunner 组件
|
||||
|
||||
```python
|
||||
class AgentRunner(BaseComponent):
|
||||
__kind__ = "AgentRunner"
|
||||
|
||||
@classmethod
|
||||
def get_capabilities(cls) -> AgentRunnerCapabilities:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def get_config_schema(cls) -> list[dict]:
|
||||
...
|
||||
|
||||
async def run(self, ctx: AgentRunContext) -> AsyncGenerator[AgentRunResult, None]:
|
||||
...
|
||||
```
|
||||
|
||||
### 5.2 Capabilities
|
||||
|
||||
建议能力声明:
|
||||
|
||||
```yaml
|
||||
capabilities:
|
||||
streaming: true
|
||||
tool_calling: true
|
||||
knowledge_retrieval: true
|
||||
multimodal_input: true
|
||||
event_context: true
|
||||
platform_api: false
|
||||
interrupt: true
|
||||
stateful_session: true
|
||||
self_managed_context: true
|
||||
host_state: optional
|
||||
```
|
||||
|
||||
`self_managed_context` 表示 runner 或外部 runtime 自己管理上下文。Host 不应给它强塞历史窗口,只提供当前事件和 context handles。
|
||||
|
||||
### 5.3 Permissions
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
models: ["invoke", "stream", "rerank"]
|
||||
tools: ["detail", "call"]
|
||||
knowledge_bases: ["list", "retrieve"]
|
||||
history: ["page", "search"]
|
||||
artifacts: ["metadata", "read"]
|
||||
storage: ["plugin", "workspace", "binding"]
|
||||
platform_api: []
|
||||
```
|
||||
|
||||
权限声明是 runner 需要的最大能力,实际可用资源仍由 binding 和当前运行上下文裁剪。
|
||||
|
||||
### 5.4 AgentRunContext
|
||||
|
||||
Context 顶层应是 event-first,而不是 Query-first:
|
||||
|
||||
```python
|
||||
class AgentRunContext(BaseModel):
|
||||
run_id: str
|
||||
trigger: AgentTrigger
|
||||
event: AgentEventContext
|
||||
conversation: ConversationContext | None = None
|
||||
actor: ActorContext | None = None
|
||||
subject: SubjectContext | None = None
|
||||
input: AgentInput
|
||||
resources: AgentResources
|
||||
context: ContextAccess
|
||||
state: AgentRunState
|
||||
runtime: AgentRuntimeContext
|
||||
config: dict[str, Any]
|
||||
```
|
||||
|
||||
`messages` 可以作为兼容字段或 bootstrap 字段,但不应继续是协议核心。
|
||||
|
||||
### 5.5 AgentRunResult
|
||||
|
||||
输出应是事件流:
|
||||
|
||||
```python
|
||||
class AgentRunResult(BaseModel):
|
||||
type: Literal[
|
||||
"message.delta",
|
||||
"message.completed",
|
||||
"tool.call.started",
|
||||
"tool.call.completed",
|
||||
"state.updated",
|
||||
"artifact.created",
|
||||
"action.requested",
|
||||
"run.completed",
|
||||
"run.failed",
|
||||
]
|
||||
data: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
当前消息回复只消费 `message.delta` / `message.completed` / `run.failed`。平台动作执行等 EBA 和 platform API 权限落地后再启用。
|
||||
|
||||
### 5.6 AgentRunAPIProxy
|
||||
|
||||
Proxy 是 runner 访问 host 能力的唯一入口:
|
||||
|
||||
- model APIs
|
||||
- tool APIs
|
||||
- knowledge APIs
|
||||
- state / storage APIs
|
||||
- history / event APIs
|
||||
- artifact APIs
|
||||
- platform APIs
|
||||
|
||||
所有请求必须带 `run_id`,host 侧按 active run session 验证 runner identity 和 resource ACL。
|
||||
|
||||
## 6. 当前实现与目标差距
|
||||
|
||||
已落地:
|
||||
|
||||
- `AgentRunnerRegistry`
|
||||
- `AgentRunOrchestrator`
|
||||
- `AgentRunContextBuilder`
|
||||
- `AgentResourceBuilder`
|
||||
- `AgentRunSessionRegistry`
|
||||
- `AgentRunAPIProxy` 基础模型 / 工具 / 知识库授权路径
|
||||
|
||||
需要调整:
|
||||
|
||||
- 把 `pipeline_config` 语义抽象为 `AgentBinding`。
|
||||
- 把 `Query` 输入抽象为 `AgentEventEnvelope`。
|
||||
- 把 legacy `max-round` 从目标设计中移除,只作为旧配置兼容处理。
|
||||
- 把 state store 改为持久 host storage backend。
|
||||
- 增加 EventLog / Transcript / ArtifactStore。
|
||||
- 增加 history / artifact / event 的受限拉取 API。
|
||||
|
||||
## 7. 落地顺序
|
||||
|
||||
1. 固化 README 路由和专题文档边界。
|
||||
2. 在 Host 中抽象 `AgentBinding`,先由 Pipeline adapter 生成。
|
||||
3. 将 `AgentRunContextBuilder` 改为 event-first。
|
||||
4. 增加持久 transcript/event log 的最小存储模型。
|
||||
5. 扩展 `AgentRunAPIProxy` 的 history / artifact / state API。
|
||||
6. 将 Pipeline-only 字段逐步下沉到兼容 adapter。
|
||||
7. 再设计官方 local-agent 插件如何消费这些基础设施。
|
||||
@@ -1,6 +1,14 @@
|
||||
# 官方 AgentRunner 插件迁移计划
|
||||
|
||||
本文档描述内置 `RequestRunner` 迁出 LangBot 后,官方 runner 插件如何组织、迁移和验收。
|
||||
它是 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md) 和
|
||||
[AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) 的下游落地计划,不是 LangBot
|
||||
宿主协议的设计前提。
|
||||
|
||||
官方 `local-agent` 可以外移,也可以重写。设计重点不是保留旧内置 runner 的内部结构,
|
||||
而是验证一个依附 LangBot host 基础设施的官方 agent 能否完整工作。同时,LangBot 的
|
||||
host 协议必须服务 Claude Code SDK、Codex、Pi Agent SDK、外部 Agent 平台等自管
|
||||
context/runtime 的 runner,不能被官方插件的实现细节绑死。
|
||||
|
||||
当前实现已经进入过渡阶段:
|
||||
|
||||
@@ -18,8 +26,9 @@
|
||||
|
||||
不要把官方 runner 插件重新绑死在 LangBot 主仓库内。允许开发期使用本地路径插件,但运行边界必须保持为:
|
||||
|
||||
- LangBot 提供通用宿主能力:上下文、资源授权、模型/工具/知识库调用代理、结果归一。
|
||||
- 插件消费这些能力,实现具体 runner 行为。
|
||||
- LangBot 提供通用宿主能力:当前事件、context handles、资源授权、状态/存储、历史、artifact、模型/工具/知识库调用代理、结果归一。
|
||||
- 插件消费这些公开能力,实现具体 runner 行为。
|
||||
- LangBot 默认不把全量历史消息 inline 给 runner;runner 按需通过授权 API 拉取历史和 artifact。
|
||||
- 旧内置 runner 只作为行为对齐的基准,不作为长期运行路径。
|
||||
|
||||
## 2. 仓库结构
|
||||
@@ -102,7 +111,6 @@ metadata:
|
||||
en_US: Run a Dify application as a LangBot AgentRunner.
|
||||
zh_Hans: 将 Dify 应用作为 LangBot AgentRunner 运行。
|
||||
spec:
|
||||
protocol_version: "1"
|
||||
config: []
|
||||
capabilities:
|
||||
streaming: true
|
||||
@@ -126,13 +134,19 @@ execution:
|
||||
attr: DefaultAgentRunner
|
||||
```
|
||||
|
||||
## 6. local-agent 插件要求
|
||||
## 6. local-agent 插件方向
|
||||
|
||||
`local-agent` 是最关键的官方插件,应等价迁移当前:
|
||||
`local-agent` 是官方插件中的重要消费者,但不是宿主协议的设计中心。它可以选择复用
|
||||
旧实现,也可以完全重写。它需要证明:一个主要依附 LangBot host 能力的 agent runner
|
||||
可以通过公开协议完成模型、工具、知识库、状态、history、artifact、上下文压缩和消息投递。
|
||||
|
||||
LangBot core 不应为了 local-agent 保留业务编排逻辑。local-agent 的 prompt 组装、history
|
||||
拉取、summary/checkpoint、tool loop、RAG 编排、fallback、多模态处理都应在插件内完成。
|
||||
|
||||
迁移或重写时需要覆盖旧内置 runner 的用户可见能力:
|
||||
|
||||
- model primary/fallback 选择
|
||||
- prompt
|
||||
- max-round
|
||||
- knowledge-bases
|
||||
- rerank-model
|
||||
- rerank-top-k
|
||||
@@ -144,17 +158,43 @@ execution:
|
||||
|
||||
与 LangBot 主仓库的责任边界:
|
||||
|
||||
- LangBot 构造 `ctx.prompt`、`ctx.messages`、`ctx.input`、`ctx.resources`
|
||||
- LangBot 构造当前事件、结构化输入、资源授权、context handles、state/storage 能力和 delivery 能力
|
||||
- LangBot 不默认 inline 全量历史,不替插件组装最终模型上下文
|
||||
- 插件负责选择模型、拼请求、调用 LLM、处理 tool call loop、输出 result stream
|
||||
- 插件不能绕过 `ctx.resources` 调用未授权模型、工具或知识库
|
||||
|
||||
为了保持旧内置 runner 行为,`local-agent` 插件必须优先消费宿主处理后的有效上下文:
|
||||
为了保持旧内置 runner 的用户可见行为,`local-agent` 插件应消费宿主处理后的有效输入和
|
||||
受限 API,而不是读取宿主内部私有结构:
|
||||
|
||||
- `ctx.prompt`:PreProcessor 和 `PromptPreProcessing` 插件事件处理后的有效 prompt;不是静态 `ctx.config["prompt"]` 的同义词。
|
||||
- `ctx.messages`:已由宿主加载并经过 prompt preprocessing 的历史消息。
|
||||
- `ctx.input.contents`:当前结构化输入,必须保留图片、文件等多模态内容。
|
||||
- `ctx.event` / `ctx.input`:当前结构化输入,必须保留图片、文件等多模态内容。
|
||||
- `ctx.context`:history cursor、inline policy、可用 context API。
|
||||
- `AgentRunAPIProxy.history`:按需读取 transcript,而不是依赖 host 每轮强塞历史窗口。
|
||||
- `AgentRunAPIProxy.artifacts`:按需读取图片、文件、工具大结果。
|
||||
- `AgentRunAPIProxy.state` / storage:保存 summary、外部 conversation id、用户偏好等可选状态。
|
||||
- `ctx.resources`:已授权模型、工具、知识库、文件和 storage。
|
||||
- `ctx.runtime.metadata.streaming_supported`:当前 adapter 是否能消费流式输出。
|
||||
- 宿主代理 action:模型、工具、知识库、rerank 调用应通过 `run_id/query_id` 找回当前 Query,以复用旧 runner 拥有的上下文能力。
|
||||
- 宿主代理 action:模型、工具、知识库、rerank 调用必须通过 `run_id` 校验资源权限。
|
||||
|
||||
旧 `max-round` 只能作为历史配置迁移输入。如果需要兼容旧 Pipeline 行为,可以把它转成
|
||||
local-agent 插件自己的 bootstrap/history policy;不要把它继续提升为 LangBot host 的目标协议。
|
||||
|
||||
建议 local-agent manifest 使用 hybrid 或 self-managed context:
|
||||
|
||||
```yaml
|
||||
context:
|
||||
ownership: hybrid
|
||||
bootstrap: current_event
|
||||
max_inline_events: 0
|
||||
max_inline_bytes: 0
|
||||
supports_history_pull: true
|
||||
supports_history_search: true
|
||||
supports_artifact_pull: true
|
||||
owns_compaction: true
|
||||
wants_static_context_refs: true
|
||||
```
|
||||
|
||||
这表示:LangBot 只给当前事件和 context handles;local-agent 自己决定是否拉取历史、是否搜索、
|
||||
何时摘要、如何构造最终 prompt。
|
||||
|
||||
## 7. 外部 runner 插件要求
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
Phase 1 的目标是让当前聊天 Pipeline 在选择插件化 AgentRunner 后,用户可感知行为与旧内置 runner 保持一致。Phase 2/EBA 不纳入本轮验收。
|
||||
|
||||
本文档是当前分支兼容性验收矩阵,不代表目标架构边界。目标协议以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) 为准:Pipeline 是兼容入口,`messages` 只是 optional bootstrap,LangBot 不默认 inline 全量历史。
|
||||
|
||||
## 1. 验收边界
|
||||
|
||||
本轮必须验收:
|
||||
@@ -87,7 +89,7 @@ Host 侧 agent runner 单测不通过时,不应进入 UI parity QA。
|
||||
| --- | --- | --- | --- |
|
||||
| P1-LA-01 | 普通文本对话 | 绑定 `plugin:langbot/local-agent/default`,发送普通文本。 | 回复正常生成;conversation history 写入用户消息和助手消息。 |
|
||||
| P1-LA-02 | 有效 prompt | 配置 system prompt,并通过 PromptPreProcessing 插件或现有预处理改变 prompt。 | runner 使用 host 处理后的 `ctx.prompt`,不是只读取静态 `ctx.config.prompt`;回复体现有效 prompt。 |
|
||||
| P1-LA-03 | 历史消息 | 连续多轮对话,第二轮引用第一轮内容。 | runner 可读到历史 `ctx.messages`;第二轮能基于上下文回答。 |
|
||||
| P1-LA-03 | 历史消息 | 连续多轮对话,第二轮引用第一轮内容。 | 当前兼容路径下 runner 能读到 host 下发的 bootstrap/history;目标协议下应通过 history API 或插件自管上下文实现。第二轮能基于上下文回答。 |
|
||||
| P1-LA-04 | 流式输出 | 使用支持流式的 adapter/WebUI,开启流式模型或流式 runner。 | UI 逐步更新;后端接收 `message.delta`;最终没有重复消息或空白卡片。 |
|
||||
| P1-LA-05 | 非流式输出 | 使用不支持流式或关闭流式的路径。 | 只输出最终消息;不会创建异常流式卡片。 |
|
||||
| P1-LA-06 | 工具调用 | 绑定一个可调用工具,提问触发工具。 | `ctx.resources.tools` 只包含授权工具;runner 能获取工具详情并调用;最终回复包含工具结果。 |
|
||||
|
||||
665
docs/agent-runner-pluginization/PROTOCOL_V1.md
Normal file
665
docs/agent-runner-pluginization/PROTOCOL_V1.md
Normal file
@@ -0,0 +1,665 @@
|
||||
# LangBot AgentRunner Protocol v1
|
||||
|
||||
本文档定义 LangBot Host 与插件 SDK / Runtime / AgentRunner 之间的协议合同。它优先描述“稳定接口应是什么”,不描述具体落地任务。
|
||||
|
||||
当前分支已有 Protocol v1 的早期实现,但仍带有 Query、Pipeline、`max-round` 等兼容语义。本文档定义的是目标 v1 合同,用于后续同步改造 LangBot 和 SDK。
|
||||
|
||||
## 1. 协议目标
|
||||
|
||||
Protocol v1 要解决四件事:
|
||||
|
||||
- LangBot 如何发现插件提供的 AgentRunner。
|
||||
- LangBot 如何把一次事件调用封装成 `AgentRunContext`。
|
||||
- AgentRunner 如何以事件流形式返回运行结果。
|
||||
- AgentRunner 如何通过受限 API 访问 LangBot host 能力。
|
||||
|
||||
Protocol v1 不定义:
|
||||
|
||||
- LangBot 内部如何持久化 AgentBinding。
|
||||
- AgentRunner 内部如何组装 prompt、压缩历史、管理 memory。
|
||||
- 官方 local-agent 的具体实现。
|
||||
- Pipeline 的长期配置模型。
|
||||
|
||||
## 2. 参与方
|
||||
|
||||
| 名称 | 职责 |
|
||||
| --- | --- |
|
||||
| LangBot Host | 事件入口、绑定解析、权限、资源、存储、生命周期、结果投递。 |
|
||||
| Plugin Runtime | 加载插件,响应 Host 的 runner discovery 和 run 调用。 |
|
||||
| AgentRunner | 插件提供的 agent 执行组件。 |
|
||||
| AgentRunAPIProxy | AgentRunner 访问 Host 能力的受限 API。 |
|
||||
| AgentBinding | Host 内部的事件到 runner 绑定配置,不直接暴露给 SDK。 |
|
||||
|
||||
`AgentBinding` 只影响 Host 构造出的 `ctx.config`、`ctx.resources`、`ctx.context` 和 `ctx.delivery`。SDK 不需要知道 binding 的持久化形态。
|
||||
|
||||
## 3. Discovery 协议
|
||||
|
||||
### 3.1 LIST_AGENT_RUNNERS
|
||||
|
||||
Host 调用 Plugin Runtime 获取当前插件暴露的 runner 列表。该请求不需要额外 payload。
|
||||
|
||||
Runtime 返回:
|
||||
|
||||
```python
|
||||
class ListAgentRunnersResponse(BaseModel):
|
||||
runners: list[AgentRunnerManifest]
|
||||
```
|
||||
|
||||
### 3.2 AgentRunnerManifest
|
||||
|
||||
```python
|
||||
class AgentRunnerManifest(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
label: I18nObject
|
||||
description: I18nObject | None = None
|
||||
capabilities: AgentRunnerCapabilities
|
||||
permissions: AgentRunnerPermissions
|
||||
context: AgentRunnerContextPolicy
|
||||
config_schema: list[DynamicFormItemSchema] = []
|
||||
metadata: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
字段要求:
|
||||
|
||||
- `id` 必须稳定,推荐 `plugin:author/name/runner`。
|
||||
- `name` 是插件内 runner 名称,例如 `default`。
|
||||
- `config_schema` 只描述绑定配置表单,不代表插件实例状态。
|
||||
- `metadata` 只能放展示、诊断、非稳定扩展信息。
|
||||
|
||||
### 3.3 Capabilities
|
||||
|
||||
```python
|
||||
class AgentRunnerCapabilities(BaseModel):
|
||||
streaming: bool = False
|
||||
tool_calling: bool = False
|
||||
knowledge_retrieval: bool = False
|
||||
multimodal_input: bool = False
|
||||
event_context: bool = True
|
||||
platform_api: bool = False
|
||||
interrupt: bool = False
|
||||
stateful_session: bool = False
|
||||
self_managed_context: bool = True
|
||||
```
|
||||
|
||||
语义:
|
||||
|
||||
- `streaming`: runner 可以返回 `message.delta`。
|
||||
- `tool_calling`: runner 可能调用 Host tool APIs。
|
||||
- `knowledge_retrieval`: runner 可能调用 Host knowledge APIs。
|
||||
- `multimodal_input`: runner 可以处理非纯文本 input / artifact。
|
||||
- `event_context`: runner 理解 event-first 输入。
|
||||
- `platform_api`: runner 可能请求平台动作。
|
||||
- `interrupt`: runner 支持取消或中断。
|
||||
- `stateful_session`: runner 可能维护跨 run 会话状态。
|
||||
- `self_managed_context`: runner 自己管理 working context,Host 不应默认 inline 历史。
|
||||
|
||||
### 3.4 Permissions
|
||||
|
||||
```python
|
||||
class AgentRunnerPermissions(BaseModel):
|
||||
models: list[Literal["invoke", "stream", "rerank"]] = []
|
||||
tools: list[Literal["detail", "call"]] = []
|
||||
knowledge_bases: list[Literal["list", "retrieve"]] = []
|
||||
history: list[Literal["page", "search"]] = []
|
||||
events: list[Literal["get", "page"]] = []
|
||||
artifacts: list[Literal["metadata", "read"]] = []
|
||||
storage: list[Literal["plugin", "workspace", "binding"]] = []
|
||||
platform_api: list[str] = []
|
||||
```
|
||||
|
||||
Manifest permissions 是 runner 需要的最大能力。实际可用资源还要经过 Host binding policy 和当前 run scope 裁剪。
|
||||
|
||||
### 3.5 Context Policy
|
||||
|
||||
```python
|
||||
class AgentRunnerContextPolicy(BaseModel):
|
||||
ownership: Literal["self_managed", "host_bootstrap", "hybrid"] = "self_managed"
|
||||
bootstrap: Literal["none", "current_event", "recent_tail", "summary_tail"] = "current_event"
|
||||
max_inline_events: int = 0
|
||||
max_inline_bytes: int = 0
|
||||
supports_history_pull: bool = True
|
||||
supports_history_search: bool = False
|
||||
supports_artifact_pull: bool = True
|
||||
owns_compaction: bool = True
|
||||
wants_static_context_refs: bool = True
|
||||
```
|
||||
|
||||
Host 使用该声明决定是否给 runner inline bootstrap history。默认原则:
|
||||
|
||||
- Host 不得默认 inline 全量历史。
|
||||
- Host 默认只 inline 当前 event / input 和 context handles。
|
||||
- Runner 拥有 working context assembly。
|
||||
- Runner 可在授权后通过 Host history / event / artifact / state APIs 拉取更多上下文。
|
||||
- `max-round` 不属于 Protocol v1 字段。
|
||||
|
||||
## 4. Run 协议
|
||||
|
||||
### 4.1 RUN_AGENT
|
||||
|
||||
Host 调用 Runtime:
|
||||
|
||||
```python
|
||||
class AgentRunRequest(BaseModel):
|
||||
runner_id: str
|
||||
runner_name: str
|
||||
context: AgentRunContext
|
||||
```
|
||||
|
||||
Runtime 返回 `AgentRunResult` 异步流。
|
||||
|
||||
插件运行时可以继续在底层 transport 中使用 `plugin_author`、`plugin_name`、`runner_name` 定位组件,但协议语义以 `runner_id` 和 `context` 为准。
|
||||
|
||||
### 4.2 AgentRunContext
|
||||
|
||||
```python
|
||||
class AgentRunContext(BaseModel):
|
||||
run_id: str
|
||||
trigger: AgentTrigger
|
||||
event: AgentEventContext
|
||||
conversation: ConversationContext | None = None
|
||||
actor: ActorContext | None = None
|
||||
subject: SubjectContext | None = None
|
||||
input: AgentInput
|
||||
delivery: DeliveryContext
|
||||
resources: AgentResources
|
||||
context: ContextAccess
|
||||
state: AgentRunState
|
||||
runtime: AgentRuntimeContext
|
||||
config: dict[str, Any] = {}
|
||||
bootstrap: BootstrapContext | None = None
|
||||
compatibility: CompatibilityContext | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
核心约束:
|
||||
|
||||
- `event` 是必选字段,Protocol v1 是 event-first。
|
||||
- `input` 表示当前事件的主输入,不等于历史消息。
|
||||
- `bootstrap` 是可选字段,不是完整 history。
|
||||
- `compatibility` 只放旧 Query / Pipeline 迁移字段,runner 不应依赖它做长期能力。
|
||||
- `config` 是 Host binding config,不是插件实例状态。
|
||||
|
||||
### 4.3 AgentTrigger
|
||||
|
||||
```python
|
||||
class AgentTrigger(BaseModel):
|
||||
type: str
|
||||
source: Literal["platform", "webui", "api", "scheduler", "system", "pipeline_compat"]
|
||||
timestamp: int | None = None
|
||||
```
|
||||
|
||||
`trigger.type` 应与 `event.event_type` 一致或更粗粒度。例如 Pipeline 兼容入口触发消息时:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "message.received",
|
||||
"source": "pipeline_compat"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 AgentEventContext
|
||||
|
||||
```python
|
||||
class AgentEventContext(BaseModel):
|
||||
event_id: str
|
||||
event_type: str
|
||||
event_time: int | None = None
|
||||
source: str
|
||||
source_event_type: str | None = None
|
||||
raw_ref: RawEventRef | None = None
|
||||
data: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- `event_type` 使用 LangBot 稳定协议名,例如 `message.received`。
|
||||
- 平台原始事件名放入 `source_event_type`。
|
||||
- 大型原始 payload 必须放入 `raw_ref` 或 artifact,不应直接塞入 `data`。
|
||||
|
||||
### 4.5 Actor / Subject / Conversation
|
||||
|
||||
```python
|
||||
class ConversationContext(BaseModel):
|
||||
conversation_id: str | None = None
|
||||
thread_id: str | None = None
|
||||
launcher_type: str | None = None
|
||||
launcher_id: str | None = None
|
||||
bot_id: str | None = None
|
||||
workspace_id: str | None = None
|
||||
|
||||
class ActorContext(BaseModel):
|
||||
actor_type: str
|
||||
actor_id: str | None = None
|
||||
actor_name: str | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
|
||||
class SubjectContext(BaseModel):
|
||||
subject_type: str
|
||||
subject_id: str | None = None
|
||||
data: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
- 消息事件:actor 是发消息的人,subject 是当前消息。
|
||||
- 入群事件:actor 是新成员或邀请人,subject 是群/成员关系。
|
||||
- 定时事件:actor 可以是 system,subject 是 schedule。
|
||||
|
||||
### 4.6 AgentInput
|
||||
|
||||
```python
|
||||
class AgentInput(BaseModel):
|
||||
text: str | None = None
|
||||
contents: list[ContentElement] = []
|
||||
attachments: list[ArtifactRef] = []
|
||||
message_chain: dict[str, Any] | None = None
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 文本、多模态、附件都属于当前 event input。
|
||||
- 大文件、图片、音频、工具大结果应以 artifact ref 传递。
|
||||
- `message_chain` 是平台兼容字段,不应成为长期稳定依赖。
|
||||
|
||||
### 4.7 DeliveryContext
|
||||
|
||||
```python
|
||||
class DeliveryContext(BaseModel):
|
||||
surface: str
|
||||
reply_target: dict[str, Any] | None = None
|
||||
supports_streaming: bool = False
|
||||
supports_edit: bool = False
|
||||
supports_reaction: bool = False
|
||||
max_message_size: int | None = None
|
||||
platform_capabilities: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
Runner 可以参考 delivery 能力决定返回 `message.delta`、`message.completed` 或 `action.requested`。
|
||||
|
||||
### 4.8 ContextAccess
|
||||
|
||||
```python
|
||||
class ContextAccess(BaseModel):
|
||||
conversation_id: str | None = None
|
||||
thread_id: str | None = None
|
||||
latest_cursor: str | None = None
|
||||
event_seq: int | None = None
|
||||
transcript_seq: int | None = None
|
||||
has_history_before: bool = False
|
||||
inline_policy: InlineContextPolicy
|
||||
available_apis: ContextAPICapabilities
|
||||
```
|
||||
|
||||
`ContextAccess` 告诉 runner:Host inline 了什么、没有 inline 什么、如果需要更多上下文应该通过哪些 API 拉取。
|
||||
它不是 Host 的业务上下文编排策略,而是 runner 按需读取上下文的入口说明。
|
||||
|
||||
```python
|
||||
class InlineContextPolicy(BaseModel):
|
||||
mode: Literal["none", "current_event", "recent_tail", "summary_tail"]
|
||||
delivered_count: int = 0
|
||||
source_total_count: int | None = None
|
||||
messages_complete: bool = False
|
||||
reason: str | None = None
|
||||
|
||||
class ContextAPICapabilities(BaseModel):
|
||||
history_page: bool = False
|
||||
history_search: bool = False
|
||||
event_get: bool = False
|
||||
event_page: bool = False
|
||||
artifact_metadata: bool = False
|
||||
artifact_read: bool = False
|
||||
state: bool = False
|
||||
storage: bool = False
|
||||
```
|
||||
|
||||
### 4.9 BootstrapContext
|
||||
|
||||
```python
|
||||
class BootstrapContext(BaseModel):
|
||||
messages: list[Message] = []
|
||||
summary: str | None = None
|
||||
artifacts: list[ArtifactRef] = []
|
||||
metadata: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `bootstrap.messages` 是 host convenience,不是协议核心。
|
||||
- 自管 context runner 默认应收到空 bootstrap 或只收到当前 event。
|
||||
- Host 不应为了“帮 agent 更聪明”而自动拼接完整 transcript。
|
||||
- 旧 `max-round` 只能影响兼容 adapter 如何生成 `bootstrap.messages`,不能成为 Protocol v1 字段。
|
||||
|
||||
### 4.10 RuntimeContext
|
||||
|
||||
```python
|
||||
class AgentRuntimeContext(BaseModel):
|
||||
host: str = "langbot"
|
||||
langbot_version: str | None = None
|
||||
trace_id: str
|
||||
deadline_at: float | None = None
|
||||
locale: str | None = None
|
||||
timezone: str | None = None
|
||||
static_refs: dict[str, StaticContextRef] = {}
|
||||
metadata: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
`static_refs` 用于 KV cache 友好的静态上下文引用,例如 system policy、tool schema、resource manifest 的 hash/version。
|
||||
|
||||
### 4.11 State
|
||||
|
||||
```python
|
||||
class AgentRunState(BaseModel):
|
||||
conversation: dict[str, Any] = {}
|
||||
actor: dict[str, Any] = {}
|
||||
subject: dict[str, Any] = {}
|
||||
runner: dict[str, Any] = {}
|
||||
binding: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
State 是可选 host-owned snapshot。Runner 也可以完全自管状态。
|
||||
|
||||
## 5. Resources
|
||||
|
||||
```python
|
||||
class AgentResources(BaseModel):
|
||||
models: list[ModelResource] = []
|
||||
tools: list[ToolResource] = []
|
||||
knowledge_bases: list[KnowledgeBaseResource] = []
|
||||
artifacts: list[ArtifactResource] = []
|
||||
storage: StorageResource = StorageResource()
|
||||
history: HistoryResource = HistoryResource()
|
||||
platform_capabilities: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
资源列表是本次 run 的授权结果。Runner 只能通过 `AgentRunAPIProxy` 访问这些资源。
|
||||
|
||||
## 6. Result Stream
|
||||
|
||||
### 6.1 AgentRunResult
|
||||
|
||||
```python
|
||||
class AgentRunResult(BaseModel):
|
||||
run_id: str
|
||||
type: str
|
||||
data: dict[str, Any] = {}
|
||||
sequence: int | None = None
|
||||
timestamp: int | None = None
|
||||
```
|
||||
|
||||
### 6.2 稳定 result types
|
||||
|
||||
| type | 说明 |
|
||||
| --- | --- |
|
||||
| `message.delta` | 流式消息片段。 |
|
||||
| `message.completed` | 完整消息。 |
|
||||
| `tool.call.started` | runner 开始工具调用的可观测事件。 |
|
||||
| `tool.call.completed` | runner 完成工具调用的可观测事件。 |
|
||||
| `artifact.created` | runner 生成 artifact。 |
|
||||
| `state.updated` | runner 请求更新 host-owned state。 |
|
||||
| `action.requested` | runner 请求 Host 执行平台动作。 |
|
||||
| `run.completed` | run 正常结束。 |
|
||||
| `run.failed` | run 失败。 |
|
||||
|
||||
Host 必须忽略未知 result type 并记录 warning,除非该 type 明确要求强校验。
|
||||
|
||||
### 6.3 message.delta
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "message.delta",
|
||||
"data": {
|
||||
"chunk": {
|
||||
"role": "assistant",
|
||||
"content": "hel"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 message.completed
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "message.completed",
|
||||
"data": {
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "hello"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.5 state.updated
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "state.updated",
|
||||
"data": {
|
||||
"scope": "conversation",
|
||||
"key": "external.session_id",
|
||||
"value": "abc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Host 必须校验 scope、key、value 大小和 JSON 可序列化性。
|
||||
|
||||
### 6.6 action.requested
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "action.requested",
|
||||
"data": {
|
||||
"action": "message.edit",
|
||||
"target": {"message_id": "..."},
|
||||
"payload": {"text": "..."}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Protocol v1 只定义表达方式。Host 是否执行 action 取决于 platform API 能力、binding policy、审批策略和实现阶段。
|
||||
|
||||
## 7. AgentRunAPIProxy
|
||||
|
||||
所有 proxy action 必须携带 `run_id`。Host 必须校验:
|
||||
|
||||
- active run session 存在。
|
||||
- caller plugin identity 匹配。
|
||||
- resource 在本次 `ctx.resources` 中授权。
|
||||
- scope 不越界。
|
||||
- payload size / rate limit / deadline 合法。
|
||||
|
||||
### 7.1 Model APIs
|
||||
|
||||
```python
|
||||
await api.models.invoke(model_id, messages, tools=None, extra_args=None)
|
||||
await api.models.stream(model_id, messages, tools=None, extra_args=None)
|
||||
await api.models.rerank(model_id, query, documents, top_k=None)
|
||||
```
|
||||
|
||||
### 7.2 Tool APIs
|
||||
|
||||
```python
|
||||
await api.tools.get_detail(tool_name)
|
||||
await api.tools.call(tool_name, parameters)
|
||||
```
|
||||
|
||||
### 7.3 Knowledge APIs
|
||||
|
||||
```python
|
||||
await api.knowledge.retrieve(kb_id, query_text, top_k=5, filters=None)
|
||||
```
|
||||
|
||||
### 7.4 History APIs
|
||||
|
||||
```python
|
||||
await api.history.page(
|
||||
conversation_id=None,
|
||||
before_cursor=None,
|
||||
after_cursor=None,
|
||||
limit=50,
|
||||
direction="backward",
|
||||
include_artifacts=False,
|
||||
)
|
||||
|
||||
await api.history.search(
|
||||
query,
|
||||
filters=None,
|
||||
top_k=10,
|
||||
)
|
||||
```
|
||||
|
||||
History API 返回 Transcript projection,不返回原始平台 payload。
|
||||
|
||||
### 7.5 Event APIs
|
||||
|
||||
```python
|
||||
await api.events.get(event_id)
|
||||
await api.events.page(before_cursor=None, limit=50)
|
||||
```
|
||||
|
||||
Event API 返回稳定 event envelope 或受限 raw ref,不默认返回大 payload。
|
||||
|
||||
### 7.6 Artifact APIs
|
||||
|
||||
```python
|
||||
await api.artifacts.metadata(artifact_id)
|
||||
await api.artifacts.read_range(artifact_id, offset=0, length=65536)
|
||||
await api.artifacts.open_stream(artifact_id)
|
||||
```
|
||||
|
||||
Artifact API 必须支持大小限制、MIME 校验、过期时间和授权范围。
|
||||
|
||||
### 7.7 State / Storage APIs
|
||||
|
||||
```python
|
||||
await api.state.get(scope, key)
|
||||
await api.state.set(scope, key, value)
|
||||
await api.state.delete(scope, key)
|
||||
|
||||
await api.storage.get(area, key)
|
||||
await api.storage.set(area, key, value)
|
||||
await api.storage.delete(area, key)
|
||||
await api.storage.list(area, prefix=None)
|
||||
```
|
||||
|
||||
建议区分:
|
||||
|
||||
- `state`: 小型 JSON 状态,适合 conversation / actor / runner / binding。
|
||||
- `storage`: blob 或较大数据,适合插件私有数据、workspace 数据、checkpoint。
|
||||
|
||||
### 7.8 Platform APIs
|
||||
|
||||
```python
|
||||
await api.platform.request_action(action, target, payload)
|
||||
```
|
||||
|
||||
平台 API 是受限能力。默认不开放。需要 runner manifest、binding policy、用户审批策略同时允许。
|
||||
|
||||
## 8. 错误模型
|
||||
|
||||
Host API 错误统一返回:
|
||||
|
||||
```python
|
||||
class AgentAPIError(BaseModel):
|
||||
code: str
|
||||
message: str
|
||||
retryable: bool = False
|
||||
details: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
建议 code:
|
||||
|
||||
| code | 说明 |
|
||||
| --- | --- |
|
||||
| `unauthorized` | 未授权访问资源或 scope。 |
|
||||
| `not_found` | 资源不存在或对当前 runner 不可见。 |
|
||||
| `deadline_exceeded` | 超过 run deadline。 |
|
||||
| `payload_too_large` | 请求或响应过大。 |
|
||||
| `rate_limited` | Host 限流。 |
|
||||
| `invalid_argument` | 参数错误。 |
|
||||
| `runtime_error` | Host 或下游能力错误。 |
|
||||
|
||||
Runner 失败使用 `run.failed`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "run.failed",
|
||||
"data": {
|
||||
"code": "runner.error",
|
||||
"message": "failed to call external agent",
|
||||
"retryable": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Timeout 与 Cancellation
|
||||
|
||||
Host 在 `ctx.runtime.deadline_at` 中下发总 deadline。SDK proxy 必须用该 deadline 限制单次 action timeout。
|
||||
|
||||
取消语义:
|
||||
|
||||
- Host 可以取消 active run。
|
||||
- Runtime 应尽力中断 runner。
|
||||
- Runner 支持中断时应返回或触发 `run.failed`,code 为 `cancelled`。
|
||||
- Host 必须 unregister active run session。
|
||||
|
||||
## 10. Security 与 Guardrail
|
||||
|
||||
Protocol v1 的安全边界在 Host:
|
||||
|
||||
- Runner 不能直接访问未授权 model/tool/kb/history/artifact/storage。
|
||||
- SDK 本地校验只提升开发体验,不能替代 Host 校验。
|
||||
- 所有 resource id 对 runner 来说都是 opaque。
|
||||
- 默认只能访问当前 conversation / thread 的 history。
|
||||
- 跨会话、workspace 级 history 或 storage 必须额外授权。
|
||||
- 大 payload 必须 artifact 化。
|
||||
- Host 必须记录 run_id、runner_id、action、resource、scope、result。
|
||||
|
||||
Host 不负责业务编排:
|
||||
|
||||
- 不拼接全量历史。
|
||||
- 不替 runner 做业务 prompt assembly。
|
||||
- 不内置 agent memory 策略。
|
||||
- 不内置 tool loop 业务流程。
|
||||
- 不内置上下文压缩策略。
|
||||
|
||||
这些能力可以由官方或第三方 AgentRunner 插件实现,并通过公开 Host APIs 消费 LangBot 的状态、历史、存储、artifact、模型、工具和知识库能力。
|
||||
|
||||
## 11. Pipeline 兼容
|
||||
|
||||
Pipeline 是兼容入口,不是协议中心。
|
||||
|
||||
兼容 adapter 应负责:
|
||||
|
||||
- 从 `Query` 构造 `AgentEventContext`。
|
||||
- 从 Pipeline config 构造临时 AgentBinding。
|
||||
- 从旧 runner config 构造 `ctx.config`。
|
||||
- 将旧 `max-round` 转换为 `bootstrap` policy。
|
||||
- 将旧 Query-only 字段放入 `compatibility`。
|
||||
|
||||
Runner 不应长期依赖 `compatibility`。新 runner 应只依赖 event-first context 和 Host APIs。
|
||||
|
||||
## 12. 最小 v1 完成标准
|
||||
|
||||
Protocol v1 可认为稳定,至少需要:
|
||||
|
||||
- SDK 定义 `AgentRunnerManifest`、`AgentRunContext`、`AgentRunResult`、`AgentRunAPIProxy`。
|
||||
- Runtime 支持 `LIST_AGENT_RUNNERS` 和 `RUN_AGENT`。
|
||||
- Host 支持 `run_id` session authorization。
|
||||
- Host 能从当前 Pipeline 入口生成 event-first context。
|
||||
- `messages` 降级为 optional bootstrap。
|
||||
- `max-round` 不出现在协议实体中。
|
||||
- Proxy 至少覆盖 model、tool、knowledge、state/storage。
|
||||
- History / event / artifact API 的方法签名确定,即使实现可以分阶段落地。
|
||||
|
||||
## 13. 开放问题
|
||||
|
||||
- `AgentBinding` 是否需要进入 SDK 文档作为只读诊断信息,还是完全 Host 内部。
|
||||
- `TranscriptItem` 的最小字段集如何定义。
|
||||
- ArtifactStore 是否复用现有 BinaryStorage backend,还是引入独立实体。
|
||||
- State 与 Storage 的边界是否需要更强类型。
|
||||
- `platform_api` action 的审批模型如何表达。
|
||||
- 多 runner 并发处理同一 event 时,result delivery 的冲突策略如何定义。
|
||||
@@ -1,535 +1,93 @@
|
||||
# Agent Runner 插件化设计
|
||||
# Agent Runner 插件化文档入口
|
||||
|
||||
## 1. 背景
|
||||
本文档是 agent-runner 插件化工作的路由页。具体设计拆到独立文档中维护,避免把 LangBot 宿主架构、SDK 协议、上下文管理、EBA 预留和官方 runner 迁移混在同一份 README 里。
|
||||
|
||||
当前 `feat/agent-runner-plugin` 分支已经验证了一个最小路径:SDK 增加 `AgentRunner` 组件,LangBot 在 Pipeline 的 `runner` 配置项中动态列出插件提供的 runner,并在 `ChatMessageHandler` 中通过 `plugin_connector.run_agent()` 调用插件实现。
|
||||
## 核心方向
|
||||
|
||||
这个方向能把内置 `RequestRunner` 之外的 Agent 实现放到插件中,但它仍然沿用“私聊/群聊消息进入 Pipeline,再由 runner 产出回复”的旧模型。它没有解决后续 Agent 需要面对复杂上下文的问题,也没有为 EBA 计划里的事件驱动能力留下足够清晰的扩展面。
|
||||
LangBot 的目标不是把旧 Pipeline runner 机制简单搬进插件系统,而是逐步转为一个面向 Agent 的宿主层:
|
||||
|
||||
本设计只聚焦 Agent Runner 插件化。EBA 文档中的事件体系、平台 API、事件路由只作为接口预留和未来兼容参考,不纳入本阶段实现范围。
|
||||
- LangBot 负责 IM / WebUI / API / 未来事件入口、会话和身份解析、权限、存储、资源授权、运行生命周期、结果投递和审计。
|
||||
- SDK 负责定义 AgentRunner 组件协议、上下文实体、返回事件流、运行期受限 API 和插件 runtime 协作方式。
|
||||
- AgentRunner 负责具体 agent runtime 的上下文策略、prompt 组装、压缩、召回、模型调用策略和业务行为。
|
||||
|
||||
## 1.1 当前实现状态
|
||||
后续会逐步弱化 Pipeline。当前 Pipeline 只能视为现有消息入口和兼容层,不应作为新架构设计的中心假设。
|
||||
|
||||
当前实现已经不是早期 PoC:
|
||||
## 设计文档
|
||||
|
||||
| 文档 | 关注点 |
|
||||
| --- | --- |
|
||||
| [PROTOCOL_V1.md](./PROTOCOL_V1.md) | LangBot Host 与 SDK / Runtime / AgentRunner 的协议合同:discovery、run context、result stream、proxy actions、错误和兼容边界。 |
|
||||
| [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md) | LangBot 宿主能力、SDK 协议、runner 发现、绑定、权限、状态、存储、生命周期和调用链。 |
|
||||
| [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) | Agent-owned context 方向:事件到来时 LangBot 传什么,agent 如何按需拉取更多历史 / artifact / state,以及如何支持 KV cache 友好的上下文管理。 |
|
||||
| [EVENT_BASED_AGENT.md](./EVENT_BASED_AGENT.md) | EBA 预留:事件模型、事件来源、触发绑定、非消息事件如何复用 AgentRunner 调度。 |
|
||||
| [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md) | 官方 runner 插件迁移,包括 local-agent 和外部 runner。它是下游落地计划,不是 LangBot 基础能力设计的前置约束。 |
|
||||
| [PHASE1_QA_ACCEPTANCE_MATRIX.md](./PHASE1_QA_ACCEPTANCE_MATRIX.md) | 当前阶段的 QA 验收矩阵。它验证现有分支的兼容性,不代表最终架构边界。 |
|
||||
|
||||
## 工作拆分
|
||||
|
||||
### 1. LangBot + SDK 基础设施
|
||||
|
||||
目标是把 LangBot 从内置 runner 执行器变成 agent host:
|
||||
|
||||
- LangBot 与 SDK 的稳定协议合同
|
||||
- runner manifest / descriptor / registry
|
||||
- agent binding 与配置解析
|
||||
- run orchestration 和生命周期管理
|
||||
- resource authorization 与 `run_id` 级权限校验
|
||||
- host-owned state / storage / event log / transcript / artifact 能力
|
||||
- SDK `AgentRunner`、`AgentRunContext`、`AgentRunResult`、`AgentRunAPIProxy`
|
||||
|
||||
协议合同详见 [PROTOCOL_V1.md](./PROTOCOL_V1.md)。
|
||||
|
||||
详见 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md)。
|
||||
|
||||
### 2. Agent-owned context
|
||||
|
||||
LangBot 不应成为最终 agentic context manager。它应提供事实源、默认上下文引用和按需读取 API;agent 或其背后的 runtime 负责历史剪裁、摘要、召回和 KV cache 策略。
|
||||
|
||||
当前代码中的 legacy `max-round` 只能视为旧 Pipeline 兼容行为,不应作为目标协议继续扩展。
|
||||
|
||||
详见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。
|
||||
|
||||
### 3. Event Based Agent
|
||||
|
||||
消息只是事件的一种。后续 `message.received`、`message.recalled`、`group.member_joined`、`friend.request_received` 等事件都应能通过统一事件 envelope 触发 AgentRunner。
|
||||
|
||||
EBA 设计要复用同一套 runner registry、resource authorization、session registry、state 更新、result normalization 和 delivery lifecycle,不能另起一套调用协议。
|
||||
|
||||
详见 [EVENT_BASED_AGENT.md](./EVENT_BASED_AGENT.md)。
|
||||
|
||||
### 4. 官方 runner 插件
|
||||
|
||||
官方 `local-agent` 和外部 runner 迁移是下游工作。它们需要依附 LangBot 提供的宿主能力,但不应反过来决定宿主协议。
|
||||
|
||||
`local-agent` 可以外移,也可以重写。验收重点是它能完整消费 LangBot 的模型、工具、知识库、存储、事件、history API 和 result stream,而不是保留旧内置 runner 的内部结构。
|
||||
|
||||
详见 [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md)。
|
||||
|
||||
## 当前实现状态
|
||||
|
||||
当前分支已经具备一部分基础设施:
|
||||
|
||||
- LangBot 已有 `AgentRunnerRegistry`、`AgentRunOrchestrator`、`AgentRunContextBuilder`、`AgentResourceBuilder`、`AgentResultNormalizer`。
|
||||
- `ChatMessageHandler` 主路径已经委托给 orchestrator,不再直接解析插件 runner 或实例化 wrapper。
|
||||
- Pipeline metadata 已经从 registry 动态生成插件 runner 选项和配置 stage。
|
||||
- `ChatMessageHandler` 主路径已经委托 orchestrator。
|
||||
- Pipeline metadata 已经能从 registry 动态生成 runner 选项和配置 stage。
|
||||
- SDK 已有 Protocol v1 的 `AgentRunContext`、`AgentRunResult`、capabilities、permissions、`AgentRunAPIProxy`。
|
||||
- 旧 `RequestRunner` 文件仍保留,当前作为迁移基准和回退分析材料;最终 parity 完成后再移除或隔离。
|
||||
- 宿主侧已有 `run_id` session registry,用于模型、工具、知识库、storage 等 runtime action 的授权校验。
|
||||
|
||||
当前仍在收尾的重点不是“能不能调用插件 runner”,而是:
|
||||
仍需要从当前实现中继续剥离的部分:
|
||||
|
||||
- 宿主侧通用能力是否足够,让插件 runner 获得旧内置 runner 隐式拥有的上下文。
|
||||
- `local-agent` 官方插件是否能在对外行为上对齐旧内置 local-agent。
|
||||
- 权限裁剪、timeout、错误隔离和端到端 parity 测试是否完整。
|
||||
- Pipeline 绑定仍是当前主要入口,后续需要抽象为通用 `AgentBinding`。
|
||||
- `AgentRunContext` 仍带有旧 Query / Pipeline 语义,需要迁移到 event-first envelope。
|
||||
- context packaging 仍受 legacy `max-round` 影响,后续应改为 context reference + pull API。
|
||||
- state store 当前是进程内实现,需要明确 host storage backend。
|
||||
- artifact / transcript / event log 还没有成为完整宿主能力。
|
||||
|
||||
## 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. 当前实现剩余问题
|
||||
|
||||
以下是当前实现仍需要收敛的点:
|
||||
|
||||
- `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. 总体架构
|
||||
|
||||
建议引入三层结构:
|
||||
|
||||
```text
|
||||
Pipeline / future Event Router
|
||||
|
|
||||
v
|
||||
AgentRunnerRegistry
|
||||
| discovers built-in runners and plugin runners
|
||||
v
|
||||
AgentRunOrchestrator
|
||||
| resolves binding, provisions context/resources/state, 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、权限声明、协议版本。
|
||||
- 提供缓存和刷新机制,插件安装、卸载、重启后刷新。
|
||||
|
||||
建议结构:
|
||||
|
||||
```python
|
||||
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。
|
||||
- 编排 `ContextBuilder` / `ResourceBuilder` 生成 SDK `AgentRunContext` envelope 与已授权资源。
|
||||
- 注册本次运行的 `run_id` / runner / resource scope,供后续 `AgentRunAPIProxy` 做权限校验。
|
||||
- 统一处理超时、异常、流式返回、取消、中断和 telemetry。
|
||||
- 将插件返回的 `AgentRunResult` 转换回当前 Pipeline 能消费的 `Message` / `MessageChunk`。
|
||||
|
||||
LangBot 当前 `ChatMessageHandler` 里的插件 wrapper 应下沉到 orchestrator,避免消息处理器知道插件 runner 的细节。
|
||||
这里的 “context” 指 Host 提供的协议 envelope、运行身份、资源、状态快照和默认工作窗口,不是 Agent 的最终 prompt 组装或长期记忆策略。最终模型上下文如何压缩、摘要、召回,应由 AgentRunner 声明策略并在 AgentRunner 边界执行;LangBot 负责提供受限的基础设施和 guardrail。
|
||||
|
||||
## 5. SDK 设计
|
||||
|
||||
### 5.1 AgentRunner 组件
|
||||
|
||||
SDK 保留当前分支新增的组件方向,但需要补齐能力声明:
|
||||
|
||||
```python
|
||||
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`
|
||||
|
||||
本阶段可以先实现 `streaming`、`tool_calling`、`knowledge_retrieval` 三项,其他字段只作为声明和预留。
|
||||
|
||||
### 5.2 上下文模型
|
||||
|
||||
当前 `AgentRunContext` 应升级为更通用的运行上下文:
|
||||
|
||||
```python
|
||||
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] = []
|
||||
context_request: AgentContextRequest | None = None
|
||||
context_packaging: ContextPackagingMetadata = ContextPackagingMetadata()
|
||||
input: AgentInput
|
||||
params: dict[str, Any] = {}
|
||||
resources: AgentResources
|
||||
state: AgentRunState = AgentRunState()
|
||||
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` 表示事件作用对象,例如被邀请用户、被撤回消息、被操作群组。
|
||||
- `prompt` 是宿主处理后的有效 prompt。它来自 LangBot 当前 conversation prompt,并且已经过 `PromptPreProcessing` 等插件事件处理;runner 调模型时应优先使用它,而不是重新读取静态 `config["prompt"]`。
|
||||
- `messages` 是历史消息,也已经过宿主 pipeline preprocessing。插件化 AgentRunner 路径不再由 Pipeline `msgtrun` 截断,而是在 AgentRunner context packaging 边界按 legacy max-round 语义裁剪。
|
||||
- `context_request` 是未来 AgentRunner manifest / binding config 提出的上下文偏好,例如 token budget、summary hybrid、external session;它不是 LangBot 单方面的策略开关。
|
||||
- `context_packaging` 描述 Host 本次实际下发的历史窗口,例如使用的策略、来源、已下发消息数、是否确认完整、未来 cursor 等。本阶段只标注 AgentRunner legacy 窗口。
|
||||
- `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 可提供:
|
||||
|
||||
```python
|
||||
ctx.input.to_text()
|
||||
ctx.conversation.to_legacy_session()
|
||||
ctx.to_legacy_query_context()
|
||||
```
|
||||
|
||||
当前代码不改 SDK v1 schema,Host 实际下发结果先作为
|
||||
`ctx.runtime.metadata.context_packaging` 下发;它是 packaging receipt,不是 LangBot 侧的长期策略控制面。
|
||||
|
||||
### 5.2.1 Agentic 上下文与文件协作方向
|
||||
|
||||
本节主要记录后续设计。本轮已把 legacy `max-round` working window 搬到
|
||||
`AgentContextPackager`;LangBot 的完整会话历史仍主要来自进程内 `Conversation.messages`,
|
||||
长期仍需要持久化 store 和压缩机制。
|
||||
|
||||
长期方向应区分三类数据:
|
||||
|
||||
- `ConversationStore` / `EventLog`: LangBot 持久保存完整原始消息、事件、工具调用和结果引用,作为审计、重放、重新压缩和历史检索的事实来源。
|
||||
- `working context`: 每次 `AgentRunner.run()` 收到的受控上下文窗口。它不应是完整历史全文,而应由 `AgentContextPackager` 组装,例如 effective prompt、压缩摘要、最近若干轮、相关历史片段、RAG/tool context 和当前输入。
|
||||
- `context state`: 压缩摘要、`last_compacted_seq`、外部 conversation id、用户偏好等跨轮状态。它由 host-owned state 或授权 storage 持久化,不能放在插件实例内存里。
|
||||
|
||||
因此不要把完整历史全部塞给插件 runner。正确边界是 LangBot host 保留完整历史,
|
||||
AgentRunner 边界下发默认安全窗口;如果 runner 需要更多历史,应通过受限
|
||||
`AgentRunAPIProxy` 按 cursor/page size 请求片段。这样可以避免每轮 O(n) 复制和跨进程
|
||||
序列化,也避免插件 runtime 收到无限膨胀的上下文。
|
||||
|
||||
上下文压缩应在后续 LiteLLM 接入、能够获得模型 context window 后再实现。建议策略是:
|
||||
|
||||
- 每轮 run 前估算 `prompt + summary + recent turns + tool/RAG context + current input` 的 token。
|
||||
- 超过阈值时,对较旧的历史窗口做 compression,生成 summary/checkpoint。
|
||||
- 原始消息不删除;summary 是派生记忆,可以重算和审计。
|
||||
- 下一轮使用 `summary + recent turns + relevant recalled history` 继续工作。
|
||||
- 重启后从持久化 `ConversationStore/EventLog` 和 summary checkpoint 恢复 working context,而不是依赖进程内窗口。
|
||||
|
||||
大文件、多模态和工具产物不应内联进 `ctx.messages`。后续建议统一成 artifact/resource
|
||||
引用:
|
||||
|
||||
- 小文本可以直接进入 message/content;大文件、图片、音频、工具输出文件只在 context 中放
|
||||
`artifact_id`、`mime_type`、`size`、`digest`、摘要和访问权限。
|
||||
- `/tmp` 只适合作为单次 run 的本地临时 staging;不能作为重启后的事实来源。
|
||||
- 长期可复用或跨工具协作的文件应放到 box/object storage。当前分支还没有合并 box 能力,
|
||||
因此本阶段只预留协议,不实现存取。
|
||||
- AgentRunner 通过受限 API 读取 artifact,例如后续的 `get_artifact_metadata()`、
|
||||
`open_artifact_stream()`、`read_artifact_range()`。Host 必须校验 run_id、runner 权限、
|
||||
文件大小、MIME、过期时间和可访问范围。
|
||||
- 工具返回大结果时也应返回 artifact ref + 摘要,而不是把完整结果塞回消息历史。
|
||||
|
||||
EBA 接入后,完整事实来源更适合建成 `EventLog + Projection`:
|
||||
|
||||
- `EventLog` 保存 `message.received`、`tool.call.completed`、`message.recalled`、
|
||||
`group.member_joined` 等原始事件。
|
||||
- `ConversationProjection` 把与对话相关的事件投影成 agent 可读 history。
|
||||
- 非消息事件不必伪造成用户消息;它可以带 `actor`、`subject`、`event_data`,再由
|
||||
`AgentContextPackager` 决定是否纳入 working context。
|
||||
|
||||
### 5.3 返回协议
|
||||
|
||||
当前 `AgentRunReturn.type` 建议规范化为事件流:
|
||||
|
||||
```python
|
||||
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.requested`,LangBot 只记录 telemetry 并忽略执行。
|
||||
|
||||
### 5.4 LangBotAPIProxy
|
||||
|
||||
Agent Runner 插件需要使用 LangBot 能力,但这些能力必须通过显式授权暴露:
|
||||
|
||||
- 模型:`invoke_llm`、`invoke_llm_stream`、rerank、后续 embedding。
|
||||
- 工具:`get_tool_detail`、`call_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_uuid`、`sender_id`、`session_name`
|
||||
|
||||
## 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。
|
||||
|
||||
这里的“绑定配置”不代表插件实例。插件安装后由插件 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 落地后,同一套 `AgentRunnerDescriptor` 和 `AgentRunOrchestrator` 需要支持直接与 Bot 的事件触发器绑定。届时 Bot event handler 可以绕过完整 Pipeline,直接选择某个 Agent Runner 处理 `message.received`、`group.member_joined`、`friend.request_received` 等事件。
|
||||
|
||||
Pipeline AI 配置建议从:
|
||||
|
||||
```json
|
||||
{
|
||||
"runner": {
|
||||
"runner": "local-agent"
|
||||
},
|
||||
"local-agent": {}
|
||||
}
|
||||
```
|
||||
|
||||
演进为:
|
||||
|
||||
```json
|
||||
{
|
||||
"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`。建议路径:
|
||||
|
||||
```text
|
||||
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` 输入:
|
||||
|
||||
```json
|
||||
{
|
||||
"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 增加:
|
||||
|
||||
```yaml
|
||||
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-pluginization`、`AgentRunContext`、`AgentRunResult` 等命名。
|
||||
|
||||
### 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 的最小调用链应是:
|
||||
|
||||
```text
|
||||
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 增加 `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` 强制迁移为官方插件包。
|
||||
- 迁移期间旧 `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.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 正常工作。
|
||||
- 官方 `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](./PHASE1_QA_ACCEPTANCE_MATRIX.md) 作为验收标准。该矩阵只验收 Agent Runner 插件化 parity,不验收 EBA EventBus、EventRouter 或平台动作执行。
|
||||
- 一个插件可以声明多个 `AgentRunner` 组件,每个组件独立暴露 manifest、配置 schema、能力和权限。
|
||||
- 插件本身按单实例、无状态执行单元理解;不同绑定不创建多个插件实例。
|
||||
- 绑定只保存 runner id 和绑定配置,不代表插件实例状态。
|
||||
- LangBot 可以提供 host-owned state / storage 能力,让 runner 把状态寄宿在 LangBot;但这应该是授权能力,不是强制要求。
|
||||
- 官方 runner 插件是协议消费者,不是协议设计的优先约束。
|
||||
- Pipeline 是当前兼容入口,不是未来架构中心。
|
||||
|
||||
Reference in New Issue
Block a user