高度なルール アクション: Python スクリプトの実行

このドキュメントでは、カスタム Python スクリプトを実行して単純なテキスト置換ルールの機能を拡張する方法について説明します。この強力な機能を使用すると、動的な応答の作成、ファイルの操作、外部 API の呼び出し、複雑なロジックの実装を音声認識ワークフロー内で直接行うことができます。

中心となる概念: on_match_exec

テキストを単に置き換えるのではなく、パターンが一致した場合に 1 つ以上の Python スクリプトを実行するようにルールに指示できるようになりました。これは、「on_match_exec」キーをルールのオプション辞書に追加することによって行われます。

スクリプトの主な仕事は、一致に関する情報を受け取り、アクションを実行し、新しいテキストとして使用される最終文字列を返すことです。

ルールの構造

スクリプト アクションを含むルールは次のようになります。

# In your map file (e.g., config/maps/.../de-DE/my_rules.py)
from pathlib import Path

# It's best practice to define the directory path once at the top
CONFIG_DIR = Path(__file__).parent

FUZZY_MAP_pre = [
    (
        None,  # The replacement string is often None, as the script generates the final text.
        r'what time is it', # The regex pattern to match.
        95, # The confidence threshold.
        {
            'flags': re.IGNORECASE,
            # The new key: a list of script files to execute.
            'on_match_exec': [CONFIG_DIR / 'get_current_time.py']
        }
    ),
]

重要なポイント:

  • on_match_exec 値は list である必要があります。

  • スクリプトはマップ ファイルと同じディレクトリに配置されるため、パスを定義するには CONFIG_DIR / 'script_name.py' が推奨される方法です。


実行可能スクリプトの作成

システムがスクリプトを使用するには、次の 2 つの単純なルールに従う必要があります。

  1. 有効な Python ファイル (例: 「my_script.py」) である必要があります。

  2. execute(match_data) という名前の関数が含まれている必要があります。

execute(match_data) 関数

これは、すべての実行可能スクリプトの標準エントリ ポイントです。ルールが一致すると、システムは自動的にこの関数を呼び出します。

  • match_data (dict): 一致に関するすべてのコンテキストを含む辞書。

  • 戻り値 (str): 関数は文字列を返す必要があります**。この文字列が新しく処理されたテキストになります。

match_data ディクショナリ

この辞書は、メイン アプリケーションとスクリプトの間の橋渡しとなります。これには次のキーが含まれています。

  • 'original_text' (str): 現在のルールからの置換が適用される「前の」フルテキスト文字列。

  • 'text_after_replacement' (文字列): ルールの基本置換文字列が適用された「後」、ただしスクリプトが呼び出される「前」のテキスト。 (置換が「なし」の場合、これは「original_text」と同じになります)。

  • 'regex_match_obj' (re.Match): 公式の Python 正規表現一致オブジェクト。これは、キャプチャ グループにアクセスする場合に非常に強力です。 match_obj.group(1)match_obj.group(2)などを使用できます。

  • 'rule_options' (dict): スクリプトをトリガーしたルールの完全なオプション辞書。


例 1: 現在時刻の取得 (動的応答)

このスクリプトは、時刻に基づいてパーソナライズされた挨拶を返します。

1.ルール (マップ ファイル内):

(None, r'\b(what time is it|uhrzeit)\b', 95, {
    'flags': re.IGNORECASE,
    'on_match_exec': [CONFIG_DIR / 'get_current_time.py']
}),

2.スクリプト (get_current_time.py):

from datetime import datetime
import random

def execute(match_data):
    """Returns a friendly, time-aware response."""
    now = datetime.now()
    hour = now.hour
    time_str = now.strftime('%H:%M')

    if hour < 12:
        greeting = "Good morning!"
    elif hour < 18:
        greeting = "Good afternoon!"
    else:
        greeting = "Good evening!"
    
    responses = [
        f"{greeting} It's currently {time_str}.",
        f"Right now, the time is {time_str}. Hope you're having a great day!",
    ]
    return random.choice(responses)

