# 事件路由与编排

> **2026-06 方向修订**：本文档的四种 handler_type（pipeline / agent / webhook / plugin）分类法已被「事件 → Agent」统一编排取代，收编映射与新数据模型见 [07-agent-orchestration.md](./07-agent-orchestration.md)。本文档中的事件匹配规则（§4）、`use_pipeline_uuid` 迁移策略（§6）、WebUI 交互骨架（§7）与 webhook 请求/响应格式（§5.4）仍然有效，将在 Agent 模型下沿用。

## 1. 概述

事件路由是 EBA 架构的核心机制：事件从适配器产生后，经由 EventBus 进入 EventRouter，由 EventRouter 根据 Bot 的配置将事件分发到对应的处理器（Handler）。

**配置方式**：用户在 WebUI 的 Bot 管理页面通过可视化编排面板管理事件处理器配置，配置数据存储在数据库的 Bot 表 `event_handlers` JSON 字段中。

## 2. 数据模型

### 2.1 Bot 实体扩展

在 `bots` 表新增 `event_handlers` 字段：

```python
class Bot(Base):
    __tablename__ = "bots"

    uuid: str               # 主键
    name: str
    description: str
    adapter: str
    adapter_config: dict    # JSON
    enable: bool

    # 新增
    event_handlers: list    # JSON — 事件处理器配置列表

    # 保留（过渡期后弃用）
    use_pipeline_name: str  # deprecated
    use_pipeline_uuid: str  # deprecated

    created_at: datetime
    updated_at: datetime
```

### 2.2 EventHandler 配置结构

`event_handlers` 字段存储一个 JSON 数组，每个元素定义一条事件路由规则：

```python
class EventHandlerConfig(pydantic.BaseModel):
    """单条事件处理器配置"""

    event_type: str
    """匹配的事件类型

    支持精确匹配和通配符：
    - "message.received"       — 精确匹配
    - "message.*"              — 匹配 message 命名空间下所有事件
    - "group.*"                — 匹配 group 命名空间下所有事件
    - "*"                      — 匹配所有事件（兜底）
    """

    handler_type: str
    """处理器类型: "pipeline" | "agent" | "webhook" | "plugin" """

    handler_config: dict = {}
    """处理器的具体配置，结构取决于 handler_type"""

    enabled: bool = True
    """是否启用此规则"""

    priority: int = 0
    """优先级，数字越大越先匹配（同一事件类型有多条规则时）"""

    description: str = ""
    """规则描述（供 WebUI 显示）"""
```

### 2.3 各 Handler 类型的 handler_config 结构

#### pipeline

```json
{
    "handler_type": "pipeline",
    "handler_config": {
        "pipeline_uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
}
```

将事件作为消息事件传入现有 Pipeline 流水线。仅适用于 `message.received` 事件。

#### agent

```json
{
    "handler_type": "agent",
    "handler_config": {
        "runner": "local-agent",
        "runner_config": {
            "model_uuid": "...",
            "prompt": "你是一个群组助理，请处理以下事件：{event_summary}",
            "tools_enabled": true
        }
    }
}
```

```json
{
    "handler_type": "agent",
    "handler_config": {
        "runner": "dify-service-api",
        "runner_config": {
            "base_url": "https://api.dify.ai/v1",
            "api_key": "...",
            "app_type": "agent"
        }
    }
}
```

直接调用 RequestRunner 处理事件。可用的 runner 包括：
- `local-agent` — 内置 LLM Agent
- `dify-service-api` — Dify 平台
- `n8n-service-api` — n8n 工作流
- `coze-api` — Coze (扣子)
- `dashscope-app-api` — 阿里百炼
- `langflow-api` — Langflow
- `tbox-app-api` — 蚂蚁 Tbox

Agent 处理器不经过 Pipeline 的多 Stage 流程，而是直接构建上下文并调用 Runner。适用于所有事件类型。

**Agent Handler 与 Pipeline 的关系**：
- Pipeline 是完整的多 Stage 处理链（PreProcessor → MessageProcessor(内含Runner) → PostProcessor → ...），适合复杂消息处理
- Agent Handler 是轻量级的，直接调用 Runner，跳过 PreProcessor/PostProcessor 等阶段
- Pipeline 内部的 AI Stage 仍然使用 Runner，所以 Runner 本身被两种 Handler 共享
- 用户可以根据场景选择：消息处理用 Pipeline（更多控制），其他事件用 Agent（更直接）

