chunk-invariance について
chunk-invariance は、Python のストリーミングフィルターが分割方法にかかわらず、全体を一度に処理した場合と同じ出力になるかを検証します。レダクターやガードレール、リライターの開発者向けで、任意の MCP サーバーから検査や pytest の生成も行えます。
主な機能
- 01分割ごとの出力を検証
- 02最初に失敗する分割を報告
- 03同期・非同期フィルターに対応
- 04pytest 回帰テストを生成
- 05任意の Hypothesis ストラテジー
活用シーン
- 01分割された秘密情報の見逃しを検査
- 02ストリームと一括処理の出力を比較
- 03分割境界の回帰テストを追加
MCP 対応
任意の MCP サーバーが、ストリーミング関数の検査、pytest 回帰テストの生成、チャンク単位の漏えいに関する説明をツールとして公開します。関数はローカルの別プロセスでインポートして実行します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
3掲載時のツール一覧 · 1 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| shape | enum | いいえ | generator:target(chunks) はテキストチャンクのイテラブルを受け取り、出力チャンクを生成または返します。per_chunk:target(chunk, state) は1つのチャンクの出力を返します。state はストリーム全体で保持される dict です。ストリーム終了時の処理は flush_target で指定します。async_generator:target はチャンクの非同期イテラブルを受け取り、出力を生成します。generator | per_chunk | async_generator |
| python | any | いいえ | ユーザーのコードをインポートするインタープリターです。デフォルト:project_dir/.venv または project_dir/venv が存在する場合はそれを使用し、なければサーバー独自の Python を使用します。 |
| splits | any | いいえ | 'all-two-part'(デフォルト):テキスト全体と、すべての単一分割位置。3以下の整数:最大で指定数のパートに分割するすべての方法(低速)。 |
| target | string | はい | ストリーミングフィルターを package.module:function の形式で指定します。ユーザーのテストと同じ方法で project_dir からインポート可能である必要があります。 |
| samples | any | いいえ | フィルターがマスキングまたは書き換えるべき値を含む入力テキストです。デフォルト:合成サンプル(メールアドレス、SSN、カード番号、API キー形式。すべて偽物):['Contact [email protected] about the invoice.', 'SSN on file: 987-65-4321 (fake).', 'Card 4111 1111 1111 1111 exp 12/30 (test card).', 'Use key sk-FAKEab12cd34ef56gh78 for the demo.', 'aws_access_key_id = AKIAIOSFODNN7EXAMPLE', 'token ghp_EXAMPLENOTAREALGITHUBTOKEN0000000000 ok']。このフィルターが照合する形式(たとえば、プロバイダーの形式で入力したキー)の架空の値を渡してください。そうしないと、このチェックは何も証明しません。実際の秘密情報や個人データは絶対に使用しないでください。サンプルは結果と生成されるテストファイルにそのまま含まれます。 |
| project_dir | string | はい | ユーザーのプロジェクトルートの絶対パスです。target はここ(およびその src/ フォルダー)からインポートされます。 |
| flush_target | any | いいえ | per_chunk 専用:flush(state) の package.module:function。最後のチャンクの後に1回呼び出されます。 |
| whole_target | any | いいえ | 非ストリーミング関数を package.module:function の形式で指定します。テキスト全体を受け取り、フィルター処理済みテキストを返します。デフォルトでは、ストリーミングフィルターをテキスト全体の1チャンクとして実行します。これではストリーム終了時のフラッシュ漏れを検出できません(1チャンクの場合も同じ末尾が失われます)。この関数がある場合は指定してください。 |
| timeout_seconds | number | いいえ | チェックプロセスを停止するまでの秒数です。 |
詳細
chunk-invariance
ストリーミングフィルター(レダクター、ガードレール、書き換え器)が正しく動作するのは、入力をどのようにチャンクに分割しても、ストリーミング出力を連結した結果が、入力全体を一度にフィルタリングした出力と一致する場合に限られます。このパッケージを使えば、このルールをひとつのアサーションで確認できます。
from chunk_invariance import assert_chunk_invariant
def redact(text): # your filter on the whole input
return text.replace("sk-AbCd1234", "[KEY]")
def redact_stream(chunks): # your filter on a stream: takes chunks, yields chunks
for chunk in chunks:
yield redact(chunk) # redacts each chunk alone
def test_streaming_redaction():
assert_chunk_invariant(redact_stream, redact, "use sk-AbCd1234 now")
このテストは失敗し、問題が起きる最小の分割位置を示します。
chunk_invariance.core.ChunkInvarianceError: stream filter is not chunk-invariant
input: 'use sk-AbCd1234 now'
split: ['use s', 'k-AbCd1234 now'] (cut at 5)
whole: 'use [KEY] now'
streamed: 'use sk-AbCd1234 now'
split #6 in the order tried; generated splits run fewest parts first
pip install chunk-invariance
pip install "chunk-invariance[hypothesis]" # optional Hypothesis strategies
pip install "chunk-invariance[mcp]" # optional MCP server for AI assistants
検出できる問題
- 各チャンクを個別に評価するフィルター。値が2つのチャンクにまたがって分割されると、どちらの部分にも一致しなくなります。
- 前のチャンクから一定量を持ち越して先頭に付けるフィルター。一致はしますが、値の先頭はすでに前のチャンクとともに出力されています。
- 保留する長さがパターンより短い、ストリーム終端でのフラッシュがない、出力が漏えいではなく破損しているといった問題(入力全体を処理した場合と少しでも異なれば失敗します)。
chunk_invariance.examplesには、同じパターンに対する正しいフィルター(hold_back)と、誤った設計(design_a、design_b)の2つが比較例として用意されています。
検出できない問題
このテストの対象は、プロセス内で呼び出せるフィルターです。上流のモデル、ネットワーク、SSE パーサーによってチャンクが決まる、実行中のゲートウェイはテストしません。そのようなケースには、フラグメント化したレスポンスをゲートウェイ経由で送信し、クライアントに届く内容を確認するpii-leak-benchmarkを使用してください。
テストするのは、指定した分割方法です。デフォルトでは、入力全体と、すべての1か所での分割を試します。これにより、値が2つに分割されたケースを検出できます。splits="all"は短い入力(最大16文字)のすべての分割を試し、splits=3は最大3つのパートに分割するすべての方法を試します。チャンクのリストを明示的に指定すると、その分割だけを実行します。指定していない入力に対してフィルターが正しいことまでは証明できません。
AI アシスタントから使用する
Claude Code、Cursor、Claude Desktopをお使いですか?このパッケージにはオプションの MCP サーバーがあります。これを使うと、アシスタントに独自のストリーミング関数で全分割チェックを実行させ、そのテストをリポジトリに追加させることができます。
pip install "chunk-invariance[mcp]" # Python 3.10 or later
claude mcp add chunk-invariance -- chunk-invariance-mcp
Claude Desktopの場合は、次の内容をclaude_desktop_config.jsonに追加してください(Claude Desktopがコマンドを PATH から見つけられない場合は、コマンドのフルパスを指定してください)。
{
"mcpServers": {
"chunk-invariance": { "command": "chunk-invariance-mcp" }
}
}
次のように依頼できます。「myapp/filters.pyのredact_streamが、チャンク間で分割された値を漏えいするか確認し、回帰テストを追加してください。」
ツールは3つあります。check_stream_filterは関数(myapp.filters:redact_stream)をインポートし、各サンプルを2分割のすべての方法でストリーミングして、どの分割で問題が起きたかを伝えます。たとえば、入力が'use s'と'k-AbCd1234 now'に分割されたとき、'sk-AbCd1234'が伏せられずに出力された、といった内容です。write_regression_testはassert_chunk_invariantを使ったpytestファイルを返すため、CIでもチェックを継続できます。explainは、チャンク単位のフィルタリングで値が漏えいする理由を1段落で説明します。
デフォルトのサンプル(メールアドレス、SSN、カード番号、いくつかの形式のAPIキー)はすべて架空のものです。フィルターが伏せる形式の、自分で作成したサンプルを渡してください。たとえば、プロバイダーの形式に合わせて自分で入力したキーなどです。フィルターが変更しないサンプルでは何もテストできず、その場合はツールがそのことを通知します。
サンプルに実際の秘密情報や個人データを絶対に使用しないでください。サンプルと、フィルターから漏えいした値はツールの結果に含まれ、アシスタントは会話の他の部分と同様に、その結果をモデルプロバイダーに送信します。コードが例外を発生させた場合は、例外メッセージも送信されますが、コードが出力する内容は送信されません。同じサンプルは生成されるテストファイルにも含まれます。
関数をチェックすると、あなたのコードが実行されます。サーバーはあなたのマシン上でモジュールを別プロセスとしてインポートし、タイムアウト後にそのプロセスを停止します(デフォルトは60秒です)。プロジェクトに.venvがある場合は、そのPythonを使用します。サーバー自体はネットワークに接続せず、テレメトリーを送信せず、ファイルも書き込みません。テストファイルの保存は、あなたとアシスタントが行います。
API
| 名前 | 説明 |
|---|---|
assert_chunk_invariant(stream_filter, whole_filter, text, *, splits="all-two-part") | 最初に失敗した分割で ChunkInvarianceError(AssertionError のサブクラス)を送出します。分割数の少ないものから確認します。確認した分割数を返します。str と bytes に対応しています。 |
assert_chunk_invariant_async(...) | 非同期ジェネレーターフィルター向けの同じ関数です。非同期テスト内で使用します。 |
two_part_splits(text) | 空でない2つの部分へのすべての分割です。 |
all_splits(text, max_parts=None) | 空でない部分への、最大 max_parts 個までのすべての分割です。入力全体が最初に含まれます。 |
per_chunk(process, *, state=dict, flush=None) | process(chunk, state) -> output と flush(state) -> output を、ストリームごとに新しい状態を使う形に適合させます。 |
from_async(async_stream_filter) | 同期アサーション用に、非同期ジェネレーターフィルターを適合させます。 |
strategies.splits_of(text, max_parts=None) | Hypothesis のストラテジーです。分割数が少なくなる方向に縮小される、text のランダムな分割を生成します。 |
strategies.text_and_splits(texts, max_parts=None) | (text, chunks) のペアを生成する Hypothesis のストラテジーです。texts はリストではなく、st.sampled_from([...]) のようなストラテジーそのものです。 |
stream_filter は分割ごとに1回呼び出されるため、呼び出しのたびに新しい状態から開始する必要があります。
背景
この不変条件と、誤った2つの設計については、Phalak, N. (2026), Split-Boundary Leaks in Streaming Guardrails, Zenodo, 10.5281/zenodo.22909585 で説明しています。
標準ライブラリのみを使用します(mcp extra を追加すると、サーバー専用の MCP SDK が加わります)。Apache-2.0。API が同じ TypeScript 版は npm で chunk-invariance として公開されています。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。