Skip to content

Event Handler

@EventHandler is a component decorator for subscribing to message and workflow events. Unlike @HookHandler's named Hook point mechanism, @EventHandler subscribes to events based on fixed EventType enum values, suitable for intercepting or observing at specific stages of the message processing flow.

Decorator Signature

python
from maibot_sdk import EventHandler
from maibot_sdk.types import EventType

@EventHandler(
    name: str,                                      # Component name (required)
    description: str = "",                          # Component description
    event_type: EventType = EventType.ON_MESSAGE,   # Subscribed event type
    intercept_message: bool = False,                # Whether to block message chain
    weight: int = 0,                                # Weight, higher executes first
    **metadata,                                     # Additional metadata
)

EventType Event Types

Enum ValueDescription
UNKNOWNUnknown event
ON_STARTPlugin startup
ON_STOPPlugin stop
ON_MESSAGE_PRE_PROCESSMessage preprocessing stage (best timing for filtering/interception)
ON_MESSAGEMessage processing stage
ON_PLANPlanning stage
POST_LLMAfter LLM call (response generated)
AFTER_LLMAfter LLM call completed
POST_SEND_PRE_PROCESSSend preprocessing stage
POST_SENDAfter message sent
AFTER_SENDAfter message send completed

intercept_message Parameter

intercept_message controls whether EventHandler participates in the message processing chain in blocking mode:

ValueBehavior
False (default)Async fire-and-forget, doesn't affect main message flow
TrueSynchronous blocking, main program waits for handler return before continuing

When set to True, the handler can intercept, modify, or even prevent subsequent message processing.

weight Weight

When multiple EventHandlers subscribe to the same EventType, weight determines execution order:

  • Higher values execute first
  • Default value is 0
  • Consistent with old system's weight semantics

Basic Usage

ON_START: Plugin Initialization

python
from maibot_sdk import MaiBotPlugin, EventHandler
from maibot_sdk.types import EventType


class StartupPlugin(MaiBotPlugin):
    async def on_load(self) -> None:
        self.ctx.logger.info("Plugin loaded")

    async def on_unload(self) -> None:
        self.ctx.logger.info("Plugin unloaded")

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    @EventHandler(
        "on_startup",
        description="Initialize resources when plugin starts",
        event_type=EventType.ON_START,
    )
    async def handle_startup(self, **kwargs):
        self.ctx.logger.info("Startup event triggered, beginning initialization")
        # Execute initialization logic needed at startup here

ON_MESSAGE_PRE_PROCESS: Message Filtering

python
from maibot_sdk import MaiBotPlugin, EventHandler
from maibot_sdk.types import EventType


class MessageFilterPlugin(MaiBotPlugin):
    async def on_load(self) -> None:
        self.ctx.logger.info("Message filter plugin loaded")

    async def on_unload(self) -> None:
        self.ctx.logger.info("Message filter plugin unloaded")

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    @EventHandler(
        "spam_filter",
        description="Filter spam messages",
        event_type=EventType.ON_MESSAGE_PRE_PROCESS,
        intercept_message=True,   # Blocking mode, can intercept messages
        weight=100,               # High weight, execute first
    )
    async def filter_spam(self, message, **kwargs):
        raw_message = message.get("raw_message", "")
        user_id = message.get("user_info", {}).get("user_id", "")

        # Detect spam messages
        if self._is_spam(raw_message, user_id):
            self.ctx.logger.info("Intercepted spam message: user=%s, text=%s", user_id, raw_message)
            return {"intercepted": True, "reason": "spam"}

        # Allow message through
        return {"intercepted": False}

    def _is_spam(self, text: str, user_id: str) -> bool:
        # Simple spam detection logic
        spam_keywords = ["advertisement", "join group", "free"]
        return any(kw in text for kw in spam_keywords)

ON_MESSAGE: Message Observation

python
from maibot_sdk import MaiBotPlugin, EventHandler
from maibot_sdk.types import EventType


class MessageObserverPlugin(MaiBotPlugin):
    async def on_load(self) -> None:
        self._message_count = 0

    async def on_unload(self) -> None:
        self.ctx.logger.info("Total messages: %d", self._message_count)

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    @EventHandler(
        "message_counter",
        description="Count messages",
        event_type=EventType.ON_MESSAGE,
    )
    async def count_message(self, message, **kwargs):
        self._message_count += 1
        self.ctx.logger.debug("Received message %d", self._message_count)

AFTER_LLM: LLM Response Post-processing

python
from maibot_sdk import MaiBotPlugin, EventHandler
from maibot_sdk.types import EventType


class LLMPostProcessor(MaiBotPlugin):
    async def on_load(self) -> None:
        self.ctx.logger.info("LLM post-processing plugin loaded")

    async def on_unload(self) -> None:
        self.ctx.logger.info("LLM post-processing plugin unloaded")

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    @EventHandler(
        "llm_response_logger",
        description="Log LLM responses",
        event_type=EventType.AFTER_LLM,
        weight=50,
    )
    async def log_llm_response(self, **kwargs):
        response = kwargs.get("response", "")
        self.ctx.logger.info("LLM response: %s", response[:200])

POST_SEND: Post-send Callback

python
from maibot_sdk import MaiBotPlugin, EventHandler
from maibot_sdk.types import EventType


class SendAuditPlugin(MaiBotPlugin):
    async def on_load(self) -> None:
        self.ctx.logger.info("Send audit plugin loaded")

    async def on_unload(self) -> None:
        self.ctx.logger.info("Send audit plugin unloaded")

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    @EventHandler(
        "send_audit",
        description="Audit all sent messages",
        event_type=EventType.POST_SEND,
    )
    async def audit_send(self, **kwargs):
        message = kwargs.get("message", {})
        self.ctx.logger.info(
            "Message sent: stream_id=%s",
            message.get("stream_id", "unknown"),
        )

Difference from HookHandler

Feature@EventHandler@HookHandler
Subscription MethodEventType enum valuesNamed Hook point strings
GranularityFixed event types, limited quantityCustom Hook names, infinitely extensible
Interception Methodintercept_message=Truemode=HookMode.BLOCKING
Priorityweight numeric weightorder three-level enum + global sorting
Exception StrategyNo dedicated parameterControlled by error_policy
Use CasesFixed stages of message flowArbitrary extension points defined by main program

General principles:

  • If you need to execute logic at fixed stages of the message flow (e.g., when receiving messages, after LLM returns), use @EventHandler
  • If you need to subscribe to specific named Hook points defined by the main program (e.g., heart_fc.heart_flow_cycle_start), use @HookHandler

Event Processing Flow