#### webhook

```json
{
    "handler_type": "webhook",
    "handler_config": {
        "url": "https://example.com/webhook/langbot-events",
        "method": "POST",
        "headers": {
            "Authorization": "Bearer xxx"
        },
        "timeout": 30,
        "retry_count": 3,
        "retry_interval": 5,
        "response_actions": true
    }
}
```

将事件序列化为 JSON POST 到外部 URL。支持的特性：
- **认证**：通过 headers 配置（Bearer Token、API Key 等）
- **重试**：配置重试次数和间隔
- **响应动作**：如果 `response_actions` 为 true，解析响应 JSON 中的 `actions` 字段并执行（如发送消息、同意好友请求等）

Webhook 请求体格式：

```json
{
    "event": {
        "type": "group.member_joined",
        "timestamp": 1700000000.0,
        "bot_uuid": "...",
        "adapter_name": "telegram",
        "group": { "id": "...", "name": "..." },
        "member": { "id": "...", "nickname": "..." }
    },
    "bot": {
        "uuid": "...",
        "name": "...",
        "adapter": "telegram"
    }
}
```

响应体格式（当 `response_actions` 为 true 时）：

```json
{
    "actions": [
        {
            "type": "send_message",
            "params": {
                "target_type": "group",
                "target_id": "123456",
                "message": [{ "type": "Plain", "text": "欢迎新成员！" }]
            }
        },
        {
            "type": "call_platform_api",
            "params": {
                "action": "pin_message",
                "params": { "chat_id": "123456", "message_id": "789" }
            }
        }
    ]
}
```

#### plugin

```json
{
    "handler_type": "plugin",
    "handler_config": {
        "plugin_filter": []
    }
}
```

将事件分发给插件的 EventListener 处理。

- `plugin_filter`：可选的插件名过滤列表，为空表示分发给所有插件
- 沿用现有的插件事件分发机制（按优先级遍历插件，支持 `prevent_postorder`）

### 2.4 完整配置示例

一个 Bot 的 `event_handlers` 配置示例：

```json
[
    {
        "event_type": "message.received",
        "handler_type": "pipeline",
        "handler_config": {
            "pipeline_uuid": "default-pipeline-uuid"
        },
        "enabled": true,
        "priority": 10,
        "description": "消息事件使用默认流水线处理"
    },
    {
        "event_type": "group.member_joined",
        "handler_type": "agent",
        "handler_config": {
            "runner": "local-agent",
            "runner_config": {
                "model_uuid": "gpt-4o-mini",
                "prompt": "有新成员 {member_name} 加入了群组 {group_name}，请生成一条欢迎消息。"
            }
        },
        "enabled": true,
        "priority": 0,
        "description": "新成员入群时用 AI 生成欢迎消息"
    },
    {
        "event_type": "friend.request_received",
        "handler_type": "webhook",
        "handler_config": {
            "url": "https://my-server.com/api/friend-request",
            "response_actions": true
        },
        "enabled": true,
        "priority": 0,
        "description": "好友请求转发到自建服务处理"
    },
    {
        "event_type": "*",
        "handler_type": "plugin",
        "handler_config": {},
        "enabled": true,
        "priority": -100,
        "description": "所有事件兜底发给插件处理"
    }
]
```

## 3. EventBus 设计

EventBus 是事件的中转站，接收来自各个 RuntimeBot 的事件，交由 EventRouter 处理。

```python
class EventBus:
    """事件总线"""

    def __init__(self, ap: Application):
        self.ap = ap
        self.event_router = EventRouter(ap)

    async def emit(
        self,
        event: Event,
        adapter: AbstractPlatformAdapter,
    ):
        """接收并分发事件

        Args:
            event: 统一事件对象
            adapter: 产生此事件的适配器实例
        """
        # 1. 全局事件日志
        self.ap.logger.debug(
            f"EventBus: {event.type} from bot {event.bot_uuid}"
        )

        # 2. 交由 EventRouter 路由处理
        await self.event_router.route(event, adapter)
```

## 4. EventRouter 设计

EventRouter 是事件路由引擎，根据 Bot 的 `event_handlers` 配置决定事件的处理方式。

