Misata: synthetic data library について
Misata は、Python を使うデータエンジニアやテスト作成者向けの宣言型合成データライブラリです。指定した制約から複数テーブルのデータを生成し、DuckDB で独立検証して再実行可能な証明を作成します。
主な機能
- 01YAML でデータ制約を宣言
- 02関連する複数テーブルを生成
- 03SQL DDL からデータを構築
- 04DuckDB で不変条件を再検証
- 05証明書と検証スクリプトを作成
- 06ストリーミングと分割出力に対応
活用シーン
- 01データパイプライン用の決定的なテストデータ作成
- 02dbt モデルの期待結果をテスト
- 03外部キーや台帳制約を満たすフィクスチャ生成
- 04検証可能な証明付きデータセットを共有
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Misata
成り立つべき条件を宣言する。その条件を満たすデータを得る。それを証明する。
MisataはPython向けの宣言型合成データジェネレーターです。月次の売上曲線から、バランスの取れた元帳、決して宙に浮かない外部キーまで、データセットが満たすべき事実を書き込むと、エンジンがそれらすべてを同時に満たす複数テーブルのデータを生成します。その後、DuckDBがディスク上のファイルを検証し、誰でも再実行できる証明書を書き出します。
60秒で試す
pip install "misata[duckdb]"
# misata.yaml
seed: 7
tables:
customers:
rows: 2000
columns:
customer_id: {type: int, unique: true, min: 1, max: 2000}
name: {type: text, text_type: name}
signed_up: {type: date, start: "2024-01-01", end: "2024-12-31"}
orders:
rows: 40000
columns:
order_id: {type: int, unique: true, min: 1, max: 1000000}
customer_id: {type: foreign_key}
channel: {type: categorical, choices: [web, app, store]}
amount: {type: float, min: 5, max: 900}
ordered_at: {type: date, start: "2025-01-01", end: "2025-12-31"}
relationships:
- customers.customer_id -> orders.customer_id
outcome_curves:
- table: orders
column: amount
time_column: ordered_at
start_date: "2025-01-01"
curve_points:
- {month: 1, target_value: 180000}
- {month: 6, target_value: 240000}
- {month: 12, target_value: 310000}
group_shares:
- table: orders
measure: amount
group_column: channel
shares: {web: 0.5, app: 0.3, store: 0.2}
misata generate -c misata.yaml -o data --attest
python data/verify.py
Attestation: data
result: PASSED
files pinned: 2
invariants held: 69 of 69
ok 2 file(s) match their attested sha256
ok 69 of 69 invariant(s) hold
この69件は、行が存在する前にYAMLから導出されます。12か月分の月次売上合計(1月は180,000.00、6月は240,000.00、12月は310,000.00で、その間は直線的に推移)、各月内のチャネル別内訳、月ごとの予定行数、孤立したcustomer_idがないこと、一意のキーです。verify.pyには標準ライブラリとduckdbだけが必要で、ジェネレーターとはコードを共有していません。
上記のYAMLを生成し、月およびチャネルごとに合計したものです。各棒は、チャネル別に分割した生成売上の1か月分を表し、上限はYAMLで宣言した合計です。この図は、このREADMEのYAMLを新たに実行し、readme_charts.pyで作成しています。
1行の1セントを変更すると、そのことが報告されます。
FAIL orders.csv: sha256 301165f9dc4e does not match the attested 670f89195826
FAIL orders.amount.month:1.outcome_curve_period: expected 180000.00, observed 180000.01
FAIL orders.amount.outcome_curve_total: expected 2945000.00, observed 2945000.01
FAIL orders.amount.web.2025-01.group_share_period: expected 90000.00, observed 90000.01
FAIL orders.amount.web.group_share_total: expected 1472500.00, observed 1472500.01
FAIL 65 of 69 invariant(s) hold
生成ではなく宣言する理由
すべてのテストフィクスチャには、正解をどう定めるかという問題があります。dbt モデル、Spark ジョブ、ダッシュボードのタイル、データエージェントを検証するには、入力データと、テスト対象のコードとは独立して把握している答えが必要です。本番データをコピーすれば、答えのないデータが手に入ります。手作業でフィクスチャを作れば、いずれ古くなる答えが手に入ります。ランダム生成器を使えば、テスト対象のコードで計算しなければ答えのわからないデータが手に入ります。
Misata はこの発想を逆転させます。仕様が正解の基準であり、行はそこから導き出されます。「1 月の売上は 180,000」というのは、1 行も存在する前から正しいため、パイプラインが 180,000.00 を返すか、バグがあるかのどちらかです。
flowchart LR
A["Declare<br/>curves, shares, ledgers,<br/>keys, lifecycles"] --> B{"Can they<br/>all hold?"}
B -- "no" --> R["Refused, with the<br/>arithmetic and a fix"]
B -- "yes" --> C["Build rows<br/>closed form where it can,<br/>by construction elsewhere"]
C --> D["Write<br/>CSV, Parquet, JSONL,<br/>DuckDB, Postgres"]
D --> E["Attest<br/>DuckDB re-runs every<br/>derived invariant"]
E --> F["Certificate<br/>in-toto Statement<br/>plus verify.py"]
オプション付きの生成器とは異なる点は、次の 3 つです。
- 宣言は守られるか、拒否されるかのどちらかで、ひそかに置き換えられることはありません。 すべての条件を同時に満たせない宣言があると、各宣言を明示し、計算過程を示して、変更案を提示するエラーが発生し、生成が停止します。
- 証明書は、データを書き込んでいないコードによって検証されます。 Misata の計算にバグがあると、自己整合性のある誤答ではなく、不変条件の検証失敗として表面化します。
- 同じ仕様、シード、バージョンを使えば、Linux、macOS、Windows で同じバイト列が得られるため、フィクスチャをフィンガープリントとともに git に保存できます。
目指すところ
Misata の目標は、どんな種類のデータセットを作る場合でも、誰も Python スクリプトを書かずに済むようにすることです。満たすべき条件を伝えると、エンジンが行を組み立て、成立しない条件は拒否し、生成したものを証明します。
まだ実現には至っていませんが、一つずつ着実に近づいています。リリースごとに、プログラミングの代わりに宣言できることが増えていきます。そして、次の三つのテストに合格したときだけ、ブロックが一つ積み上がります。数行の YAML で宣言できること、別の宣言と矛盾する場合は生成前に拒否されること、そして生成後に、生成時とは別のコードで行を再検証できることです。
各レンガは、コードの代わりに宣言できるものです。misata/registry.py から作成した一覧で、コントラクトテストによってコードがこの一覧を守るため、エンジンにないレンガが壁にあると主張することはできません。
一つの事実、二つの方法。 事実を成り立たせるためにスクリプトで行うことと、その代わりに記述する内容を示します。
| 事実 | ジェネレータースクリプトで行うこと | Misata での記述 |
|---|---|---|
| 1 月の売上は 180,000.00 です。 | 金額を生成し、合計し、再スケーリングして丸め、最後の 1 セントを調整する | {month: 1, target_value: 180000} |
| web は毎月の売上の半分を占めます。 | 月とチャネルごとに再スケーリングし、直前に崩した月ごとの合計を修正する | shares: {web: 0.5, app: 0.3, store: 0.2} |
| すべての注文に実在する顧客がいます。 | 親テーブルから ID をサンプリングし、両方の整合性を保つ | customers.customer_id -> orders.customer_id |
| 離脱した顧客の中には、先にアクティブだった顧客もいます。 | ステータスに応じて分岐し、タイムスタンプを順番に設定し、それ以外は NULL にして、手作業で数える | transitions: [[trialing, active], [active, churned]] |
| 送金の 1% は、一定の比率でマネーロンダリングのパターンを形成します。 | 各リングやファンインを手作業で構築し、背景データに独自のサイクルがないことを証明する | rate: 0.01 と shares: {cycle: 0.4, fan_in: 0.25, ...} |
| 試算表の残高はゼロです。 | すべての仕訳で両側に記帳し、最後に照合する | 仕訳明細に対する balanced_ledger 制約 |
まだコードが必要なものと、それぞれの状況:
| まだ手作業でスクリプトを書く必要があるもの | 状況 |
|---|---|
| Misata が逆算できないロジックの期待出力:ウィンドウ関数、カスタム SQL、ビジネスルール | misata solve は、実際の dbt、SQL、または Python パイプラインを動かし、指定された許容誤差内で宣言した数値に合わせます。それ以上の部分は、期待値を記述する必要があります |
| 例だけでは記述できない相関関係 | misata.mimic は実データから相関関係を学習しますが、その出力は匿名化されません |
| 独自の語り口を持つフリーテキスト | テキストは文法から生成され、行の内容と一致します。人が書いたように読める文章は、まだ宣言できません |
| バージョン履歴(SCD2)と在庫推移 | 残高が合うように生成されますが、段階ごとの宣言にはまだ対応していません |
| 文書、画像、音声 | 未着手 |
LIMITATIONS.md に全項目を記載しています。この表から項目がなくなるのは、それに代わるブロックが三つのテストすべてに合格したときです。
証拠
ガントレット:13 個のテーブルと 136 件の SQL アサーションを DuckDB で実行します。Faker スクリプトは、1 列ずつの事実に関する検証では同等ですが、列、行、テーブル間で整合性が必要な検証では劣ります。ソース。python -m benchmarks.gauntlet で再実行できます。
勾配ブースティング分類器で、生成された行と実データの行を見分けられるかを試します。5 つのシードの平均です。実データの行を一切見ずに、宣言されたスキーマから生成した場合、実際の注文データで学習した SDV より優れています。タクシー乗車では SDV が優れています。料金を知らずに生成するのは難題です。ソース、数値は summary.json にあります。
| 主張 | 測定結果 | 再現方法 |
|---|---|---|
| 構築時に有効性を保証 | 5 つの DDL スキーマで、キー、CHECK、UNIQUE、NOT NULL、自己参照ツリーに違反はありません。列ごとに生成する Faker スクリプトでは、スキーマごとに 15,014~50,416 件の違反が発生します。有効なデータで学習した SDV のマルチテーブルモデルでは 3,055~5,557 件の違反が発生し、5 つのうち 2 つは処理を拒否します | python -m benchmarks.validity_bench |
| 適合性 | 3 つのスイートで計 294 件の SQL アサーションをすべて満たしました。ガントレット 136 件、Warren 110 件、Ledger 48 件です | python -m benchmarks.gauntlet |
| 再現性 | Linux、macOS、Windows でフィンガープリントが一致します | python -m benchmarks.golden |
| 規模 | Apple M1 で 14 個のテーブルにまたがる 10,000,000 行を 36.3 秒で生成し、ピーク時のメモリ使用量は 0.78 GB です。その後、すべての外部キーを検証します | python benchmarks/bench_r3_volume.py --scale 10 --stream out/ |
宣言できる内容
各宣言には、テスト可能な保証、対象となる列の一覧、コスト、拒否条件があります。LANGUAGE.md を参照してください。25 種類すべての宣言について、行が作成される前に実現可能性を確認し、作成後にも行を再検証します。✓ が付いたものは DuckDB の証明書にも含まれます。
| 宣言 | 保証内容 | 証明書 |
|---|---|---|
outcome_curves | 期間ごとの指標合計がセント単位で一致する | ✓ |
rate_curves | 期間ごとのフラグ付き行の割合が、最も近い行単位で一致する | ✓ |
group_shares | 各期間内で、各グループの指標に占める割合が一致する | ✓ |
balanced_ledger | 仕訳ごとに借方と貸方が一致し、試算表がゼロになる | ✓ |
waterfalls | コンポーネントごとに、期首残高と増減の合計が期末残高に一致する | ✓ |
リレーション、min_children、partition_by | 孤立行がなく、子のない親がなく、テナントをまたいでキーが混在しない | ✓ |
unique、inequality、col_range、選択肢、NOT NULL、DDL CHECK | ルールに違反する行がない | ✓ |
lifecycles | ステータスに応じた履歴全体が正しく、適切なタイムスタンプが順序どおりに設定され、それ以外は NULL になる | ✓ |
dag_edges、graph_motifs | あらかじめ仕込んだケース以外にサイクルがなく、各リング、ファン、チェーンが正確な件数になる | ✓ |
ロールアップ、stock_flows、scd2、bitemporal、when_then、lte_parent | 親が子の値と一致し、在庫が収支と一致し、バージョンがタイムラインを隙間なく埋める | |
outliers、typos、duplicates、missingness、late_arrivals | 汚れたデータが正確な件数で含まれ、正解キーがある | |
retention、joint_distributions、time_grids、event_logs、processes | コホート曲線、同時分布の周辺分布、宣言された時刻基準、ステータスと一致するログ |
どの宣言でも、その前提として外部キーは生成時に解決され(親を子より先に作成)、テキストは行の内容と一致し、金額は偏った分布になり、タイムスタンプには日次のリズムがあり、少数の顧客が注文の大部分を行います。そのため、何も宣言しなくても、データはビジネスデータらしく見えます。文章を指定すると、SaaS やフィンテックから医療、物流、ストリーミングまで、18 種類のストーリードメインから選ばれ、それぞれのテーブル、キー、調整済み分布が使われます。
エントリーポイントを選ぶ
| 用意するもの | 実行方法 | 備考 |
|---|---|---|
| 文章 | misata.generate("A SaaS company with 1,000 customers", seed=42) | 最も手早く始められます。テーブルを表示するか、misata explain を実行すると、生成された内容を確認できます |
| YAML または dict | misata.generate_from_schema(schema) | すべての宣言が成立する場合にのみ生成され、成立しない場合は拒否されます |
| SQL DDL | misata.from_ddl(ddl) または misata generate -c schema.sql | NOT NULL、UNIQUE、複合キー、CHECK 範囲と enum、自己参照に対応します |
| 稼働中のデータベース | misata seed postgresql://... | スキーマを調査し、1 つのトランザクションで挿入します。--truncate を渡さない限り、テーブルを切り詰めません |
| dbt プロジェクト | misata dbt-unit-test --select my_model | キーが一致する manifest.json からフィクスチャを生成します |
| Prisma または Django | misata prisma-seed、misata.from_django(app_labels=[...]) | |
| メモリに収まらない行数 | misata.generate_stream(schema) | (table, batch) を順に生成します。パーティション分割された Parquet では、パーティションごとに 1 ファイルを作成します |
| 出力を指定した数値に一致させる必要があるパイプライン | misata solve -c solve.yaml | 実際の dbt または Python パイプラインに入力を渡し、指定された許容誤差の範囲内で宣言された結果を達成します |
| 共有するデータセット | misata publish data --hf you/name | 再検証してから、仕様、証明書、検証ツールとともにデータをアップロードします |
| 模倣する実データ | misata.mimic(df) | 実データに合わせて学習されます。匿名化はされません |
毎回シードを渡してください。同じシードなら、同じデータが生成されます。
12 のレシピ
それぞれのフォルダーには、スキーマ、コマンド、宣言が守られていることを証明するチェックが含まれています。tools/stranger.py --clean は、リリースのたびに、クリーンな仮想環境で新しくビルドした wheel から、すべてのページに記載されたコマンドをすべて実行します。そのため、ページに記載された数値は実際に確認できる数値です。
| レシピ | 対象 | 証明 |
|---|---|---|
| 開発用データベースにデータを投入する | アプリ開発者、コーディングエージェント | 3 つの CREATE TABLE 文から導かれる 22 個の不変条件 |
| dbt の既知解テスト | アナリティクスエンジニア | 月末日のバグを検出する dbt テスト |
| ユーザーを迎える前のデータウェアハウス | 創業者、データリーダー | パーティション分割された Parquet の 291,000 行、80 個の不変条件 |
| デモ用テナント | セールスエンジニア、PM | 解約済みと健全なアカウント、少数の大口顧客。リアリティ 0.96 |
| 評価用データベース | AI 研究所、評価チーム | ローテーションされた 3 つのデータベースで検証したテキストから SQL への回答 |
| エージェント用サンドボックス | エージェントの開発者 | サンドボックスと採点ツールが行ごとに一致 |
| 負荷テスト | プラットフォームエンジニア | ホットキーと日次カーブ。再生前にキーをチェック |
| 不正対策と AML | リスクチーム、ML チーム | 非循環の背景グラフと、宣言された件数どおりの各リング |
| 照合可能な台帳 | 財務エンジニアリング、監査 | 2 会計年度、52 個の不変条件、試算表の差額ゼロ |
| 持ち帰り課題 | 採用チーム、コース作成者 | 候補者ごとに異なる解答キーを用意し、それぞれ検証 |
| PII を含まない環境 | 規制対象のチーム | 本番の DDL から作成したステージングデータ。本番の行は使用しません |
| ドキュメント用のサンプルデータ | DX、API、SDK チーム | ファイル間で ID を解決できるネストされた JSONL |
履歴を示すステータス
ステートマシンを一度宣言すれば、各行のタイムスタンプはステータスに基づいて決まります。デモ用テナントから:
stateDiagram-v2
direction LR
[*] --> trialing
trialing --> active
trialing --> churned
active --> past_due
active --> churned
past_due --> churned
lifecycles:
- name: subscription
table: subscriptions
state_column: status
start_column: started_at
initial: trialing
states:
- {name: trialing}
- {name: active, timestamp: activated_at}
- {name: past_due, timestamp: past_due_at}
- {name: churned, timestamp: churned_at, terminal: true}
transitions:
- [trialing, active]
- [trialing, churned]
- [active, past_due]
- [active, churned]
- [past_due, churned]
weights: {trialing: 0.07, active: 0.6, past_due: 0.07, churned: 0.26}
1,200 件のサブスクリプションのうち、アクティブがちょうど 720 件、解約済みが 312 件、支払い遅延が 84 件、トライアル中が 84 件です。重みは確率ではなく件数であり、最大剰余法で割り当てられます。支払い遅延の各行には、有効化日と支払い遅延日がこの順序で記録され、いずれも開始日より後になっています。アクティブな行に解約日はありません。証明書は両方を行ごとにチェックします。複数の経路で到達可能な状態では、行はすべての経路に分配されます。解約済みの 312 件のうち、156 件はトライアル中に、78 件はアクティブになった後に、78 件は支払い遅延になった後に解約しています。
デモ用テナントの 1,200 件のサブスクリプションを、設定されているタイムスタンプごとに分類しています。6 つの経路があり、どの行もその範囲外にはありません。レシピ独自の YAML から readme_charts.py で描画しています。
拒否されるとき
$ misata explain bad.yaml
Generation would refuse: 1 conflict(s).
1 declaration cannot be satisfied together:
1. [orders.channel] group_shares on amount
declared shares sum to 1.500 (web=0.6, app=0.6, store=0.3); a share of a whole cannot exceed 1.0
→ Suggestion: scale the shares so they sum to 1.0, or drop a group. Misata will not renormalise them for you, because the result would be a specification you did not write
Nothing was generated. Change the declarations each conflict names; its remedy says how.
misata explain は、予定されている行、各宣言が書き込む内容、証明書に含まれる不変条件の数、すべての競合を出力しますが、何も生成しません。すべてのコマンドで --json を指定でき、単一のエンベロープを出力します。終了コード 3 は、宣言をすべて成立させることができず、何も書き込まれなかったことを意味します。
コーディングエージェントと使う
エージェントがスキーマを設計し、Misata が計算を行い、DuckDB がチェックします。エージェントは自分の出力を認証できません。これは、エージェント自身が書いたものではない答えを得る方法です。
claude mcp add misata -- uvx --from "misata[mcp]" misata-mcp # Claude Code
misata mcp install --client all # Cursor, Windsurf, Claude Desktop
15 個のツールがあります:generate_from_schema、generate_dataset、explain_schema、
validate_yaml、inspect_schema、preview_story、list_domains、
validate_domain、seed_database、create_sandbox、query_sandbox、
solve_outcome、attest_dataset、audit_dataset、check_realism。
AGENTS.md では、どの作業にどのエントリーポイントが適しているか、
また、エビデンスがどの主張を裏付けるかをエージェントに説明しています。
これは何ではないか
Misata は、依頼されない限り何も模倣しません。データから同時分布を学習することはありません。特定の非公開テーブルの統計的な双子が必要な場合は、明示されたイプシロンを持つ差分プライバシー対応の合成データ生成ツールを使用してください。mimic は実データの行に適合するため、匿名ではありません。
宣言できる内容(合計、率、割合、集計、台帳、キー、ライフサイクル)については正確ですが、任意の SQL に対応するわけではありません。ウィンドウ関数や、Misata が逆算できないビジネスロジックに基づくモデルでも、整合性があり、キーが有効な入力は得られますが、期待される出力は自分で記述します。misata solve は、入力を実際のパイプラインに通して、指定した許容誤差の範囲内で宣言された結果を導くことで、この隔たりの一部を埋めます。証明書には、その許容誤差が記載されます。
事前分布は英語を優先し、金額については米国寄りです。また、率は行全体の値に丸められます。LIMITATIONS.md に制限事項の全リストがあり、他のドキュメントと食い違う場合はこれが優先されます。
| 命令型 | 推論型 | 宣言型 | |
|---|---|---|---|
| 渡すもの | 値を出力するコード | 適合させるための実データ | 成立しなければならない事実 |
| 保証されること | 事後の保証はなし | おおよその類似性 | 事実の正確な成立、または拒否 |
| 外部キー | 自分で対処 | テーブルごとなら問題ないが、テーブル間では不安定 | 構築時に保証 |
| 実データが必要か | いいえ | はい | いいえ |
| 例 | Faker、Mockaroo、LLM シードスクリプト | SDV、学習型合成データ生成ツール | Misata |
引用
Misata が役立った場合は、このソフトウェアを引用してください。GitHub の このリポジトリを引用 ボタンは、同じメタデータを CITATION.cff から読み込みます。
@software{rasin_misata,
title = {Misata: declarative synthetic data},
author = {Rasin, Muhammed},
url = {https://github.com/rasinmuhammed/misata}
}
貢献
git clone https://github.com/rasinmuhammed/misata && cd misata
uv sync --all-extras && uv run pytest -q
python tools/stranger.py --clean # every documented command, from the built wheel
機能として採用されるのは、宣言して検証できるものだけです。benchmarks/ の適合性テストスイートが仕様です。まずアサーションを書き、失敗することを確認してから、パスするように実装してください。MIT ライセンスです。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。