简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

name: erispulse-hot-reload description: "适用于:改代码不重启(热重载)、闲置自动归眠(dormant_after)、运行时注册模块。覆盖场景:この記事を読む必要があるタイミング:ラグロードモジュールを使用する場合、または activate_on イベント駆動型のアクティベーションを使用する場合。それ以外の場合はスキップしても、他の機能の使用に影響はありません。このドキュメントに沿って進めると、/hello コマンドに応答できる実行可能なモジュールが完成し、モジュールのプロジェクト構造、登録方法、ロード/アンロードフックについても理解できます。今後のすべてのモジュールドキュメントは、この骨組みの上に成り立っています。"

ErisPulse 热重载与懒加载休眠

This skill is auto-built from the ErisPulse documentation. Repository: https://github.com/ErisPulse/ErisPulse Source paths are annotated per section; see docs/ in the repository for the latest content.

内容大纲

AI 按需读取:只读需要的章节即可,不必全文加载。

懒加载与休眠

来源: docs/ja/advanced/lazy-loading.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ja/advanced/lazy-loading.md)

ラグロードモジュールシステム

Tip


この記事を読む必要があるタイミング:ラグロードモジュールを使用する場合、または activate_on イベント駆動型のアクティベーションを使用する場合。それ以外の場合はスキップしても、他の機能の使用に影響はありません。

ErisPulse SDK のラグロードモジュールシステムは、モジュールの初期化を実際にアクセスされたタイミングまで遅らせます。起動フェーズではプレースホルダーエージェントのみを登録し、使用されなかったモジュールは起動時間やメモリを占有しません。

概要

ErisPulse のコア機能の 1 つである遅延ロードモジュールシステムは、以下の方法で動作します。

動作原理

LazyModule クラス

ラグジュアリー・ロード・システムの中心となるのが LazyModule クラスです。これは、最初にアクセスされたときにのみモジュールを実際に初期化するラッパーです。

初期化プロセス

モジュールが初めてアクセスされたとき、LazyModule は以下の操作を実行します:

  1. モジュールクラスの __init__ パラメータ情報を取得します
  2. パラメータに基づいて sdk リファレンスを渡すかどうかを決定します
  3. モジュールの moduleInfo 属性を設定します
  4. BaseModule を継承したモジュールの場合、on_load メソッドを呼び出します
  5. module.init ライフサイクルイベントをトリガーします

イベント駆動型遅延起動(activate_on)

Note

この機能は ErisPulse 2.8.0+ が必要です。

lazy_load=True のモジュールはデフォルトで最初の属性アクセス時にのみロードされます。モジュールがコマンド/イベントハンドラを登録している場合、従来の方法では lazy_load=False として即時ロードするしかありませんでした。activate_on は第三の選択肢を提供します:トリガを宣言し、最初の一致するイベント/コマンドが到着した時点でモジュールを自動的にアクティブ化する——メモリに常駐させることなく、トリガのエントリポイントを失うこともありません。

from ErisPulse.loaders import ModuleLoadStrategy

class MyModule(BaseModule):
    @staticmethod
    def get_load_strategy():
        return ModuleLoadStrategy(
            lazy_load=True,
            activate_on=[
                # ---- イベントトリガ(受動的到着、ユーザーの意識を必要としない)----
                "message",                                    # タイプレベル:任意のメッセージイベント
                {"notice": "group_member_increase"},          # タイプ + 単一 detail_type
                {"message": ["private", "group"]},            # タイプ + 複数 detail_type

                # ---- コマンドトリガ(能動的入力、Help に表示されるプレースホルダーコマンド)----
                {"command": "roll"},                          # 簡易表記:コマンド名
                {"command": ["roll", "dice"]},                # コマンド名リスト
                {"command": {                                 # dict 形式で宣言(name は必須)
                    "name": "dice",
                    "help": "サイコロを振る",
                    "usage": "/dice",
                    "group": "娯楽",
                    "aliases": ["d"],
                    "hidden": False,
                }},
            ],
        )

コマンド dict 形式の宣言パラメータ

dict 形式は @command() デコレータのユーザーレベルのパラメータを反映し、モジュールのロード前にプレースホルダーコマンドを登録するのに使用されます。

パラメータ 型 デフォルト 説明
name str 必須 コマンド名;on_load での @command(name) と一致する必要がある。一致しない場合、アクティブ化後にプレースホルダーが削除され、コマンドは存在しなくなる
help str フォールバックチェーン Help に表示される説明;未宣言の場合、フォールバックチェーンから値を取得する(以下参照)
usage str 自動生成 使用法行;デフォルトは {prefix}{name}
group str None コマンドグループ
aliases list[str] [] 別名を同時に登録し、別名の入力でもトリガとして機能する
hidden bool False True の場合、プレースホルダーコマンドも非表示(アクティブ化後の実際のコマンドの非表示の意味と一致);コマンド名を知っているユーザーの入力でもトリガとして機能する