```python
class EventRouter:
    """事件路由引擎"""

    def __init__(self, ap: Application):
        self.ap = ap
        self.handlers: dict[str, AbstractEventHandler] = {
            "pipeline": PipelineHandler(ap),
            "agent": AgentHandler(ap),
            "webhook": WebhookHandler(ap),
            "plugin": PluginHandler(ap),
        }

    async def route(
        self,
        event: Event,
        adapter: AbstractPlatformAdapter,
    ):
        """路由事件到对应处理器"""

        # 1. 获取 Bot 配置
        bot = await self.ap.platform_mgr.get_bot_by_uuid(event.bot_uuid)
        if not bot:
            return

        # 2. 获取事件处理器配置
        event_handlers = bot.bot_entity.event_handlers or []

        # 3. 匹配规则（按 priority 降序排列）
        matched_handlers = self._match_handlers(event.type, event_handlers)

        if not matched_handlers:
            # 未匹配到任何规则 → 默认交给插件处理（向后兼容）
            await self.handlers["plugin"].handle(event, adapter, {})
            return

        # 4. 执行第一个匹配的 Handler
        #    （未来可扩展为多个 Handler 串行/并行执行）
        handler_config = matched_handlers[0]
        handler = self.handlers.get(handler_config.handler_type)

        if handler:
            await handler.handle(event, adapter, handler_config.handler_config)
        else:
            self.ap.logger.warning(
                f"Unknown handler type: {handler_config.handler_type}"
            )

    def _match_handlers(
        self,
        event_type: str,
        handlers: list[EventHandlerConfig],
    ) -> list[EventHandlerConfig]:
        """匹配事件类型到处理器配置

        匹配规则：
        1. 精确匹配：event_type == handler.event_type
        2. 命名空间通配：handler.event_type 为 "message.*" 时匹配所有 "message.xxx"
        3. 全局通配：handler.event_type 为 "*" 时匹配所有事件
        4. 按 priority 降序排列
        5. 只返回 enabled=True 的规则
        """
        matched = []
        for handler in handlers:
            if not handler.enabled:
                continue
            if self._event_type_matches(event_type, handler.event_type):
                matched.append(handler)

        matched.sort(key=lambda h: h.priority, reverse=True)
        return matched

    @staticmethod
    def _event_type_matches(event_type: str, pattern: str) -> bool:
        """判断事件类型是否匹配模式"""
        if pattern == "*":
            return True
        if pattern == event_type:
            return True
        if pattern.endswith(".*"):
            namespace = pattern[:-2]
            return event_type.startswith(namespace + ".")
        return False
```

## 5. 事件处理器（Handler）实现

### 5.1 Handler 基类

```python
class AbstractEventHandler(abc.ABC):
    """事件处理器基类"""

    def __init__(self, ap: Application):
        self.ap = ap

    @abc.abstractmethod
    async def handle(
        self,
        event: Event,
        adapter: AbstractPlatformAdapter,
        config: dict,
    ) -> None:
        """处理事件

        Args:
            event: 统一事件对象
            adapter: 适配器实例（用于调用平台 API 发送响应）
            config: handler_config 配置
        """
        ...
```

### 5.2 PipelineHandler

将消息事件注入现有 Pipeline 流水线处理。

```python
class PipelineHandler(AbstractEventHandler):
    """Pipeline 处理器 — 将事件送入现有 Pipeline 流水线"""

    async def handle(self, event, adapter, config):
        pipeline_uuid = config.get("pipeline_uuid")

        if not isinstance(event, MessageReceivedEvent):
            self.ap.logger.warning(
                f"PipelineHandler only supports MessageReceivedEvent, "
                f"got {event.type}"
            )
            return

        # 将 MessageReceivedEvent 转换为现有的 Query 并投入 QueryPool
        # 复用现有的 MessageAggregator + QueryPool + Pipeline 机制
        launcher_type = (
            LauncherTypes.PERSON
            if event.chat_type == ChatType.PRIVATE
            else LauncherTypes.GROUP
        )

        await self.ap.msg_aggregator.add_message(
            bot_uuid=event.bot_uuid,
            launcher_type=launcher_type,
            launcher_id=event.chat_id,
            sender_id=event.sender.id,
            message_event=event.to_legacy_event(),  # 转换为 FriendMessage/GroupMessage
            message_chain=event.message_chain,
            adapter=adapter,
            pipeline_uuid=pipeline_uuid,
        )
```

### 5.3 AgentHandler

直接调用 RequestRunner 处理事件，不经过 Pipeline Stage 链。

