コンテンツにスキップ

自由式の書き方

自由式の書き方ガイド

Marionetteでは、Jinja2、Lambda式、Async関数の3つの記法を使って処理を記述できます。
これをまとめて、「自由式」と呼びます。

項目 Jinja2 Lambda式 Async関数
用途 文字列フォーマット変換 情報フィルタリング・フォーマット変換 高度な処理分岐。幅広い関数の利用
関数 文字列操作のみ 一般的なPython関数 一般的なPython関数
Value △ Read ✓ Read/Write ✓ Read/Write
States ✓ Read/Write ✓ Read/Write ✓ Read/Write
Dialog △ Read △ Read ✓ Read/Write

記法の例

Jinja2

文字列に対して、出し分けやフォーマットの変換を行います。

: values.score が80以上であれば「合格」、そうでなければ「不合格」と出力

こんにちは、{{ values.name }}さん!
{% if values.score >= 80 %}合格{% else %}不合格{% endif %}

: rolesystemとなっているものを除いて、user: xxxの形式で会話履歴を構成

{% for message in history %}
{% if message.role != "system" %}
{{ message.role }}: {{ message.text }}
{% endif %}
{% endfor %}

重要

Jinja2の出力は、常に 文字列 です。
また、自由式の中では、lambdaasyncが無いと自動的にJinja2と判断されます。

よくあるエラー

よくあるエラー: 例えばTrueと文字を書いた場合でも、それはbool型ではなく文字列として解釈されます。bool型としてのTrueを返す必要がある場合は、lambda: Trueという表記を利用してください。

Lambda式

値の計算、簡単な変換、条件判定を行います。
lambda: ...という書き方で式を書きます。

: states.phaseが1かどうかを判定する。出力はbool型のTrue/False

lambda: states.phase == 1

: values.historyから、過去10回分はassistantとuserを残し、それより古いものはuserのみを残す

lambda: [msg for msg in values.history[-10:] if msg.role in ['assistant', 'user']] + [msg for msg in values.history[:-10] if msg.role == 'user']

: states.start_timestampから10分経過しているかどうかを判定する

lambda: (datetime.now() - datetime.fromtimestamp(states.start_timestamp)) >= timedelta(minutes=10)

重要

標準的なPython組み込み関数はあらかじめimportされており、利用することが可能です。

Async関数

複雑なロジック、会話履歴へのアクセス、複数ステップの処理を行います。

: 会話履歴を取得して処理する

async def process(input_msg, output_msg):
    history = await input_msg.dialog.get_recent(limit=10)
    result = process_history(history)
    await output_msg.update("result", result, is_last=True)

: for文で、updated()のすべての内容がis_lastになるまで待つ

async def wait_for_completion(input_msg, output_msg):
    async for update in input_msg.updated():
        # 更新を処理
        if update.is_last:
            break
    await output_msg.update("result", result, is_last=True)

重要

標準的なPython組み込み関数はあらかじめimportされており、利用することが可能です。
また、非同期関数が利用できるため、データベースへのアクセスを伴うdialogへの書き込み操作を行うこともできます。


各変数の利用方法

自由式では、以下の変数にアクセスできます。アクセス方法は記法によって異なります。

values(入力値)

前のItemから渡されたデータにアクセスします。

Jinja2・Lambda式:

values.name          # ドットアクセス
values.count         # ドットアクセス
values.history       # リストや辞書もドットアクセス

Async関数:
Async関数では、input_msg.valuesを通してアクセスする必要があります。
また、実際の値にアクセスするには.xが必要です。

input_msg.values.name.x        # ドットアクセス(.xで値を取得)
input_msg.values.count.x       # ドットアクセス
input_msg.values.history.x     # リストや辞書もドットアクセス

states(状態)

セッション全体で共有される永続的なデータにアクセスします。

Jinja2・Lambda式・Async関数 :

states.phase                    # ドットアクセス
states.conversation.count       # 階層的なドットアクセス
states.get('key')     # 未定義の場合は、`None`が返る

また、prev_states(前回の処理時のstatesの状態)も利用可能です。状態の変化を検出する際に使用します(詳細は後述)。

: 前回との差分で状態変化を検出する

# phaseが変化したかどうかを判定
lambda: prev_states and prev_states.conversation.phase != states.conversation.phase

