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
UNKNOWN— Unknown eventON_START— Plugin startupON_STOP— Plugin stopON_MESSAGE_PRE_PROCESS— Message preprocessing stage (best timing for filtering/interception)ON_MESSAGE— Message processing stageON_PLAN— Planning stagePOST_LLM— After LLM call (response generated)AFTER_LLM— After LLM call completedPOST_SEND_PRE_PROCESS— Send preprocessing stagePOST_SEND— After message sentAFTER_SEND— After message send completed
intercept_message Parameter
intercept_message controls whether EventHandler participates in the message processing chain in blocking mode:
False(default) — Async fire-and-forget, doesn't affect main message flowTrue— Synchronous 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
weightsemantics
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 hereON_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
- Subscription Method —
@EventHandlerEventTypeenum values →@HookHandlernamed Hook point strings - Granularity —
@EventHandlerfixed event types, limited quantity →@HookHandlercustom Hook names, infinitely extensible - Interception Method —
@EventHandlerintercept_message=True→@HookHandlermode=HookMode.BLOCKING - Priority —
@EventHandlerweightnumeric weight →@HookHandlerorderthree-level enum + global sorting - Exception Strategy —
@EventHandlerno dedicated parameter →@HookHandlercontrolled byerror_policy - Use Cases —
@EventHandlerfixed stages of message flow →@HookHandlerarbitrary 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