サポートされていない priority / permission / master:プレースホルダーコマンドの役割はトリガの起動だけであり、権限チェックはアクティブ化後の実際のコマンドが実行する(プレースホルダー段階で権限チェックを行うと、「コマンド入力による起動」が無効になる)。

占位コマンドの Help フォールバックチェーン

モジュールがロードされていない場合、Help に表示されるコマンドの説明は以下の順序で取得されます(最初に見つかった値を使用):

  1. dict 形式で宣言されたコマンドレベルの help(最も正確)
  2. モジュールの get_meta() の description
  3. モジュールの __description__ 属性
  4. パッケージのメタデータの Summary(PyPI パッケージの概要)
  5. 一般的なメッセージ:「このコマンドは遅延ロードモジュール X から来ています。初めて使用すると、モジュールが自動的にロードされます」

トリガの意味

アーキテクチャ図と完全な意味については、アーキテクチャ概要を参照してください。

懒惰ロードの休眠(dormant_after)

Note

この機能は ErisPulse 2.11.0+ が必要です。

長期間使用されていない遅延ロードモジュールに対して、フレームワークはその休眠を可能にします。これにより、実行時のリソース(タスク、接続、イベントの登録など)を解放しますが、遅延ロードプロキシと登録のスタブは保持されます。モジュールは「登録済みだがロードされていない」状態に戻り、次回の属性アクセス、module.call、activate_on イベントによって透明に再起動されます(再インスタンス化され、on_load が実行されます)。再起動のための API は一切不要です。休眠後のシステムの状態は、「一度も起動されていない」場合と完全に同一です。

宣言方法

@staticmethod
def get_load_strategy():
    return ModuleLoadStrategy(
        lazy_load=True,
        dormant_after="30m",   # 30 分間の無使用で自動的に休眠;宣言しない場合は休眠しない(デフォルト)
    )

時間の指定は、コマンド管理の cooldown と同じ形式です(90s / 1h30m / 1d)。スクリーナーは固定間隔(60 秒)でスキャンを行い、実際の休眠時刻は閾値 + 最大でスキャン間隔分のずれがあります。また、手動で休眠させることも可能です:await sdk.module.sleep("モジュール名")。明示的な unload は完全なアンロード(遅延プロキシも削除)になります。

状態の継続

休眠前にフレームワークは on_before_unload(reason="dormant") をトリガーします(アンロードの最初のステップ、リソースはまだ有効)。モジュールは sdk.storage に状態を保存します。再起動後は on_load で状態を復元します:

async def on_before_unload(self, event):
    if event["reason"] in ("dormant", "reload", "disable"):
        await self.sdk.storage.aset(f"{self._module_name}:counter", self._count)

async def on_load(self, event):
    self._count = await self.sdk.storage.aget(f"{self._module_name}:counter") or 0

意味と境界

懒惰ロードの設定

グローバル設定

設定ファイルでグローバルな遅延ロードを有効または無効にします:

[ErisPulse.framework]
enable_lazy_loading = true  # true=遅延ロードを有効にする(デフォルト), false=遅延ロードを無効にする

モジュールレベルの制御

モジュールは get_load_strategy() 静的メソッドを実装することで、ロード戦略を制御できます:

from ErisPulse.Core.Bases import BaseModule
from ErisPulse.loaders import ModuleLoadStrategy

class MyModule(BaseModule):
    @staticmethod
    def get_load_strategy():
        """モジュールのロード戦略を返す"""
        return ModuleLoadStrategy(
            lazy_load=False,  # Falseを返すと即時ロード
            priority=100      # ロードの優先度、数値が大きいほど優先度が高い
        )

ラグロードモジュールの使用

基本的な使い方

開発者にとって、ラグロードモジュールは通常のモジュールと使用方法にほとんど違いはありません:

# SDKを介してラグロードモジュールにアクセス
from ErisPulse import sdk

# 以下のようにアクセスするとモジュールのラグロードがトリガーされます
result = await sdk.my_module.my_method()

モジュールの取得の統一されたエントリーポイント

SDK属性、モジュールマネージャー属性を通じてアクセスする場合でも、module.get()で検索する場合でも、 「登録済みだがまだロードされていない」ラグロードモジュールに対しては、すべて同じラグロードプロキシが返り、 そのプロパティにアクセスすることで初めてモジュールの初期化がトリガーされます:

# 3つの方法で取得できるのはすべてラグロードプロキシです(モジュールがロードされていない場合)、動作は一貫しており、ユーザーには透明です
sdk.my_module          # ロードをトリガーするエントリーポイント
sdk.module.my_module   # 同様にラグロードプロキシを返します
sdk.module.get("my_module")  # これもラグロードプロキシを返します。この操作自体はロードをトリガーしません

