ForcePilot/backend/package/yuxi/channels/pipeline/base.py
Kris 7a972055b7 feat: 新增渠道服务基础框架与核心工具类
新增大量渠道适配器相关的协议、策略、工具类与基础设施代码,包括:
1.  多协议定义:认证、消息、配置、网关等核心接口
2.  策略模块:上下文、群聊、去重、防抖等业务策略
3.  工具集:重试、去重、文本分块、消息格式化等SDK工具
4.  基础设施:外部进程管理、事件广播、熔断机制等
5.  账户与管道系统:账户管理、消息处理管道实现
6.  运行时服务:状态收集、维护任务、日志等后台服务
2026-05-12 00:53:57 +08:00

80 lines
2.5 KiB
Python

from __future__ import annotations
from collections.abc import Awaitable, Callable
from typing import Any
from yuxi.channels.pipeline.context import PipelineContext
from yuxi.utils.logging_config import logger
PipelineStage = Callable[
["BaseInboundPipeline", PipelineContext],
Awaitable[PipelineContext | None],
]
class BaseInboundPipeline:
def __init__(self, adapter: Any):
self._adapter = adapter
self._stages: list[PipelineStage] = []
async def initialize(self) -> None:
self._stages = await self._build_stages()
logger.info(
f"[Pipeline] {self.__class__.__name__} initialized with "
f"{len(self._stages)} stages: "
f"{[getattr(s, '__name__', s.__class__.__name__) for s in self._stages]}"
)
async def _build_stages(self) -> list[PipelineStage]:
return []
async def process(
self,
event_type: str,
event_data: dict[str, Any],
) -> PipelineContext | None:
ctx = self._build_context(event_type, event_data)
for idx, stage in enumerate(self._stages, start=1):
stage_name = self._resolve_stage_name(stage, idx)
try:
result = await stage(self, ctx)
except Exception as e:
logger.error(
f"[Pipeline::{self.__class__.__name__}] Stage [{idx}] '{stage_name}' raised exception: {e}",
exc_info=True,
)
return None
if result is None:
reason = ctx._stop_reason or f"stage_{idx}:{stage_name}"
logger.debug(
f"[Pipeline] Message dropped at [{idx}] '{stage_name}': {reason} | ctx={ctx.debug_summary()}"
)
return None
ctx = result
if ctx.stopped:
logger.debug(f"[Pipeline] Message stopped at [{idx}] '{stage_name}': {ctx._stop_reason}")
return None
return ctx
def _build_context(self, event_type: str, event_data: dict[str, Any]) -> PipelineContext:
return PipelineContext(event_type=event_type, event_data=event_data)
@staticmethod
def _resolve_stage_name(stage: PipelineStage, idx: int) -> str:
name = getattr(stage, "__name__", "")
if name and not name.startswith("_"):
return name
if hasattr(stage, "__self__"):
return type(stage.__self__).__name__
return f"stage_{idx}"
@property
def adapter(self) -> Any:
return self._adapter