```python
class AgentHandler(AbstractEventHandler):
    """Agent 处理器 — 直接调用 RequestRunner 处理事件"""

    async def handle(self, event, adapter, config):
        runner_name = config.get("runner", "local-agent")
        runner_config = config.get("runner_config", {})

        # 1. 查找 Runner 类
        runner_cls = None
        for r in preregistered_runners:
            if r.name == runner_name:
                runner_cls = r
                break

        if not runner_cls:
            self.ap.logger.error(f"Runner not found: {runner_name}")
            return

        # 2. 构建事件上下文（将事件信息整理为 Runner 可处理的格式）
        event_context = self._build_event_context(event, runner_config)

        # 3. 实例化并调用 Runner
        runner = runner_cls(self.ap, self._build_runner_pipeline_config(config))

        response_messages = []
        async for result in runner.run(event_context):
            response_messages.append(result)

        # 4. 发送响应（如果 Runner 产生了回复）
        if response_messages and isinstance(event, MessageReceivedEvent):
            # 将 Runner 输出转换为 MessageChain 并回复
            reply_chain = self._build_reply_chain(response_messages)
            await adapter.reply_message(event, reply_chain)

    def _build_event_context(self, event, runner_config):
        """将事件构建为 Runner 可处理的上下文

        对于消息事件，直接使用消息内容。
        对于其他事件，根据 runner_config 中的 prompt 模板生成描述文本。
        """
        ...

    def _build_runner_pipeline_config(self, config):
        """将 handler_config 转换为 Runner 需要的 pipeline_config 格式"""
        ...
```

### 5.4 WebhookHandler

将事件 POST 到外部 URL。

```python
class WebhookHandler(AbstractEventHandler):
    """Webhook 处理器 — 将事件 POST 到外部 URL"""

    async def handle(self, event, adapter, config):
        url = config.get("url")
        method = config.get("method", "POST")
        headers = config.get("headers", {})
        timeout = config.get("timeout", 30)
        retry_count = config.get("retry_count", 3)
        response_actions = config.get("response_actions", False)

        # 1. 构建请求体
        bot = await self.ap.platform_mgr.get_bot_by_uuid(event.bot_uuid)
        payload = {
            "event": event.model_dump(),
            "bot": {
                "uuid": bot.bot_entity.uuid,
                "name": bot.bot_entity.name,
                "adapter": bot.bot_entity.adapter,
            }
        }

        # 2. 发送请求（带重试）
        response = await self._send_with_retry(
            url, method, headers, payload, timeout, retry_count
        )

        # 3. 处理响应动作
        if response_actions and response:
            await self._execute_response_actions(
                response, adapter, event
            )

    async def _execute_response_actions(self, response, adapter, event):
        """执行响应中的动作列表"""
        actions = response.get("actions", [])
        for action in actions:
            action_type = action.get("type")
            params = action.get("params", {})

            if action_type == "send_message":
                chain = MessageChain.model_validate(params.get("message", []))
                await adapter.send_message(
                    params["target_type"],
                    params["target_id"],
                    chain,
                )
            elif action_type == "reply":
                chain = MessageChain.model_validate(params.get("message", []))
                await adapter.reply_message(event, chain)
            elif action_type == "call_platform_api":
                await adapter.call_platform_api(
                    params["action"],
                    params.get("params", {}),
                )
            elif action_type == "approve_friend_request":
                await adapter.approve_friend_request(
                    params["request_id"],
                    params.get("approve", True),
                )
            # ... 更多动作类型
```

### 5.5 PluginHandler

将事件分发给插件的 EventListener。

```python
class PluginHandler(AbstractEventHandler):
    """Plugin 处理器 — 分发给插件 EventListener"""

    async def handle(self, event, adapter, config):
        plugin_filter = config.get("plugin_filter", [])

        # 复用现有的插件事件分发机制
        # 通过 plugin_connector 将事件发送给 Plugin Runtime
        await self.ap.plugin_connector.emit_event(
            event=event,
            adapter=adapter,
            plugin_filter=plugin_filter,
        )
```

## 6. use_pipeline_uuid 迁移

### 6.1 自动迁移

数据库迁移脚本将现有的 `use_pipeline_uuid` 自动转换为 `event_handlers`：