使用法:

入力: 「今何時ですか」 出力: 「こんにちは! 現在 14:30 です。」

例 2: 単純な計算機 (キャプチャ グループの使用)

このスクリプトは、正規表現からのキャプチャ グループを使用して計算を実行します。

1.ルール (マップ ファイル内):

(None, r'calculate (\d+) (plus|minus) (\d+)', 98, {
    'flags': re.IGNORECASE,
    'on_match_exec': [CONFIG_DIR / 'calculator.py']
}),

2.スクリプト (calculator.py):

def execute(match_data):
    """Performs a simple calculation based on regex capture groups."""
    try:
        match_obj = match_data['regex_match_obj']
        
        num1 = int(match_obj.group(1))
        operator = match_obj.group(2).lower()
        num2 = int(match_obj.group(3))

        if operator == "plus":
            result = num1 + num2
        elif operator == "minus":
            result = num1 - num2
        else:
            return "I didn't understand that operator."
            
        return f"The result is {result}."
    except (ValueError, IndexError):
        return "I couldn't understand the numbers in your request."

使用法:

入力: 「55 プラス 10 を計算します」 出力: 「結果は 65 です。」

例 3: 永続的なショッピング リスト (ファイル I/O)

この例では、ユーザーの元のテキストを検査することで 1 つのスクリプトで複数のコマンド (追加、表示) を処理する方法と、ファイルに書き込むことでデータを保持する方法を示します。

1.ルール (マップ ファイル内):

# Rule for adding items
(None, r'add (.*) to the shopping list', 95, {
    'flags': re.IGNORECASE,
    'on_match_exec': [CONFIG_DIR / 'shopping_list.py']
}),

# Rule for showing the list
(None, r'show the shopping list', 95, {
    'flags': re.IGNORECASE,
    'on_match_exec': [CONFIG_DIR / 'shopping_list.py']
}),

2.スクリプト (shopping_list.py):

from pathlib import Path

LIST_FILE = Path(__file__).parent / "shopping_list.txt"

def execute(match_data):
    """Manages a shopping list stored in a text file."""
    original_text = match_data['original_text'].lower()
    
    # --- Add Item Command ---
    if "add" in original_text:
        item = match_data['regex_match_obj'].group(1).strip()
        with open(LIST_FILE, "a", encoding="utf-8") as f:
            f.write(f"{item}\n")
        return f"Okay, I've added '{item}' to the shopping list."
    
    # --- Show List Command ---
    elif "show" in original_text:
        if not LIST_FILE.exists() or LIST_FILE.stat().st_size == 0:
            return "The shopping list is empty."
        with open(LIST_FILE, "r", encoding="utf-8") as f:
            items = f.read().strip().splitlines()
        
        item_str = ", ".join(items)
        return f"On the list you have: {item_str}."
        
    return "I'm not sure what to do with the shopping list."

使用法:

入力 1: 「買い物リストに牛乳を追加」 出力 1: 「はい、買い物リストに「牛乳」を追加しました。」

入力 2: 「買い物リストを表示」 出力 2: 「リストには牛乳があります。」


ベストプラクティス

  • スクリプトごとに 1 つのジョブ: スクリプトを単一のタスクに集中させます (例: calculator.py は計算のみを実行します)。

  • エラー処理: アプリケーション全体がクラッシュするのを防ぐために、スクリプトのロジックを常に try...excel ブロックで囲みます。 excel ブロックからわかりやすいエラー メッセージを返します。

  • 外部ライブラリ: 外部ライブラリ (requestswikipedia-api など) を使用できますが、それらが Python 環境にインストールされていることを確認する必要があります (pip install <library-name>)。

  • セキュリティ: この機能はあらゆる Python コードを実行できることに注意してください。信頼できるソースからのスクリプトのみを使用してください。