コンテンツにスキップ

基礎知識

ドラマ作成者向けガイド - 基礎知識

このガイドでは、Marionetteでドラマを作成するための基礎知識を学びます。


ドラマとは

ドラマ は、Marionetteの会話シナリオです。
ドラマは、様々な Item をツリーのようにつなげることで、任意のシナリオを実現します。

ドラマのツリーは、AI発話を生成する度に一回呼ばれます 。シナリオ遷移ではなく、発話生成の度に上から下へ流れるように実行されます。
クライアント側からは、以下の順序で処理が呼ばれます:

  1. まず、ユーザーの発話や、ユーザーの状態をドラマに設定 (dialog.add, state.set)
  2. AI発話生成をトリガー (event.trigger)

ドラマ側では、このトリガを受け取り、最終的にAI発話や音声 を返すことが基本動作です (event.response)

graph TD
    A[Item1<br/>Entry] --> B[Item2<br/>DialogAdd]

    style A fill:#c8e6c9,stroke:#4caf50,color:#1b5e20
    style B fill:#e1bee7,stroke:#9c27b0,color:#4a148c
  • Itemは、箱で表されている要素。音声合成やRAGなど、特定の処理を行います
  • Linkは、Item同士の処理の順序を表します。その他にもいくつか機能を持っています:
    • 前のItemの処理結果の情報を次のItemに伝えます (Value)
    • 前のItemの処理が完了したかどうかを、次のItemに伝えます (is_last)

Entry

Entry は、ドラマの開始点となるItemです。外部からのイベント(event.trigger)を受け取り、ドラマの処理を開始します。すべてのドラマは、必ずEntryから始まります。

Group

Group は、複数のItemを組み合わせて再利用可能なコンポーネントとして扱う機能です。よく使う処理パターンをグループ化することで、ドラマの作成効率を向上できます。

Groupは画面上「タブ」として表現されています。「+」ボタンを押すことで追加可能です。
追加後は通常通りItemを追加していきますが、Group特有の特殊な概念も存在します:

  • GroupInを追加。そこが処理の開始地点となり、Groupの前の処理の流れやデータを受け取る
  • Group以降に出力したいデータや処理の流れがある場合は、GroupOutを追加し、そこにLinkをつなげる

重要

Groupでは、グループ専用の環境変数を設定できます。自由式の中で ${変数名} として参照すると、その値が展開されます。

同じGroupを複数の場所で使う際に、環境変数に異なる値を設定することで、同じ処理パターンを使いながら、それぞれ異なる挙動を実現できます。

使用例 : キャラクター発話用のGroupを作成し、環境変数 ${ai_name} を設定します。このGroupを2箇所で使用する際、一方では ai_name = "Tsuyoshi"、もう一方では ai_name = "Koichi" と設定することで、同じGroupを使いながら異なるキャラクターとして振る舞わせることができます。


DialogとStates

クライアントとサーバー側双方から参照と書き換えができる共通データベースとして、dialogstatesが用意されています。

  • dialog: 会話履歴を保持します。ユーザーとアシスタントの会話だけでなく、システムメッセージなども含みます。
  • states: なんでも保存できる変数です。任意の変数名を使えますし、ドットによる階層化も可能です。使用例:
    • ユーザー情報の保存: user.nickname, user.age
    • 前回会話の要約: conversation.summary
    • 会話進行管理: greeting.done, start_timestamp

これらは、内容が変化すると、サーバー・クライアント側にすぐに通知されます。

重要

Statesとは別に、そのツリーの中で前のItemから伝わってくる一時的な情報を扱うValueという概念存在します。Valueはクライアント側には伝わらない内部変数です。
Marionetteの中では、このDialog, States, Valueの3つを組み合わせながら、あらゆる処理を行います。

永続States(Persistent States)

通常の States はセッション(1回の会話)が終了すると消えます。しかし、ユーザーの名前や年齢、累計ポイントなど セッションをまたいで保持したい値 もあります。

永続 States を使うと、指定した States のキーが プレイヤー × ドラマ の組み合わせごとにデータベースへ自動保存され、次回のセッション開始時に自動的に復元されます。

sequenceDiagram
    participant P as プレイヤー
    participant S as セッション
    participant DB as データベース

    Note over P,DB: セッション開始
    DB->>S: 保存済みStatesを読み込み
    S->>P: 復元した値で会話開始

    Note over P,DB: 会話中(States変更)
    P->>S: states.user.age = 25

    Note over P,DB: セッション終了
    S->>DB: persist: true のキーを保存
概念
用語 意味
永続States セッション終了時にDBへ保存され、次回セッション開始時に復元されるStates
プレイヤー エンドユーザーの識別単位。同一プレイヤーであれば異なるセッションでも同じ保存値を共有する
initial セッション開始時に保存値が見つからなかった場合に使われる初期値。YAMLに Pythonリテラル として記述する
persist true のキーのみDBに保存される。false または未設定のキーは毎回 initial から始まる

YAMLが正(Source of Truth)

persistfalse に変更した場合や、YAMLから該当エントリを削除した場合は、過去にDBへ保存された値は使われず、常に initial の値が適用されます。YAMLの設定が最優先です。

ドラマでの指定方法

ドラマYAMLの meta セクションに persistence.states を追加します。

meta:
  persistence:
    states:
      entries:
        - key: user.name
          initial: "no_name"
          persist: true

        - key: user.age
          initial: 0
          persist: true

        - key: session.greeting_done
          initial: False
          persist: false

各フィールドの意味:

フィールド 必須 説明
key string States のキー名。ドット区切りでネスト可能(例: user.age
initial any 初期値。Pythonリテラルとして解釈される(0 → 整数, True → 真偽値, None → null)
persist bool true でDBに永続化。false または省略で毎セッション initial にリセット

initial の型について

initial の値は Python の ast.literal_eval で解釈されます。そのため、Python と同じリテラル表記が使えます:

YAML での記述 解釈される値 Python の型
0 0 int
3.14 3.14 float
True / False True / False bool
None None NoneType
"hello" "hello" str
[1, 2, 3] [1, 2, 3] list
{"a": 1} {"a": 1} dict

よくある使い方

  • ユーザープロフィール : user.name, user.agepersist: true にし、会話中にヒアリングした情報を次回以降も覚えておく
  • 累計カウンター : stats.total_conversationspersist: true, initial: 0 にし、会話のたびにカウントアップ
  • 一時フラグ : session.greeting_donepersist: false, initial: False にし、毎セッション初回だけ挨拶を行う

自由式

Marionetteでは、柔軟な処理を実現するために、自由に式を記入できる箇所が多く存在しています。

Jinja2

テキスト入力を柔軟に行うための書き方です。

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

Lambda式(短い処理向け)

lambda values: values.count + 1
lambda: states.phase == 1

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)

重要

詳細は、式の書き方ガイドを参照してください。
また、これらはいずれも一般的なプログラミング言語のため、ネット検索やChatGPT等で自分で記述することも可能です。