```python
# 迁移逻辑
for bot in all_bots:
    if bot.use_pipeline_uuid and not bot.event_handlers:
        bot.event_handlers = [
            {
                "event_type": "message.received",
                "handler_type": "pipeline",
                "handler_config": {
                    "pipeline_uuid": bot.use_pipeline_uuid
                },
                "enabled": True,
                "priority": 10,
                "description": "Auto-migrated from use_pipeline_uuid"
            },
            {
                "event_type": "*",
                "handler_type": "plugin",
                "handler_config": {},
                "enabled": True,
                "priority": -100,
                "description": "Default plugin handler"
            }
        ]
```

### 6.2 过渡期兼容

在过渡期内，如果 `event_handlers` 为空且 `use_pipeline_uuid` 非空，EventRouter 自动回退到旧行为：

```python
# EventRouter.route() 中的兼容逻辑
if not event_handlers and bot.bot_entity.use_pipeline_uuid:
    # 回退：消息事件走 Pipeline，其他事件走 Plugin
    if isinstance(event, MessageReceivedEvent):
        await self.handlers["pipeline"].handle(
            event, adapter,
            {"pipeline_uuid": bot.bot_entity.use_pipeline_uuid}
        )
    else:
        await self.handlers["plugin"].handle(event, adapter, {})
    return
```

## 7. WebUI 编排面板数据模型

### 7.1 交互设计概要

在 WebUI 的 Bot 管理页面，新增"事件处理器"标签页（或区域），呈现为一个**规则列表**：

```
┌─────────────────────────────────────────────────────────────┐
│  事件处理器                                       [+ 添加规则]  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─ 规则 1 ─────────────────────────────────── [启用] [删除] ─┐ │
│  │  事件类型: [message.received    ▾]                         │ │
│  │  处理器:   [Pipeline           ▾]                         │ │
│  │  Pipeline: [默认流水线          ▾]                         │ │
│  │  优先级:   [10]                                           │ │
│  │  描述:     消息事件使用默认流水线处理                         │ │
│  └──────────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─ 规则 2 ─────────────────────────────────── [启用] [删除] ─┐ │
│  │  事件类型: [group.member_joined ▾]                         │ │
│  │  处理器:   [Agent              ▾]                         │ │
│  │  Runner:   [local-agent        ▾]                         │ │
│  │  模型:     [gpt-4o-mini        ▾]                         │ │
│  │  Prompt:   [有新成员加入...]                                │ │
│  │  优先级:   [0]                                            │ │
│  └──────────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─ 规则 3 (兜底) ──────────────────────────── [启用] [删除] ─┐ │
│  │  事件类型: [*                   ▾]                         │ │
│  │  处理器:   [Plugin             ▾]                         │ │
│  │  优先级:   [-100]                                         │ │
│  └──────────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 7.2 前端数据结构

```typescript
interface EventHandlerRule {
  event_type: string;       // 下拉选择，选项从适配器 manifest 的 supported_events 获取
  handler_type: string;     // "pipeline" | "agent" | "webhook" | "plugin"
  handler_config: Record<string, any>;  // 根据 handler_type 动态渲染不同的配置表单
  enabled: boolean;
  priority: number;
  description: string;
}

// Bot 编辑接口扩展
interface BotConfig {
  uuid: string;
  name: string;
  adapter: string;
  adapter_config: Record<string, any>;
  enable: boolean;
  event_handlers: EventHandlerRule[];  // 新增
}
```

### 7.3 事件类型下拉选项

从 Bot 关联的适配器 manifest 中获取 `supported_events`，加上通配符选项：

```
- message.received
- message.edited
- message.deleted
- message.reaction
- feedback.received
- group.member_joined
- group.member_left
- group.member_banned
- group.info_updated
- friend.request_received
- friend.added
- bot.invited_to_group
- bot.removed_from_group
- bot.muted
- bot.unmuted
- platform.specific
─────────────────
- message.*          (所有消息事件)
- feedback.*         (所有反馈事件)
- group.*            (所有群组事件)
- friend.*           (所有好友事件)
- bot.*              (所有 Bot 事件)
- *                  (所有事件)
```

### 7.4 HTTP API

```
GET    /api/v1/bots/{uuid}/event-handlers         获取 Bot 的事件处理器配置
PUT    /api/v1/bots/{uuid}/event-handlers         更新 Bot 的事件处理器配置
GET    /api/v1/adapters/{name}/supported-events    获取适配器支持的事件类型
GET    /api/v1/adapters/{name}/supported-apis      获取适配器支持的 API
```