# プロキシの任意の属性にアクセスすることで、モジュールの初期化が実際に行われます
result = await sdk.my_module.my_method()

module.get()は検索用のインターフェースであり、ロードをトリガーしません:

明示的にロードをトリガーするには、await sdk.load_module("my_module")を使用してください。

非同期初期化

非同期初期化が必要なモジュールについては、まず明示的にロードすることを推奨します:

# まずモジュールを明示的にロード
await sdk.load_module("my_module")

# その後モジュールを使用
result = await sdk.my_module.my_method()

同期初期化

非同期初期化を必要としないモジュールについては、直接アクセスできます:

# 直接アクセスすると自動的に同期初期化されます
result = sdk.my_module.some_sync_method()

最佳実践

ロード戦略を選択する際は、以下の決定フローを参考にしてください。

flowchart TD
    A["モジュール宣言<br/>get_load_strategy()"] --> B{"起動時に即座に準備が必要か<br/>または高頻度でトリガーされるか?"}
    B -->|"はい"| C["lazy_load=False<br/>即時ロード"]
    B -->|"いいえ"| D{"コマンド / イベントハンドラを登録しているか?"}
    D -->|"はい"| E["lazy_load=True + activate_on<br/>イベント/コマンドが到着した際にアクティベート"]
    D -->|"いいえ"| F["lazy_load=True<br/>最初の属性アクセス時にロード"]
    C --> G["起動時に on_load() を呼び出す"]
    E --> H["stub を登録 → トリガー時にインスタンス化"]
    F --> I["LazyModule 代理"]

懒惰ロード(lazy_load=True)を使用することを推奨する場面

懒惰ロードを禁止することを推奨する場面(lazy_load=False)

priority パラメータは、即時ロードされるモジュール間の初期化順序を制御します。数値が大きいほど先に初期化されます。同じ優先度のモジュールは登録順にロードされます。

注意事項

  1. モジュールが遅延ロードを使用している場合、ErisPulse内で他のモジュールによって一度も呼び出されない場合、そのモジュールは決して初期化されません。
  2. モジュール内にEventの監視など、他のモジュールを積極的に監視するモジュールが含まれている場合、2つの選択肢があります:activate_on トリガーを宣言して遅延ロードを維持し、イベントが到達したときに自動的にアクティブ化するか、または即時ロードを必要とすることを宣言する(lazy_load=False)か、さもなければモジュールの正常な業務に影響を及ぼす可能性があります。
  3. 特殊な要望がない限り、遅延ロードを無効にすることは推奨しません。そうしないと、依存管理やライフサイクルイベントなどの問題が生じる可能性があります。
  4. activate_on のコマンド dict 声明において、name はモジュールの on_load で @command() によって登録された実際のコマンド名と一致する必要があります。一致しない場合、モジュールがアクティブ化された後にプレースホルダーコマンドが解除され、宣言と実装が一致しないコマンドは存在しません。

関連ドキュメント

次の意図に応じて選択してください:

模块开发入门(加载策略)

来源: docs/ja/developer-guide/modules/getting-started.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ja/developer-guide/modules/getting-started.md)

モジュール開発入門

Note

L1 コア必読――トップページの「学習パス」にある5つのコアドキュメントの1つで、モジュール開発に必須です。

このドキュメントに沿って進めると、/hello コマンドに応答できる実行可能なモジュールが完成し、モジュールのプロジェクト構造、登録方法、ロード/アンロードフックについても理解できます。今後のすべてのモジュールドキュメントは、この骨組みの上に成り立っています。

プロジェクト構造

標準的なモジュールの構造:

MyModule/
├── pyproject.toml
├── README.md
├── LICENSE
└── MyModule/
    ├── __init__.py
    └── Core.py

pyproject.toml 設定

[project]
name = "ErisPulse-MyModule"
version = "1.0.0"
description = "モジュール機能の説明"
readme = "README.md"
requires-python = ">=3.10"
license = { file = "LICENSE" }
authors = [ { name = "yourname", email = "[email protected]" } ]
dependencies = []

[project.urls]
"homepage" = "https://github.com/yourname/MyModule"

[project.entry-points."erispulse.module"]
"MyModule" = "MyModule:Main"

__init__.py

from .Core import Main

Core.py - 基本モジュール

from ErisPulse import sdk
# ルートからのインポートを推奨(2.10+):よく使うシンボルはルートパッケージから直接インポートでき、深いパスも引き続き互換
from ErisPulse import BaseModule, command