dialog(会話履歴)

会話履歴にアクセスします。記法によってアクセス方法が異なります

Jinja2・Lambda式:

Jinja2やLambda式では、dialogのメソッド呼び出しは 事前実行 されます。つまり、式が評価される前に結果が埋め込まれます。

dialog.get_recent(limit=10)     # メソッド呼び出し(事前実行される)
dialog.get(index=-1, role="user")  # メソッド呼び出し

Async関数:

Async関数では、dialoginput_msg.dialogとしてアクセスし、 非同期処理await)が必要です。これにより、リアルタイムで会話履歴を取得・更新できます。

書き込みにはDialogEntryを作成してadd_entry()メソッドを使用します。DialogEntryrole("user", "assistant", "system"など)とtext(発話内容)を指定します。

# 読み取り
await input_msg.dialog.get_recent(limit=10)  # input_msg経由で非同期アクセス
await input_msg.dialog.get(index=-1, role="user")

# 書き込み
entry = DialogEntry(role="assistant", text="こんにちは")
step = await input_msg.dialog.add_entry(entry)  # 会話履歴に追加

利用可能なモジュール

Async関数では、以下のモジュールが名前空間に含まれており、import文なしで使用できます:

  • math - 数学関数(sin, cos, sqrt, ceil, floorなど)
  • random - 乱数生成(randint, choice, uniformなど)
  • datetime - 日時処理(datetime, timedelta, nowなど)
  • time - 時刻処理(time.time()など)
  • json - JSON処理(dumps, loads)
  • re - 正規表現(match, search, sub, findallなど)
  • base64 - Base64エンコード/デコード
  • hashlib - ハッシュ関数(md5, sha256など)
  • itertools - イテレータツール(chain, combinations, permutationsなど)
  • collections - コレクション型(Counter, defaultdict, dequeなど)

その他、基本的な組み込み関数(len, str, int, list, dictなど)も利用可能です。


prev_states(前回のステート)

prev_statesは、 前回の処理時のステートの状態 を保持する変数です。各Itemの処理が完了した後に、現在のstatesprev_statesとして保存されます。

主な使用例:

  • DialogAddのconditionパラメータ - 状態が変化したときのみメッセージを追加
  • Gateのtriggerパラメータ - 状態が変化したときのみ処理を実行
  • StateSetのupdate_statesパラメータ - 前回の状態を参照して更新
  • Entryの出力式 - 前回の状態を参照して処理を分岐
  • その他の自由式を使用するすべてのItem - 状態の変化を検出して処理を制御

制御コマンド(Skip, Continue, Break)

クラス 対象 動作 用途 使用可能な記法
Skip() 項目 その項目をスキップ 条件に合わない項目の更新を防ぐ Lambda式のみ
Continue() ループ 次のイテレーションへ ストリーミング処理で条件待ち Async関数(Realtimeモード時)のみ
Break() ループ ループを終了 処理の早期終了 Async関数(Realtimeモード時)のみ

Skip()

対象 : Lambda式
用途 : 項目の更新・送信をスキップ(項目レベルのスキップ)
動作 : その項目の更新や送信を行わず、次の項目に進みます。

使用例 :

# StateSetChain: 値が空の場合は状態更新をスキップ
{
  "user.name": "lambda values: values.name if values.name else Skip()"
}

# EventResponseChain: 条件に合致しない場合は送信をスキップ
{
  "message": "lambda values: values.text if values.enabled else Skip()"
}

Continue()

対象 : Async関数(Realtimeモード)
用途 : ループのイテレーションをスキップ(for文のcontinue相当)
動作 : 現在のイテレーションをスキップして、次のイテレーションに進みます。

使用例 :

# ClassifierChain (realtime): 【】が閉じるまで待機
async def get_text_when_ready(input_msg, output_msg):
    text = input_msg.values.response.x or ''

    # 【】が閉じていない場合はContinue()
    if '【' in text and '】' not in text:
        return Continue()

    # 【】が閉じている場合、xxxを抽出
    import re
    match = re.search(r'【(.+?)】', text)
    return match.group(1) if match else text

Break()

対象 : Async関数(Realtimeモード)
用途 : ループを終了(for文のbreak相当)
動作 : ループを終了して、処理を完了します。

