自由式の書き方
自由式の書き方ガイド¶
Marionetteでは、Jinja2、Lambda式、Async関数の3つの記法を使って処理を記述できます。
これをまとめて、「自由式」と呼びます。
| 項目 | Jinja2 | Lambda式 | Async関数 |
|---|---|---|---|
| 用途 | 文字列フォーマット変換 | 情報フィルタリング・フォーマット変換 | 高度な処理分岐。幅広い関数の利用 |
| 関数 | 文字列操作のみ | 一般的なPython関数 | 一般的なPython関数 |
| Value | △ Read | ✓ Read/Write | ✓ Read/Write |
| States | ✓ Read | ✓ Read | ✓ Read/Write |
| Dialog | △ Read | △ Read | ✓ Read/Write |
記法の例¶
Jinja2¶
文字列に対して、出し分けやフォーマットの変換を行います。
例 : values.score が80以上であれば「合格」、そうでなければ「不合格」と出力
例 : roleがsystemとなっているものを除いて、user: xxxの形式で会話履歴を構成
{% for message in history %}
{% if message.role != "system" %}
{{ message.role }}: {{ message.text }}
{% endif %}
{% endfor %}
重要
Jinja2の出力は、常に 文字列 です。
また、自由式の中では、lambdaやasyncが無いと自動的にJinja2と判断されます。
よくあるエラー
よくあるエラー: 例えばTrueと文字を書いた場合でも、それはbool型ではなく文字列として解釈されます。bool型としてのTrueを返す必要がある場合は、lambda: Trueという表記を利用してください。
Lambda式¶
値の計算、簡単な変換、条件判定を行います。
lambda: ...という書き方で式を書きます。
例 : states.phaseが1かどうかを判定する。出力はbool型のTrue/False
例 : 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分経過しているかどうかを判定する
重要
標準的なPython組み込み関数はあらかじめimportされており、利用することが可能です。
Lambda式の引数は禁止
重要: Lambda式では引数を取る形式は**使用禁止**です。values, states, dialog, prev_statesは名前空間に自動的に読み込まれているため、引数として渡す必要はありません。
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 input_msg.is_last():
break
await output_msg.update("result", result, is_last=True)
重要
標準的なPython組み込み関数はあらかじめimportされており、利用することが可能です。
また、非同期関数が利用できるため、データベースへのアクセスを伴うdialogへの書き込み操作を行うこともできます。
各変数の利用方法¶
自由式では、以下の変数にアクセスできます。アクセス方法は記法によって異なります。
values(入力値)¶
前のItemから渡されたデータにアクセスします。
Jinja2・Lambda式:
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`が返る
書き込み(Async関数のみ):
Async関数では、input_msg.state.update_state()を使用してstatesを更新できます。
async def process(input_msg, output_msg):
# statesに新しい変数を追加・更新
await input_msg.state.update_state({
"user.name": "Alice", # 新しい変数を追加
"conversation.count": 5, # 既存の変数を更新
"settings.theme": "dark" # ネストした変数も可能
})
await output_msg.update("result", "done", is_last=True)
Lambda式・Jinja2では書き込み不可
Lambda式やJinja2では、statesへの直接書き込みはできません。statesを更新する必要がある場合は、Async関数を使用するか、StateSetChainコンポーネントを使用してください。
また、prev_states(前回の処理時のstatesの状態)も利用可能です。状態の変化を検出する際に使用します(詳細は後述)。
例 : 前回との差分で状態変化を検出する
# phaseが変化したかどうかを判定
lambda: prev_states and prev_states.conversation.phase != states.conversation.phase
dialog(会話履歴)¶
会話履歴にアクセスします。記法によってアクセス方法が異なります 。
読み取り(Jinja2・Lambda式):
Jinja2やLambda式では、dialogのメソッド呼び出しは 事前実行 されます。つまり、式が評価される前に結果が埋め込まれます。
読み取り・書き込み(Async関数):
Async関数では、dialogはinput_msg.dialogとしてアクセスし、 非同期処理 (await)が必要です。これにより、リアルタイムで会話履歴を取得・更新できます。
書き込みにはDialogEntryを作成してadd_entry()メソッドを使用します。DialogEntryはrole("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) # 会話履歴に追加
# metaを含めた書き込み
entry = DialogEntry(
role="system",
text="これはシステムメッセージです",
meta={"source": "tool", "timestamp": 1234567890}
)
await input_msg.dialog.add_entry(entry)
Lambda式・Jinja2では書き込み不可
Lambda式やJinja2では、dialogへの書き込みはできません。会話履歴を追加する必要がある場合は、Async関数を使用するか、DialogAddChainコンポーネントを使用してください。
利用可能なモジュール¶
Lambda式・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など)も利用可能です。
トークン関連関数(LLMチェーン専用)¶
GeppettoLLMChain、PublicLLMChain、SystemLLMChainのscene/promptテンプレート内では、テキストのトークン数を制御するための関数が利用できます。
利用可能な場所
これらの関数は**LLMチェーン(GeppettoLLMChain、PublicLLMChain、SystemLLMChain)のscene/prompt内でのみ利用可能**です。
他のチェーン(Gate、Parser等)では使用できません。
truncate() - テキストをトークン数で切り詰め¶
長いテキストを指定したトークン数に切り詰めます。LLMの入力長制限を超えないようにするために使用します。
構文:
引数:
| 引数 | 型 | 説明 |
|---|---|---|
text | str | 切り詰め対象のテキスト |
max_tokens | int | 最大トークン数 |
position | str | 切り詰め位置('start', 'end', 'middle')。デフォルトは'middle' |
position の動作:
| 値 | 説明 | 結果例(元: "ABCDEFGHIJ") |
|---|---|---|
'start' | 先頭を削除し、末尾を残す | "...GHIJ" |
'end' | 末尾を削除し、先頭を残す | "ABCD..." |
'middle' | 中間を削除し、先頭と末尾を残す | "AB...IJ" |
使用例(Jinja2):
{# 基本的な使い方 #}
{{ truncate(states.user.prompt, 100) }}
{# position を指定 #}
{{ truncate(states.context.history, 500, position='start') }}
{# 複数の値を組み合わせる #}
過去の会話:
{{ truncate(states.conversation.summary, 200, position='end') }}
ユーザーの質問:
{{ states.user.question }}
使用例(Lambda式):
count_tokens() - トークン数を計算¶
テキストのトークン数を計算します。デバッグや条件分岐に使用します。
構文:
引数:
| 引数 | 型 | 説明 |
|---|---|---|
text | str | トークン数を計算するテキスト |
戻り値: int - トークン数
使用例(Jinja2):
{# トークン数を表示(デバッグ用) #}
{# 現在のトークン数: {{ count_tokens(states.user.prompt) }} #}
{# 条件分岐に使用 #}
{% if count_tokens(states.context) > 1000 %}
{{ truncate(states.context, 500) }}
{% else %}
{{ states.context }}
{% endif %}
トークナイザーについて¶
使用されるトークナイザーは、LLMチェーンの設定によって自動的に決定されます:
| チェーン | トークナイザー |
|---|---|
| GeppettoLLMChain | HuggingFace transformers(tokenizer_model_idで指定) |
| SystemLLMChain | HuggingFace transformers(tokenizer_model_idで指定) |
| PublicLLMChain | tiktoken(provider/modelから自動判定) |
PublicLLMChainでのエンコーディング対応:
| プロバイダー/モデル | エンコーディング |
|---|---|
| OpenAI GPT-4o, o1, o3 | o200k_base |
| OpenAI GPT-4, GPT-3.5 | cl100k_base |
| Anthropic Claude | cl100k_base(近似) |
| Google Gemini | cl100k_base(近似) |
Anthropic/Geminiについて
AnthropicとGeminiは独自のトークナイザーを使用していますが、tiktokenのcl100k_baseで近似しています。
正確なトークン数ではありませんが、実用上は十分な精度です。
buffer(リアルタイムモード専用)¶
realtimeモードのAsync関数では、buffer という辞書が自動的に提供されます。
これは、関数が複数回呼び出される際に、呼び出し間で状態を保持するために使用します。
async def my_func(input_message, output_message):
# buffer は realtime モードで自動的に提供される辞書
# 初回呼び出し時に初期化
if 'count' not in buffer:
buffer['count'] = 0
buffer['prev_text'] = ""
# 状態を更新
buffer['count'] += 1
# 処理...
| ケース | 説明 | 例 |
|---|---|---|
| 差分検出 | 前回の入力と比較して新しい部分だけを処理 | 文字列の増分だけを抽出 |
| データ蓄積 | 複数回の入力を結合して処理 | 文区切りバッファリング |
| カウンター | 呼び出し回数や処理回数を記録 | デバッグ、制限処理 |
注意
buffer は **realtime モード専用**です。gate モードでは関数が一度しか呼ばれないため、状態保持の必要がありません。
prev_states(前回のステート)¶
prev_statesは、 前回の処理時のステートの状態 を保持する変数です。各Itemの処理が完了した後に、現在のstatesがprev_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.name if values.name else Skip()"
}
# EventResponseChain: 条件に合致しない場合は送信をスキップ
{
"message": "lambda: 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式を作成してください。
【重要な制約】
Lambda式は必ず「引数なし」で記述してください。`lambda x: ...`や`lambda values: ...`のような引数を取る形式は**禁止**です。
すべての変数(values, states, dialog, prev_states)は名前空間に自動的に読み込まれています。
```python
# ❌ 禁止
lambda x: x.text
lambda values: values.text
# ✓ 正しい形式
lambda: values.text
lambda: states.phase == 1
【利用可能な変数】
- 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: タイムスタンプ
【利用可能なモジュール(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`: 会話履歴(非同期アクセス)
- `input_msg.state`: 状態マネージャー(statesの読み書きに使用)
- `states`: セッション全体で共有される永続的なデータ(ドットアクセス可能、例: `states.phase`)※読み取り専用
- `prev_states`: 前回の処理時の`states`の状態(ドットアクセス可能)
- `buffer`: realtimeモード時に自動提供される辞書(呼び出し間で状態を保持)
【statesへの書き込み】
statesへの書き込みは `input_msg.state.update_state()` を使用します。
```python
# statesに変数を追加・更新
await input_msg.state.update_state({
"user.name": "Alice", # 新しい変数を追加
"conversation.count": 5, # 既存の変数を更新
"settings.theme": "dark" # ネストした変数も可能
})
【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など)
【buffer(Realtimeモード専用)】
realtimeモードでは、bufferという辞書が自動的に提供されます。
これは、関数が複数回呼び出される際に、呼び出し間で状態を保持するために使用します。
# 初回呼び出し時に初期化
if 'count' not in buffer:
buffer['count'] = 0
buffer['prev_text'] = ""
# 状態を更新
buffer['count'] += 1
【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チェックが必要です
- bufferはrealtimeモード専用です。gateモードでは利用できません。
【作成依頼】
[ここに目的を記載してください。例: 「会話履歴から最新10件を取得して、userロールのメッセージのみを抽出する」]
[また、利用可能なvalues(直前のEdgeをクリックすると取得できる)や、利用したいstatesがあれば、箇条書きで記入してください]
```