class Main(BaseModule):
    def __init__(self, sdk):
        self.sdk = sdk
        self.logger = sdk.logger.get_child("MyModule")
        self.storage = sdk.storage
    
    @staticmethod
    def get_load_strategy():
        """モジュールのロード戦略を返します"""
        from ErisPulse.loaders import ModuleLoadStrategy
        return ModuleLoadStrategy(
            lazy_load=True,
            priority=0,
            depends=[],  # 任意:依存する他のモジュールのリスト
            # 任意:イベント駆動の遅延アクティベーション――トリガーを宣言すると、最初に一致するイベント/コマンドが到着した時点で自動的にロードされます
            # activate_on=[{"command": {"name": "hello", "help": "挨拶を送る"}}],
        )
    
    async def on_load(self, event):
        """モジュールのロード時に呼び出されます"""
        @command("hello", help="挨拶を送る")
        async def hello_command(event):
            name = event.get_user_nickname() or "フレンド"
            await event.reply(f"こんにちは、{name}さん!")
        
        self.logger.info("モジュールがロードされました")
    
    async def on_unload(self, event):
        """モジュールのアンロード時に呼び出されます"""
        self.logger.info("モジュールがアンロードされました")

設定の読み取り:上記の基本例では設定を使用していません。設定を読み取る必要がある場合は、ネストした ConfigClass を宣言し、self.cfg 経由でリアルタイムに読み取る方法が推奨されます(詳細は モジュールのコア概念 を参照)。_load_config() を手動で呼び出す旧式の書き方は非推奨となりました。

モジュールのテスト

ローカルテスト

# プロジェクトディレクトリでモジュールをインストール
epsdk install ./MyModule

# プロジェクトを実行
epsdk run main.py --reload

コマンドのテスト

コマンドを送信してテストします:

/hello

コア概念

BaseModule 基底クラス

すべてのモジュールは BaseModule を継承する必要があり、以下のメソッドを提供します:

メソッド 説明 必須
__init__(self, sdk) コンストラクター(フレームワークが sdk インスタンスを渡します) いいえ
get_load_strategy() ロード戦略を返します いいえ
get_meta() モジュール紹介のメタ情報を返します(任意) いいえ
on_load(self, event) モジュールのロード時に呼び出されます はい
on_unload(self, event) モジュールのアンロード時に呼び出されます はい

モジュール紹介 meta

Note

この機能には ErisPulse 2.8.0+ が必要です。

get_meta() を通じて、モジュールの紹介メタ情報(このモジュールが何をするのか、どのカテゴリに属するのかなど)を宣言します。 メタ情報はモジュールの汎用紹介データであり、help モジュール、Dashboard のモジュールリスト、モジュールストアなど、さまざまなインターフェース/エコシステムモジュールから利用されます。

get_load_strategy() が ModuleLoadStrategy を返すのと同様に、**ModuleMeta 設定クラスのインスタンスを返すことが推奨されます**(属性の型付け、IDE 補完)。dict を直接返すことにも対応しています:

class MyModule(BaseModule):
    @staticmethod
    def get_meta() -> ModuleMeta:
        return ModuleMeta(
            name="天気",               # 表示名(デフォルトの登録名)
            description="都市の天気を照会",  # モジュールの概要
            version="1.0.0",
            author="ErisDev",
            group="ツール",               # 機能グループ
            tags=["天気", "照会"],
        )

互換的な書き方(dict):

class MyModule(BaseModule):
    @staticmethod
    def get_meta() -> dict:
        return {
            "name": "天気",
            "description": "都市の天気を照会",
            "version": "1.0.0",
            "author": "ErisDev",
            "group": "ツール",
            "tags": ["天気", "照会"],
        }

meta フィールドの i18n サポート

メタ情報のフィールド値には、プレーンな文字列、または i18n 辞書 {"i18n": "key.path", "default": "フォールバックテキスト"}(設定の description と同じ規約)を使用できます。 翻訳キーは I18nClass で宣言・登録し、module.get_meta() が読み取る際に現在の言語のテキストへ自動的に解決されます:

class MyModule(BaseModule):
    class I18nClass(BaseI18n):
        meta_description: I18nKey = I18nKey(
            default="Weather lookup",
            zh_CN="查询城市天气",
            en="Weather lookup",
        )

    @staticmethod
    def get_meta() -> ModuleMeta:
        return ModuleMeta(
            name="天気",
            description={"i18n": "MyModule.meta_description", "default": "Weather lookup"},
        )

SDK オブジェクト

sdk オブジェクトを通じてコア機能にアクセスします:

from ErisPulse import sdk

sdk.storage    # ストレージシステム
sdk.config     # 設定システム
sdk.logger     # ログシステム
sdk.adapter    # アダプターシステム
sdk.router     # ルーティングシステム
sdk.lifecycle  # ライフサイクルシステム

次のステップ

学習パスの続き

作成後は