使用例 :

# ClassifierChain (realtime): 終了マーカーでループ終了
async def get_text_with_end_marker(input_msg, output_msg):
    text = input_msg.values.response.x or ''

    # 終了マーカーがある場合はループを終了
    if '[END]' in text:
        return Break()

    return text


ChatGPTへの質問テンプレート

ChatGPTに適切な構文作成依頼をするためのプロンプトテンプレートです。コピーして、目的に合わせて一部を書き換えて使用してください。

Jinja2テンプレート用プロンプト
Marionetteという会話型AIアプリケーション開発フレームワークで利用する、Jinja2テンプレートを作成してください。

【利用可能な変数】
- `values`: 前のItemから渡されたデータ(ドットアクセス可能、例: `values.name`, `values.count`)
- `states`: セッション全体で共有される永続的なデータ(ドットアクセス可能、例: `states.phase`, `states.conversation.count`)
- `prev_states`: 前回の処理時の`states`の状態(ドットアクセス可能、例: `prev_states.conversation.phase`)
- `dialog`: 会話履歴(メソッド呼び出しは事前実行される)

【dialogで利用可能なメソッド】
- `dialog.get_recent(limit=10)`: 最新の会話エントリを取得(古い順で返る)
- `dialog.get(index=-1, role="user")`: インデックスと役割でエントリを取得(負のインデックスは最新から)
  - `role=None`: すべての役割を対象
  - `role="user"`: 単一の役割を指定
  - `role=["user", "assistant"]`: 複数の役割を指定
- `dialog.get_last_assistant_step()`: 最後のアシスタントエントリのステップ番号を取得
- `len(dialog)`: エントリ数を取得

【DialogEntryの属性】
各エントリは以下の属性を持ちます:
- `entry.role`: 役割("user", "assistant", "system"など)
- `entry.text`: 発話内容
- `entry.meta`: メタデータ(ReadOnlyDotDict形式)
- `entry.timestamp`: タイムスタンプ

【作成依頼】
[ここに目的を記載してください。例: 「values.scoreが80以上なら「合格」、それ以外は「不合格」と表示する」]
[また、利用可能なvalues(直前のEdgeをクリックすると取得できる)や、利用したいstatesがあれば、箇条書きで記入してください]
Lambda式用プロンプト
Marionetteという会話型AIアプリケーション開発フレームワークで利用する、Lambda式を作成してください。

【利用可能な変数】
- `values`: 前のItemから渡されたデータ(ドットアクセス可能、例: `values.name`, `values.count`)
- `states`: セッション全体で共有される永続的なデータ(ドットアクセス可能、例: `states.phase`, `states.conversation.count`)
- `prev_states`: 前回の処理時の`states`の状態(ドットアクセス可能、例: `prev_states.conversation.phase`)
- `dialog`: 会話履歴(メソッド呼び出しは事前実行される)

【dialogで利用可能なメソッド】
- `dialog.get_recent(limit=10)`: 最新の会話エントリを取得(古い順で返る)
- `dialog.get(index=-1, role="user")`: インデックスと役割でエントリを取得(負のインデックスは最新から)
  - `role=None`: すべての役割を対象
  - `role="user"`: 単一の役割を指定
  - `role=["user", "assistant"]`: 複数の役割を指定
- `dialog.get_last_assistant_step()`: 最後のアシスタントエントリのステップ番号を取得
- `len(dialog)`: エントリ数を取得

【DialogEntryの属性】
各エントリは以下の属性を持ちます:
- `entry.role`: 役割("user", "assistant", "system"など)
- `entry.text`: 発話内容
- `entry.meta`: メタデータ(ReadOnlyDotDict形式)
- `entry.timestamp`: タイムスタンプ

【Lambda式の引数】
values, states, dialog, prev_statesはすでにnamespaceに読み込んでいるので、`lambda: ...`と引数なしで書いてください。

【利用可能なモジュール(import不要)】
- `math`, `random`, `datetime`, `time`, `json`, `re`, `base64`, `hashlib`, `itertools`, `collections`

【その他の利用可能な関数】
- 基本的な組み込み関数(`len`, `str`, `int`, `list`, `dict`, `min`, `max`, `sum`, `sorted`など)
- `Skip()`: 項目をスキップする(条件に合わない場合に使用)

【注意事項】
- Lambda式は1行で記述します
- `prev_states`は最初の処理時には`None`の可能性があるため、`prev_states and ...`のように`None`チェックが必要です

【作成依頼】
[ここに目的を記載してください。例: 「states.phaseが1かどうかを判定する」]
[また、利用可能なvalues(直前のEdgeをクリックすると取得できる)や、利用したいstatesがあれば、箇条書きで記入してください]
Async関数用プロンプト
Marionetteという会話型AIアプリケーション開発フレームワークで、Async関数を作成してください。

【関数の基本形】
async def process(input_msg, output_msg):
    # 処理
    await output_msg.update("result", result, is_last=True)

【利用可能な変数】
- `input_msg.values.name.x`: 前のItemから渡されたデータ(`.x`で値を取得)
- `input_msg.dialog`: 会話履歴(非同期アクセス)
- `states`: セッション全体で共有される永続的なデータ(ドットアクセス可能、例: `states.phase`)
- `prev_states`: 前回の処理時の`states`の状態(ドットアクセス可能)

【dialogで利用可能なメソッド(非同期アクセス)】
- `await input_msg.dialog.get_recent(limit=10)`: 最新の会話エントリを取得(古い順で返る)
- `await input_msg.dialog.get(index=-1, role="user")`: インデックスと役割でエントリを取得(負のインデックスは最新から)
  - `role=None`: すべての役割を対象
  - `role="user"`: 単一の役割を指定
  - `role=["user", "assistant"]`: 複数の役割を指定
- `await input_msg.dialog.get_diff(from_step=5, to_step=10)`: ステップ範囲のエントリを取得
- `await input_msg.dialog.get_current_step()`: 現在のステップ番号を取得
- `await input_msg.dialog.get_last_assistant_step()`: 最後のアシスタントエントリのステップ番号を取得

【よく使うパターン】
- エントリ数取得: `len(await input_msg.dialog.get_recent(limit=1000))`
- 最新エントリ取得: `await input_msg.dialog.get(index=-1)`

【DialogEntryの属性】
各エントリは以下の属性を持ちます:
- `entry.role`: 役割("user", "assistant", "system"など)
- `entry.text`: 発話内容
- `entry.meta`: メタデータ(ReadOnlyDotDict形式)
- `entry.timestamp`: タイムスタンプ

【会話履歴への書き込み】
entry = DialogEntry(role="assistant", text="こんにちは")
step = await input_msg.dialog.add_entry(entry)

`DialogEntry`は名前空間に含まれているため、`import`不要です。

【出力の更新】
await output_msg.update("key", value, is_last=True)

【利用可能なモジュール(import不要)】
- `math`, `random`, `datetime`, `time`, `json`, `re`, `base64`, `hashlib`, `itertools`, `collections`

【その他の利用可能な関数】
- 基本的な組み込み関数(`len`, `str`, `int`, `list`, `dict`, `min`, `max`, `sum`, `sorted`など)

【Realtime処理時に使える関数】
本関数が呼び出される際、`input_msg.values.xxx`が逐次的に更新されている場合があります (Realtimeモード)。
例えば、「こ」「こんに」「こんにちは、」「こんにちは、私は」...のような感じで、次々と文字列等が増えていきます。
Realtimeモードのときには、そのすべてのターンで本関数が呼び出されます。
とはいえ、例えば「、」が登場するまで何もしたく無いというときや、「。」が来たら終了させる、といった処理を行いたいケースに備え、以下の2つの関数が定義され、呼び出すことができます。
- `Continue()`: 本ループでは何もせず、ループの次のイテレーションへ
- `Break()`: ループを終了

【注意事項】
- `input_msg.values.name`は`Value`オブジェクトを返すため、実際の値にアクセスするには`.x`が必要です (`input_msg.values.name.x`)
- `dialog`へのアクセスは`await`が必要です
- `prev_states`は最初の処理時には`None`の可能性があるため、`None`チェックが必要です

【作成依頼】
[ここに目的を記載してください。例: 「会話履歴から最新10件を取得して、userロールのメッセージのみを抽出する」]
[また、利用可能なvalues(直前のEdgeをクリックすると取得できる)や、利用したいstatesがあれば、箇条書きで記入してください]