コンテンツにスキップ

機能一覧

実装済みと計画中のすべての機能。使い方または出力の例と、main ブランチのソースへのリンク付き。

indx の機能を 1 つずつ列挙します。各項目をクリックすると、例と main ブランチの実装ファイルへのリンクが開きます。色付きチップはラダーの kind です。破線の 計画中 バッジは、目標だがまだ実装されていないものを示します。それ以外はすべて main に実装済みです。作る順はロードマップにあります。

URI スキームは indx 本体ではなく、インストールされたローダーディストリビューションが提供します。独自に追加できます。

file:data: URI

indx-loader-file がローカルファイルと data URI を解決します。INDX_LOADER_FILE_ROOTS を設定すると file: を特定のディレクトリに制限できます。その外のパスは 422 source_forbidden になります。未設定の場合、プロセスが読めるすべてのパスが対象です。

file:///absolute/path/report.pdf
data:application/pdf;base64,JVBERi0xLjcK…

packages/indx-loader-file/src/indx_loader_file/loader.py

packages/indx-loader-file/tests/test_loader.py

packages/indx-source/tests/test_source.py

http:https: URI

indx-loader-http がリモートソースを取得します。プライベート・ループバック・リンクローカルのホストは既定で拒否され(INDX_LOADER_HTTP_ALLOW_PRIVATE_HOSTS でオプトイン)、このチェックは最大 5 回の各リダイレクトでも繰り返されます。INDX_LOADER_HTTP_TIMEOUT_SECONDS が各リクエストを制限します(既定 30 秒)。

https://example.com/filings/annual-report.pdf

packages/indx-loader-http/src/indx_loader_http/loader.py

packages/indx-loader-http/tests/test_loader.py

s3: URI

indx-loader-s3 が、s3 エクストラで入る boto3 を通じて URI ごとに 1 オブジェクトを取得します。明示的な INDX_LOADER_S3_ACCESS_KEY_IDINDX_LOADER_S3_SECRET_ACCESS_KEY が優先され、未設定なら boto3 自身のチェーンが適用されるので、EC2 や EKS 上のロールはシークレットを複製せずに使えます。それが INDX_LOADER_S3_BUCKETS が URI の名指しできるバケットを囲う理由です。INDX_LOADER_S3_ENDPOINT_URL は S3 互換サーバーを指し、受け入れスイートはその一つに対して実行されます。

s3://filings/2025/annual-report.pdf

packages/indx-loader-s3/src/indx_loader_s3/loader.py

packages/indx-loader-s3/tests/test_loader.py

packages/indx-loader-s3/tests/test_integration.py

tests/bdd/features/s3.feature — Scenario: Plan an object fetched from S3

インライン base64 ソース

インラインソースはリクエスト本文に含まれるため、素のインストールでも動きます。何も取得しないからです。data は厳密な base64 です。

{
"request_id": "quickstart",
"source": {
"type": "inline",
"media_type": "application/pdf",
"data": "JVBERi0xLjcKJc…",
"filename": "invoice.pdf"
}
}

packages/indx-interfaces/src/indx_interfaces/sources.py

packages/indx-source/tests/test_source.py

tests/bdd/features/plan.feature — Scenario: Plan a native multi-page document from JSON

マルチパートアップロード

HTTP アダプターはファイルそのものも受け取ります。JSON の request パートと並べて file パートを送る形式で、planencodeembed のいずれでも使えます。読み取りは INDX_MAX_INPUT_BYTES を 1 バイト超えた時点で止まります。上限が拒否するためだけに全体をバッファすることはしません。大きなソースが入口で 3 分の 1 大きくなることもありません。

Terminal window
curl -F 'request={"request_id":"quickstart"}' -F file=@report.pdf \
http://127.0.0.1:8000/v1/plan

packages/indx-app-server/src/indx_app_server/app.py

packages/indx-app-server/tests/test_app.py

tests/bdd/features/plan.feature — Scenario: Plan a scanned multi-page upload

gs:az: ソース 計画中

Google Cloud Storage、Azure Blob Storage 向けに indx が同梱するローダーディストリビューションです。形は indx-loader-s3 が今持っているものと同じで、ファーストパーティのパッケージがオブジェクトストアの import を得ることはありません。誰でも今日から書けるものであり、計画されているのは indx がそれを同梱することだけです。それぞれが同じ責務を負います。認証情報は INDX_* を通し、ベンダー SDK の暗黙の認証情報を尊重するか拒否するかを明示的に決めること。バケットやコンテナの許可リスト、すなわちオブジェクトストア版の INDX_LOADER_FILE_ROOTS を持つこと。SDK はエクストラの背後に置き、無い場合は壊れるのではなく利用不可として報告すること。対象は 1 つのオブジェクトであってプレフィックスではありません。プレフィックスを展開するのは、別途計画されているコーパス実行です。

developer/protocols/source-loader

観察は低コストでローカル、決定的です。オブザーバーもインストールされたディストリビューションから届きます。独自に追加できます。

PDF の観察 application/pdf

indx-observer-pdf は、何もレンダリングせずに、各ページのテキスト層の状態と fontimageempty の signal を報告します。フォーム XObject をたどり、フォントも画像も見つからない場合にのみコンテンツストリームを展開します。

packages/indx-observer-pdf/src/indx_observer_pdf/observer.py

packages/indx-observer-pdf/tests/test_observer.py

暗号化された PDF

ユーザーパスワードが空で暗号化された PDF は、誰かが何かを打ち込む必要はありません。プリフライトが持っていなかった依存が必要なだけです。pypdf[crypto]indx-observer-pdf と共に配られるので、任天堂の有価証券報告書は他の提出書類と同じように開き、ネイティブ抽出へルーティングされます。そうなるまではベンチマークの 2 つあったルートミスの 1 つでした。同じラベル群で正確度は 86% から 93% へ動き、残るミスは手書きだけになりました。

呼び出し元が持っていないパスワードを本当に必要とする PDF は、クラッシュではなく型付きの拒否になります。ソースを開く際に送出されたそれ以外の失敗は、処理されない 500 ではなく source_unreadable です。

{"error": {"type": "invalid_source", "code": "source_encrypted",
"message": "source is encrypted and could not be opened: …", "param": "source"}}

packages/indx-observer-pdf/src/indx_observer_pdf/observer.py

packages/indx-observer-pdf/tests/test_observer.py

画像の観察 image/jpeg image/png

indx-observer-image は画像を、テキスト層のない 1 ページとして image signal 付きで申告します。バイト列を一切開きません。メディアタイプそのものが証拠です。

packages/indx-observer-image/src/indx_observer_image/observer.py

packages/indx-observer-image/tests/test_observer.py

複数ページ TIFF の観察 image/tiff

スキャン資料の標準的な入れ物です。1 ファイルに複数ページという点が、indx-observer-image が既に申告する単一ページ画像との違いです。観察は同じディストリビューションが担い、そこで唯一バイト列を実際に開く形式でもあります。image file directory の連鎖をたどればピクセルを一切デコードせずにページ数が得られるので、観察はプリフライトの予算の内側に留まります。

読み取り経路は、スキャン PDF のページを既に読んでいる OCR レーンです。そしてそこが image/jpegimage/png と共有する弱点でもあります。画像を無料で読むものはなく、読み取り経路はエクストラの背後にあります。--extra ocr がなければ TIFF は計画され、はしごを最後まで降りて人間に届きます。正直な答えであり、ベンチマークのインストールがエクストラを持つ理由でもあります。

オフセットはすべて境界検査され、訪問済みのディレクトリは記憶されます。切り詰められたファイルは自分の終端の先を指し、循環する連鎖は放っておけば永遠に回り続けるからです。どちらも呼び出し側が渡したバイト列から到達可能です。BigTIFF は誤読ではなく拒否します。マジックは 43、オフセット幅は 8 バイトで、この走査は黙って読み違えるからです。縮小解像度のフラグを持つディレクトリは、スキャナがページの隣に保存するサムネイルであってページではありません。走査はそのタグ 1 つだけを読んで飛ばし、リーダーも同じフレームを飛ばすので、ページ番号は計画の両側で同じ画像を指します。

packages/indx-observer-image/src/indx_observer_image/observer.py

packages/indx-observer-image/tests/test_observer.py

プレーンテキストの観察 text/plain

エンジンもエクストラも不要です。バイト列がデコードできるかどうか、それだけです。text-extractionnative-extraction を宣言します。テキストファイルを読むことはルーティングの判断ではないからです。「ディストリビューションをインストールすることがメディアタイプを計画可能にする」ことの、最小の実例でもあります。

改ページを表すためにプレーンテキストファイルが使うのはフォームフィードなので、ページを分けるのもそれです。フォームフィードを含まないファイルは 1 ページであり、これは架空のページ付けを持ち込むのではなく、正直に読んだ結果です。

エンコーディングは INDX_TEXT_ENCODING です。カンマ区切りの順序付きリストで既定は utf-8-sig、各名前を厳密に試して最初にデコードできたものが採用されます。日本の公的機関の CSV は圧倒的に Shift_JIS で、気象庁も東京都オープンデータも CP932 で配信しているため、それらを読む環境は utf-8-sig,cp932 を設定します。どの名前にも一致しないファイルは、最後に試したものへ押し込まれるのではなく失敗します。

indx-observer-textsniff を宣言しません。これは省略ではなく答えです。.txt.csv.tsv は、内側の区切り文字が違うだけの同じ文字列です。ここで書ける唯一の署名 —— 「UTF-8 としてデコードできるか」 —— は、本当に正しい申告済みタイプを追い越して JSON も XML も HTML も自分のものだと主張してしまいます。

packages/indx-observer-text/src/indx_observer_text/observer.py

packages/indx-observer-text/tests/test_observer.py

CSV と TSV の観察 text/csv text/tab-separated-values

同じディストリビューションであり、エンジンが不要な点も同じです。どちらの形式にもページ付けがないため、区切りテキストのファイルは 1 ページです。ベンチマークのケーススキーマ 1.2 が最小値を緩めたのはこれを受け入れるためでした。上書きすべきページを持たないケースには、2 ページ規則を満たす手立てがないからです。

区切り文字は意図的に判定しません。メディアタイプがすでにそれを名指ししており、下流で行を消費するものも存在しないからです。indx-chunker-page はページを 1 つの文字列として扱うので、csv.Sniffer は答えを受け取る読み手のいない呼び出しになります。行とセルが指定可能になるのは、ここではなく計画中の表構造の作業のもとでです。

$ uv run indx plan file://$PWD/quarterly.csv
document [text-extraction, manual-review] # 1 ページ、エンジンなし

packages/indx-capability-text-extraction/src/indx_capability_text_extraction/reader.py

packages/indx-capability-text-extraction/tests/test_provider.py

Office 形式の観察 .docx .xlsx .pptx

いずれも XML の zip なので、indx-observer-office はエンジンを開かず、何もレンダリングしません。リーダーも同じです。zipfilexml.etree がスタックのすべてであり、そのため両者が共有するパッケージ層 indx-ooxml は依存のないライブラリであり、どのディストリビューションもサードパーティの OOXML ライブラリを宣言せず、それを入れるための extra も持ちません。ページ数は形式自身が示す区切りです。ワークシート数、スライド数、w:sectPr の数です。Word のページはファイルを開いた側が決めるレイアウトの結果なので、数えるにはレンダリングが必要になります。

3 つとも PK\x03\x04 で始まり、.jar.epub.odt も同じです。そのため sniff は先頭のバイト列ではなく、ファイル末尾にある zip のメンバー一覧を読みます。全文を渡す検出が存在するのはこのケースのためです。

$ uv run indx plan file://$PWD/report.xlsx
document [office-extraction, manual-review] # シートタブ 28 枚

packages/indx-observer-office/src/indx_observer_office/observer.py

packages/indx-observer-office/tests/test_observer.py

DXF の観察と読み取り image/vnd.dxf

DXF で保存された CAD 図面はネイティブのテキストレイヤーです。すべての TEXT と MTEXT、表題欄のようなブロック参照の属性、寸法の描画された計測値が、ファイルが述べる位置でファイルの中にあります。indx-observer-dxf はページを CAD プログラムが示すレイアウト(モデル空間と各ペーパー空間レイアウト、タブ順)として数え、indx-capability-dxf-extraction は無料の段でそれを読み、各テキストに位置を付けるので、indx-chunker-lines が図面に PDF ページと同じ矩形を与えます。どちらも ezdxf を通ります。バイナリと ASCII のあらゆるバージョン、コードページ、MTEXT の書式、ブロック変換を読みます。図形はあるが文字のないレイアウトは正直に missing であり、図面を描画する段はないので人手のレビューに落ちます。デモホストは ezdxf の SVG バックエンドでレイアウトを矩形の下に描き、図面のフォントをホストにある CJK フォントで置き換えるので、フォントがあれば日本語も描かれます。

$ uv run indx plan file://$PWD/tests/fixtures/drawing-title-block.dxf
document [dxf-extraction, manual-review] # 3 layouts: Model, Layout1, Sheet1
$ uv run indx encode file://$PWD/tests/fixtures/drawing-title-block.dxf | jq '[.blocks[] | select(.kind=="chunk")] | {n: length, boxed: (map(select(.bbox != null)) | length)}'
{ "n": 11, "boxed": 11 }

packages/indx-dxf/src/indx_dxf/drawing.py

packages/indx-observer-dxf/tests/test_observer.py

tests/bdd/features/plan.feature — Scenario: A DXF drawing plans one page per layout, read by its native rung

テキストレイヤーの先の CAD 計画中

図面がテキストのほかに運ぶもの。描画された計測値ではなく形状としての寸法、部品そのものとしてのブロック参照、製図者が意味を込めたレイヤー。多くの CAD プログラムが保存する DWG は、DXF への変換を経るか、対象外か。3D 形式(STEP、IFC)を対象にするかは既定ではなく決定です。スプリント 2 は何かを作る前に、2D/3D の需要に照らしてこの範囲を書きます。

developer/decisions — ADR-0048

スプリント — スプリント 2

スライドの位置情報 .pptx

indx-chunker-pptx はスライドを構成する図形へ切り分け、それぞれにファイルが述べる位置を載せます。文字を持つ図形と表は一つずつチャンクに、ラスター画像は一つずつ画像チャンクになり、文書順に並びます。グループ変換を適用し、プレースホルダーの枠はレイアウトとマスターから継承するため、自身の位置を持たないタイトルも PowerPoint が描く場所に置かれます。記号、コネクタ、枠の矩形はチャンクではありません。チャンクは呼び出し側が取り出せるものであり、それらの位置は工程系統図パーサーの領分です。

矩形を持たないチャンクは、型付きの bbox_reason で理由を述べます。位置を持たない形式なら no_geometry、元の文書が隠している図形なら hidden、ファイルが位置を述べていないなら unresolved です。理由を述べないチャンクはモデルが拒みます。

$ uv run indx encode file://$PWD/tests/fixtures/office-process-chart.pptx | jq '.blocks[] | select(.id=="page:2/chunk:9") | {text, bbox, bbox_reason}'
{ "text": "非表示メモ", "bbox": null, "bbox_reason": "hidden" }

packages/indx-ooxml/src/indx_ooxml/drawingml.py

packages/indx-chunker-pptx/tests/test_chunker.py

tests/bdd/features/encode.feature — Scenario: PowerPoint chunks carry their shape’s rectangle, and a chunk without one says why

工程系統図シグネチャ .pptx

PowerPoint のネイティブ図形で描かれた工程系統図を、文字としてだけでなくグラフとして読みます。signature_detection を有効にすると、indx-capability-process-chart は計画時にすべてのスライドの図形を走査し、記号(△ 材料、○ 加工、◇ 検査、二重の △ 組立)が 2 本以上のコネクタで結ばれ、記号の種類が 2 種類以上あるスライドに一致します。ラベルを持つ記号の割合は確信度を上げるだけです。一致はページ単位なので、process-chart-parser がそのスライドのルートだけを先導し、デッキの他のスライドは汎用ルートのままです。パーサーは office-extraction が返したはずのテキストをそのまま返し、ページブロックの metadata.process_chart にグラフを加えます。ノード(symbolpicturecontainertext。それぞれチャンクと同じ矩形を持ちます)とエッジ(コネクタ自身の stCxn/endCxn 参照から得た connection。矢印があればその向き、なければ上から下。領域の矩形には contains)です。どのテキストがどの記号のラベルかだけが経験則であり、そう明記してあります。

$ uv run indx plan --signatures file://$PWD/tests/fixtures/office-process-chart.pptx
document [office-extraction, manual-review]
page:2 [process-chart-parser, office-extraction, manual-review] # process-chart: connected_symbols, symbol_family, labelled_symbols

packages/indx-capability-process-chart/src/indx_capability_process_chart/chart.py

packages/indx-capability-process-chart/tests/test_provider.py

tests/bdd/features/signatures.feature — Scenario: A slide drawn as a process chart nominates its parser for that slide alone

OCR 行の位置情報 .pdf .tiff .png

認識で読まれたページは、これまで位置のない一つのチャンクとして届いていました。OCR リーダーがエンジンの行をテキストに結合し、その枠を捨てていたからです。いまは generic-ocr が認識した各行を PageOutput.lines に述べ(ワイヤには届かないポートモデルの項目です)、indx-chunker-lines がそれを行ごとに一つのチャンクとして、その矩形で切ります。どのメディアタイプでも、エンジンを二度走らせることなく。スキャンページはテキストレイヤーと同じ矩形を得るので、プレイグラウンドはスキャンを PDF と同じように描き、検索はページではなく行を索引します。行を述べないリーダーのページは、これまで通り一チャンクの床に残ります。

$ uv run indx encode file://$PWD/scan.pdf | jq '[.blocks[] | select(.kind=="chunk")] | {n: length, boxed: (map(select(.bbox != null)) | length)}'
{ "n": 119, "boxed": 119 }

packages/indx-chunker-lines/src/indx_chunker_lines/chunker.py

packages/indx-capability-generic-ocr/tests/test_provider.py

packages/indx-executor/tests/test_executor.py

メールの観察 message/rfc822

.eml を標準ライブラリで扱います。ヘッダーと本文は読み、添付ファイルは開かずに名前を記録します。添付をたどることは 1 つのソースを多数に変えることであり、それはオブザーバーではなくコーパス実行の問題です。読まれなかった添付は、黙って落とすのではなく出力に名前として残ります。

観察が解析するのはヘッダーだけです。それが観察をプリフライトの予算の内側に留め、ヘッダーを持たないバイト列を「本文がファイルのメッセージ」ではなく型付きの拒否にします。ブロックに届くヘッダーは 5 つ —— FromToCcDateSubject —— です。メールサーバーが道中で押していくものはすべて転送情報であり、Received の連なりの下に件名を埋めることは、ベクトルが検索語と一致しなくなる道筋そのものだからです。HTML しかない本文は html.parser で可視テキストに落とします。

From: Aiko Tanaka <aiko@example.co.jp>
Subject: 第1四半期の請求書
Attached is the quarterly invoice.
Attachments:
- invoice-2026q1.pdf (application/pdf)

packages/indx-capability-email-extraction/src/indx_capability_email_extraction/reader.py

packages/indx-observer-email/src/indx_observer_email/observer.py

計画はオブザーバーが報告した内容から決まります。高コストなルートを実行することは決してなく、同じ入力からは同じ plan_id が得られます。

宣言済みフォールバック付きルーティングラダー

すべての計画は kind を段階的にたどります。ネイティブテキスト層 → OCR → VLM → 手動レビュー。選ばれたラングより下はすべて、順序付きフォールバックとして計画に残ります。

mixed-multipage.pdf
document [native-extraction, generic-ocr, generic-vlm, manual-review]
page:2 [generic-ocr, generic-vlm, manual-review] # スキャンページ

packages/indx-router/src/indx_router/policy.py

packages/indx-router/tests/test_policy.py

tests/bdd/features/encode.feature — Scenario: Encode without supplying a plan

シグネチャ検出と指名

CLI では --signatures、HTTP/Python では signature_detection: true でオプトインします。請求書シグネチャは 4 つの正規表現シグナルのうち 2 つ以上(英語・日本語対応)が一致すると成立し、パーサーをラダーの前に指名します。置き換えることはありません。

$ uv run indx plan --signatures file://$PWD/tests/fixtures/invoice-anthropic.pdf
document [invoice-parser, native-extraction, generic-ocr, generic-vlm, manual-review]

packages/indx-capability-invoice/src/indx_capability_invoice/provider.py

packages/indx-router/tests/test_signatures.py

packages/indx-capability-invoice/tests/test_provider.py

tests/bdd/features/signatures.feature — Scenario: A recognized invoice is routed to its specialist

パーサーしか読まない形式は、声を上げる unsatisfied な計画になる

パーサーがルートに至る扉は指名だけで、signature_detection の既定は false です。そのため、他に読むものがない形式の専用パーサーをインストールすると、以前は 415 が静かな manual-review ルートに変わっていました。1 ページ $2 と 1 日の人手のキューへ、インストールしたパーサーは使われないまま、計画にはその旨が何も書かれずに。

このケースは今や unsatisfied な計画になり、その制約はパーサーと、そこへ到達させるフラグの両方を名指しします。エグゼキューターは unsatisfied な計画を拒否するので、encode も同じことを言います。この制約は検出が一度も走らなかったときにだけ発火します。参照された上で何にも一致しなかった検出器はシステムが正しく働いた姿であり、そのときは manual review が正直な答えです。

routing: application/vnd.acme.contract is read only by parser acme-contract-parser,
and a parser reaches a route only through signature nomination
-- plan with signature_detection=true

packages/indx-router/src/indx_router/policy.py

packages/indx-router/tests/test_policy.py

developer/decisions — ADR-0003

業務制約

5 つのフィールドが、実行前にラダーを絞り込みます。さらに 2 つのフィールドでリーダーを直接名指しできます (後述)。data_residency は外部デバイスの機能を拒否します。満たせない組み合わせはエラーではなく unsatisfied な計画として報告されます。

{
"constraints": {
"minimum_quality": 0.6,
"deadline_ms": 30000,
"maximum_cost_usd": 0.05,
"gpu_allowed": false
}
}

packages/indx-interfaces/src/indx_interfaces/constraints.py

packages/indx-router/tests/test_policy.py

実行前に分かる費用・レイテンシ・品質

すべての計画は、決定されたルートに期待される費用・所要時間・品質を持ち歩きます。上記の制約が照らし合わせる数値そのものであり、その計画の価格として公開されます。キーは能力 ID ではなく種別なので、サードパーティの OCR ディストリビューションも OCR として値付けされます。実際の数値は各デプロイが INDX_ROUTING_ECONOMICS に書き下ろします。既定値は計測ではなく、上書き手段付きの明示された仮定です。

{"estimates": {"cost_usd": 0.0005, "latency_ms": 400, "quality": 0.8}}

packages/indx-router/src/indx_router/economics.py

packages/indx-router/tests/test_policy.py

計画の受け渡しと正確な再実行

保存した計画は encode() に渡し直せます。Executor はソースダイジェスト、メディアタイプ、スナップショットを検証します。不一致は 409 plan_conflict であり、黙った再計画は決して行われません。

plan = service.plan(PlanRequest(...))
result = service.encode(EncodeRequest(..., plan=plan))

developer/api-contract

packages/indx-executor/tests/test_executor.py

tests/bdd/features/encode.feature — Scenario: A plan decided for another source is refused

リクエストでリーダーを名指しする

制約の capability_ids はケイパビリティの許可リストで、fallbacks_allowed はフォールバックを禁止します。許可リストは、指名されたパーサーと手動レビューも含め、ページが取りうるすべてのルートを絞り込みます。許可された候補の順位はラダーが付けます。両方を指定すると、計画はページごとに 1 つのリーダーを名指しします。リストのどれも読めないページは、capability_ids を名指しした unsatisfied で返ります。スナップショットにない ID も同様です。実行は変わりません。もともと計画が名指ししたものしか動かさないからです。フォールバックがなければ、読み取りの失敗はページとリーダーを名指しする既存の 503 になります。どちらも省略すれば、これまでどおりラダーが決めます。

{
"constraints": {
"capability_ids": ["native-extraction"],
"fallbacks_allowed": false
}
}

packages/indx-router/src/indx_router/policy.py

packages/indx-router/tests/test_policy.py

tests/bdd/features/plan.feature — Scenario: A request that names the reader gets that reader or a refusal saying why

developer/decisions — ADR-0063

developer/decisions — ADR-0050

機能一覧 — 業務制約

レシピと類似フォーマットの再利用 計画中

レシピとは、認識済みフォーマットごとに保存される例とスキーマの組です。インコンテキストで、ファインチューニングはしません。過去事例は文書シグネチャで検索されるため、既知のものに似た新フォーマットは素早く立ち上がります。

developer/core-user-features

native text layer native-extraction

既存の PDF テキスト層を CPU で抽出します。エクストラ不要、モデル不要、ほぼゼロコスト。ラダーの最初のラングです。

packages/indx-capability-native-extraction/src/indx_capability_native_extraction/provider.py

packages/indx-capability-native-extraction/tests/test_provider.py

ネイティブテキストレイヤー office-extraction

.docx.xlsx.pptx を依存関係ゼロのまま CPU で読みます。文字はすでにファイルの中にあるからです。ワークシートはタブ区切りの行として返り、空セルは列位置を保ちます。スライドはランを文書順に、Word のセクションは 1 段落 1 行で返します。シートとスライドの順序は参照元パートのリレーションシップから取り、メンバー名からは取りません。sheet1.xml が最初のシートである保証はないからです。

グラフシートはタブなのでページであり、セルではなく絵を持ちます。そこで failed と理由を返してラダーが下ります。空文字列を返せば、空のシートを読んだと主張することになるためです。

this sheet holds a chart rather than cells

packages/indx-capability-office-extraction/src/indx_capability_office_extraction/reader.py

packages/indx-capability-office-extraction/tests/test_provider.py

ocr generic-ocr –extra ocr

onnxruntime 経由の PP-OCR を CPU で実行します。PDF と画像に対応します。エクストラがない場合、不足しているものを明記した利用不可の記述子として表示され、ラダーはそれを飛ばして降下します。

unavailable_reason: "install the 'ocr' extra; missing: rapidocr"

packages/indx-capability-generic-ocr/src/indx_capability_generic_ocr/provider.py

packages/indx-capability-generic-ocr/tests/test_provider.py

vlm generic-vlm –extra vlm

LiteLLM 経由で任意の視覚モデルを使います。エクストラと INDX_VLM_MODEL が必要です。ローカルでは GPU、ホスト型では external デバイスとして動作し、data_residency はこれを基準に絞り込みます。

packages/indx-capability-generic-vlm/src/indx_capability_generic_vlm/provider.py

packages/indx-capability-generic-vlm/tests/test_provider.py

manual review manual-review

終端のラングです。人間は何でも読めるためメディアタイプを宣言しません。フォールバック先を持たないことが、読めないページを消さずに見える状態に保ちます。

packages/indx-capability-manual-review/src/indx_capability_manual_review/provider.py

packages/indx-capability-manual-review/tests/test_provider.py

tests/bdd/features/encode.feature — Scenario: A page nothing could read is accounted for rather than dropped

出力検証と、未処理ページ単位のフォールバック

フォールバックはかつて不在か例外でだけ発火し、悪い出力では決して発火しませんでした。そのため、あるページについて failed を宣言した機能は「答えた」と数えられ、そのページを保持し、ラダーを止めていました。PageOutput.statusreason は、リーダーが沈黙で失敗を伝えなくて済むようにこそ存在するのに、誰もそれを読んでいなかったのです。

チェックは今 3 つ、それぞれが何をどれだけ仮定するかの順です。不在は何も仮定しません。宣言された failed も何も仮定しません — 機能自身がそう言ったのだから。自己申告の確信度INDX_VALIDATION_MIN_CONFIDENCE を下回るかどうかは数値を仮定します。だからこの下限の既定は 0.0 で、無効のまま出荷されます。ラベル付きコーパスに対して閾値を測定したものが、ここには何も無いからです。unreadable は意図的に拒否ではありません — それは試行ではなく内容についての判定であり、終端の段からフェイルオーバーすれば、既定インストールのあらゆるスキャンページに 503 を返すことになります。

ラダーはグループ単位ではなく、未処理のページ単位で降ります。10 ページ中 8 ページを読んだ機能はその 8 ページを保ち、2 ページだけを下へ送ります。グループ全体をやり直せば、OCR がすでに読んだページに視覚モデルを費やすことになり、それこそがルーティングの存在理由である無駄です。

packages/indx-executor/src/indx_executor/validation.py

packages/indx-executor/src/indx_executor/settings.py

packages/indx-executor/tests/test_executor.py

developer/decisions — ADR-0015

難しい企業データの読み取り 計画中

スキャン、手書き、スプレッドシート、図面、大容量ファイルそれぞれに読み取り経路を用意します。目標は 10 のメディアタイプです。1 つと数えるのは、この環境が観察でき、いずれかの機能が読め、採用済みのベンチマークケース 1 件が採点する場合に限ります。型付きの 415 は正直な答えではありますが、カバレッジではありません。

12 のメディアタイプが観察も読み取りもできます。 目標が名指しする全形式に、余裕枠 2 つを加えた数です。application/pdfimage/jpegimage/pngimage/tifftext/plaintext/csvtext/tab-separated-values.docx.xlsx.pptxmessage/rfc822image/vnd.dxf

ベンチマークはこのカバレッジ数値を手作業ではなく自ら計算します。値は 5、すなわち application/pdf.xlsx.pptxtext/csvimage/jpeg です。11 種すべてが読み取り可能になったあとも 2 スライスのあいだ 2 のままでしたが、それは後退ではなく規則が働いている姿でした。ある型が数に入るのは、採用済みのベンチマークケースがそれを採点したときだけであり、11 のうち 9 つは何にも採点されていなかったからです。

その 9 つのうち 3 つが採用されました。.pptx はデジタル庁の重点計画(概要)、text/csv は警察庁の警備業統計、image/jpeg は国土交通省の土地分類図のスキャンです。**残る 6 つのうち 4 つは、条件を満たす文書が公開されていないように見えることが障害です。**日本の官公庁の .docx を 10 件調べてもすべてセクションは 1 つで、ページ規則は 2 つを求めます。公開されている text/plain は 2 件とも改ページ(フォームフィード)を含みません。国のオープンデータカタログには TSV も TIFF も 1 件もありません。手作業で 11 と主張するページは、計算がまさに防いでいる形で誤ることになります。

.dwg.xdw は、読み取り経路ではなく文書化された拒否として記録します。どちらにも開かれたリーダーが存在しないためです。手書きはベンチマーク唯一のルート誤りです。

developer/core-user-features

Office リーダーをライブラリと比較して測る 計画中

office-extractionzipfilexml.etree を手で歩きます。それが無料の段を無料に保ち、既定インストールを依存ゼロに保ちました。ただしそれは測定された答えではありません。同じバイト列から openpyxlpython-docxpython-pptx が生成するテキストと比較した者はまだおらず、手歩きの方が劣りそうな箇所はすでに見えています。数値書式 — セルは保存された値のまま出るので、日付は Excel のシリアル値、パーセントはその分数です。結合セル — 結合された見出しは、1 つのラベルとそれに続く空の列として読まれます。スピーカーノート、ヘッダー、フッター、脚注、コメント、テキストボックス。これらはリーダーが渡されなかったパートにあります。数式セル — キャッシュされた値が正しいのは、Excel が最後に保存したファイルについてだけです。変更履歴とフィールドコード。

まず測ること。ベンチマークがまだ負っているラベル付き部分集合に対して。「ライブラリの出力の方が網羅的に見える」は結果ではなく好みです。ライブラリが勝つなら、このディストリビューションの extra の背後か、自分の機能 ID を持つ 2 つ目のディストリビューションへ入ります。素の依存としては決して入りません。無料の段は、何も無しでインストールできるままでなければならないからです。そして何が読むにせよ、ページ列挙は動かせません。チャートシートを飛ばしたりメンバー名に従ったりするライブラリは、その文書がルーティングされた計画と静かに食い違います。

packages/indx-capability-office-extraction/src/indx_capability_office_extraction/reader.py

developer/decisions — ADR-0021

OCR の訂正と復元した行を、ブロックごとに報告する 計画中

generic-ocr は認識器が読んだものを返し、誤読(受理日受理目)や落とした行は黙って通ります。目標は両方を、それを担うブロックの上で報告します。ocr.lines は結合した認識行の数、ocr.corrections は読んだものとブロックがいま言っているものの組、ocr.recovered は見直しで取り戻した行です。訂正はブロックのテキストを書き換えるので、まさに ADR-0030 の見直し条件が名指しする場合です。エンティティのオフセットは認識器のテキストではなく、訂正後のテキストを指さなければなりません。

developer/decisions — ADR-0060、セールスページが描くもの

developer/decisions — ADR-0030

PDF 行チャンカー

indx-chunker-pdf は PDFium のテキストランを視覚的な行へマージし、正規化された bbox 付きで 1 行 1 チャンクを生成します。実際のチャンクブロック(抜粋):

{
"id": "page:1/chunk:1", "kind": "chunk", "parent_id": "page:1",
"bbox": [0.889, 0.944, 0.949, 0.954],
"text": "Page 1 of 1",
"embeddings": [{"embedding_space_id": "default-text", "embedder_id": "hashed-text", "vector": [0.0, ]}],
"provenance": {"capability_id": "native-extraction", "device": "cpu", "fallback_index": 0}
}

packages/indx-chunker-pdf/src/indx_chunker_pdf/chunker.py

packages/indx-chunker-pdf/tests/test_chunker.py

ページ単位フォールバックチャンカー

indx-chunker-page は下限です。読めたページごとに 1 チャンク、任意のメディアタイプ、bbox なし。fallback = True を設定しており、インストール済みのすべてのチャンカーの後に並びます。

packages/indx-chunker-page/src/indx_chunker_page/chunker.py

packages/indx-chunker-page/tests/test_chunker.py

領域(リージョン)ブロックは黙って捨てず、拒否する

granularities: ["region"]422 unsupported_granularity になり、この環境が実際に生成するものを明記します。以前は文書ブロックだけを載せた 200 を返し、理由を何も言いませんでした。呼び出し側は「この環境が領域を引かない」のか「この文書に領域がない」のかを区別できませんでした。

拒否はソースを取得する前に行われるので、答えようのないリクエストに転送コストはかかりません。メッセージは document, page, chunk を列挙します。解決できないスキームに対する 415 が解決可能なスキームを列挙するのと同じ形です。領域を生成することは依然として答えのもう半分であり、計画中のままです。RegionEvidence は、領域がルートを変えることを計測が示すまで生成されません。ここで領域を捏造すれば、誰も決めていないアドレスを公開することになります。チャンクの bbox はこの空白に対する検索側の答えであって、アドレス側の答えではありません。

{"error": {"type": "unsupported_granularity", "code": "unsupported_granularity",
"message": "no installed capability produces region blocks; this installation produces document, page, chunk",
"param": "granularities"}}

packages/indx-executor/src/indx_executor/service.py

packages/indx-executor/tests/test_executor.py

tests/bdd/features/encode.feature — Scenario: A granularity nothing produces is refused rather than silently dropped

チャンクサイズの制御 計画中

現在、インストール済みチャンカーが生成する粒度より粗い、または細かいチャンクを要求する手段がありません。ネイティブ PDF は、宛先が 128 トークンの埋め込みモデルでも画面でも、行単位のチャンクになります。

developer/protocols/chunker

リスト順を超えたチャンカー選択 計画中

誰が境界を引くかは、今日はリスト順だけで決まります — インストール済み、次に組み込み、次にフォールバック、ページごとに最初の回答が勝ちます。これはオブザーバーの規則の再利用であり、非フォールバックのチャンカーがちょうど 1 つしか存在しないうちはそれで十分です。SourceLoader がスキームを宣言するようにチャンカーをメディアタイプへ限定する手段はなく、2 つのうち一方をデプロイメントが優先する手段もありません。

同じページを主張する非フォールバックのチャンカーが 2 つある場合は、発見順で先勝ち、静かに解決されます。実際に 2 つ目のチャンカーが存在してから再検討します。それ以前ではありません。同点になる前に同点処理を設計するのは当て推量です。上のチャンクサイズ制御とは別物で、あちらは呼び出し元のレバー、こちらはデプロイメントの順序付けです。

packages/indx-executor/src/indx_executor/service.py

developer/protocols/chunker

リクエストでチャンカーを名指しする 計画中

上の項目が欠落そのもので、こちらはその答えの形です。しかも新しい形ではなく、分類器の形です。Chunkerid を持ち、インストール済みの ID は機能スナップショットのコンテンツハッシュの外に乗り — つまりチャンカーをインストールしてもどの計画も動かず — EncodeRequest が尋ねる順にそれらを名指しします。何もインストールされていない ID は、存在するものを列挙する 422 で、ソース取得の前に送出されます。

既定は今日の振る舞いのままです。何も名指ししなければインストール順が決め、床は最後。だからこれは、すべての呼び出し元への新しい義務ではなく上書きです。リクエストは意見があるときにだけ意見を述べ、それ以外の場所ではデプロイメントの既定が正直な答えです。

チャンキングはどの計画にも触れないので、これは POLICY_VERSION もスナップショット ID も動かしません。動かすのは openapi.json と生成クライアントで、だからこそ、選択が現実になる 2 つ目のチャンカーを待ちます。

developer/decisions — ADR-0026

developer/protocols/chunker

文と段落のチャンク境界 計画中

indx-chunker-pdf は視覚的な行で切ります。これはグリフが置かれた位置であって、意味の切れ目ではありません。折り返された 1 文は 2 チャンクになり、1 段落は十数チャンクに分かれるため、各ベクトルは断片しか担いません。同じ PDFium のランから、同じ CPU 上で、チャンクごとに bbox を 1 つ保ったまま、行を文と段落へマージすることが、より良いデフォルトの候補です。上のチャンクサイズの制御とは別物です。あちらはレバーで、こちらはそのレバーが調整するデフォルトそのものです。どの境界が検索に有利かは好みではなく計測の問題です。

セールスページは目標を描いています。段落ごとに paragraph ブロックが 1 つ、そのすべての行を囲むボックスと、結合した行の数を持ちます。encode が返すのは行です。

developer/protocols/chunker

developer/decisions — ADR-0060、セールスページが描くもの

表構造ブロック 計画中

ページ上に描かれた表は表計算ファイルではなく、2 つは別の問題です。.xlsx は現在読めるようになり、シートタブ 1 枚を 1 ページとしてタブ区切りの行で返りますが、格子は平坦化されます。.csv.tsv は、区切り文字を文書が置いたまま 1 ページのテキストとして読みます。行を消費するものが下流にまだ存在しないからです。一方、PDF やスキャン内部の行とセルは今日も読まれてはいるものの、チャンカーの行として読み順に並ぶだけで、格子は失われます。

セールスページは目標を描いています。columns を持つ table ブロックが 1 つあり、cells はそれぞれ行、列、自身のボックスを持ち、テキストが金額や日付であれば、印字された値の横に正規化された値を持ちます(0円0 JPY)。段落が指している表(「次表」)は、その段落を referenced_by に名指しします。

developer/core-user-features

見出しブロックと、すべてのブロックの章節パス 計画中

今日、見出しは 1 行であり、その下の段落と区別がつきません。目標は level を持つ heading ブロックと、すべてのブロックが持つ section です。これはそのブロックの上にある見出しの経路(別紙 › 第1 認定の基準 › 次表)で、検索のヒットやエージェントの引用が、どのページかだけでなく文書のどこにあるかを言えるようにします。PDF では同じ PDFium のランから読むフォントの大きさ、太さ、番号付けであり、Office ではファイルがすでに名指ししているスタイルです。見出しは段落を終わらせるので、段落ブロックの境界を正直にするものでもあります。

developer/protocols/chunker

developer/decisions — ADR-0060、セールスページが描くもの

ページの付属物: 柱と脚注を印づけ、検索から外す 計画中

(参考:改正後全文) のような柱やページ番号は、今日ほかのチャンクと同じチャンクであり、埋め込まれ、検索で返り得ます。目標は page_header または page_footer ブロックです。ページが欠けないよう出力には残し、埋め込みと検索からは外します。見分けるのは同じ位置でページをまたいで繰り返されることであり、ページ単位ではなく文書単位の処理です。

developer/protocols/chunker

developer/decisions — ADR-0060、セールスページが描くもの

改ページをまたいで続くブロック 計画中

22 ページから 23 ページへ続く段落は、今日は無関係な 2 つのチャンクです。目標はそれを 1 つの読みとして保ちます。22 ページのブロックは continues_on を、23 ページのブロックは continues_from を名指しし、それぞれが自分のページとボックスを保つので、来歴はぼやけません。先に段落ブロックが必要で、チャンカーは 1 ページずつ尋ねられるため、文書単位の処理も必要です。

developer/protocols/chunker

developer/decisions — ADR-0060、セールスページが描くもの

ブロックとしての図、記号と接続線ごとのボックス 計画中

工程系統図のグラフは今日読まれ、ページブロックの metadata["process_chart"] に書かれます。それを担うチャンクはなく、ノードにもエッジにもページ上のボックスはありません。目標は diagram_type とボックスを持つ diagram ブロックで、nodesedges がそれぞれ自分のボックスを持ち、スライド上の記号を指せばそのノードが見えるようにします。凡例の行は説明しているノードへ解決され(含侵真空含浸機、ノードの equipment)、ブロックは書き出せる形式を exports に並べます。ChunkPiece が今日担うのはテキストか画像で、構造ではないため、これは契約の変更です。

developer/protocols/chunker

developer/decisions — ADR-0060、セールスページが描くもの

features — Draw.io と Mermaid の図の出力

embed default-text ハッシュ空間

モデルを使わない 256 次元の文字 n-gram 空間です。純粋な計算だけなので、素のインストールでも決定的に埋め込みと検索ができます。コサイン距離、L2 正規化。

packages/indx-capability-embedding-hashed/src/indx_capability_embedding_hashed/provider.py

packages/indx-capability-embedding-hashed/tests/test_hashed.py

tests/bdd/features/embed.feature — Scenario: Embed a text query in a declared space

embed minilm-multilingualclip-vit-b32 –extra fastembed

MiniLM(384 次元、多言語テキスト)と CLIP(512 次元)です。CLIP の画像レーンはクエリと文書側の両方に答えます。後者は、テキストとして誰も読めなかったページ — PDF チャンカーがレンダリングするページ — に対してです。ページが両方を持つ場合はテキストが優先されます。

Terminal window
uv run indx embed --space minilm-multilingual "annual recurring revenue"

packages/indx-capability-embedding-fastembed/src/indx_capability_embedding_fastembed/provider.py

packages/indx-capability-embedding-fastembed/tests/test_engines.py

tests/bdd/features/embed.feature — Scenario: Embed an image query in a space that advertises an image lane

embed hosted-text –extra hosted

LiteLLM 経由で任意のホスト型埋め込みモデルを使います。INDX_EMBED_MODELINDX_EMBED_DIMENSION が必要です。未設定の場合は、壊れた空間ではなく、空間を一切広告しません。

packages/indx-capability-embedding-hosted/src/indx_capability_embedding_hosted/provider.py

packages/indx-capability-embedding-hosted/tests/test_hosted.py

文書側のビジュアル埋め込み –extra fastembed

どの機能もテキストとして読めなかったページは、indx-chunker-pdf が 1 つの画像チャンクへレンダリングし、CLIP のビジョンレーンが埋め込みます。これにより、そのページは何も持たない代わりにベクトルを持ちます。以前は理由を抱えたページブロックだけが残り、検索できる内容は何もありませんでした。文書側の埋め込みがベクトル化するのはチャンクだけであり、テキストのないページはチャンクを生まなかったからです。

計測が先に行われ、テストとしてコミットされています。採用済みのベンチマーク文書 2 件において、ページ画像のベクトルは視覚的なクエリに正しく答える一方、同じページの抽出テキストはそうなりません。富山県の図面では、803 文字の日本語タイトルブロックによって「an engineering drawing」が図面自身よりも領収書のページに近くなります。レンダリングの対象は、リーダーが failed または unreadable と報告したページに限られます。空文字列として読めたページは読めており、白紙だからです。

packages/indx-chunker-pdf/src/indx_chunker_pdf/chunker.py

packages/indx-capability-embedding-fastembed/tests/test_clip_compatibility.py

packages/indx-chunker-pdf/tests/test_chunker.py

リクエストごとに有効化する文書分類

分類器は文書が何であるか — 種別、作った部門、属する業種 — を、文書ブロックの metadataclassification に、ランク付きラベルのファセットとして述べます。言語検出器から複製されたプラグインポートで、テキストで尋ねられますが、残りすべてを形作る違いが一つあります。分類器は呼び出しに対価がかかるので、リクエストが名指ししなかったものは何も動きません。document_ids はインストール済みの分類器を尋ねる順に有効にし、あるファセットについて意見を持つ最初のものがそれを取り、インストール済みの ID は機能スナップショットにハッシュの外で公表されます。答えられるファセットを宣言する分類器は、先の分類器がそのすべてを取った時点でまるごと飛ばされるので、一つのファセットのために二つのレーンを有効にしても、払うのは一回の呼び出しであって二回ではありません。何も宣言しない ID は、宣言されているものを名指しする 422 です。

分類器はテキストを渡され、必要なら自分でそれを縛ります。サンプリングはリクエストフィールドでもデプロイの既定値でもありません。実装に属します。トークン窓や請求を持っているかどうかを知っているのは実装だけだからです。このポートは 5 つのうちの 1 つです。ページ粒度とチャンク粒度での同じ回答と、もう一方の戻り値の形であるエンティティ抽出があります。

Terminal window
uv run indx encode --classifier words file://$PWD/tests/fixtures/invoice-anthropic.pdf
{"id": "document", "metadata": {"classification": {"document_type": [{"label": "invoice", "confidence": 0.5}]}}}

packages/indx-executor/src/indx_executor/classification.py

packages/indx-executor/tests/test_executor.py

developer/protocols/document-classifier

tests/bdd/features/classification.feature — Scenario: The first enabled classifier with an opinion wins a facet

tests/bdd/features/classification.feature — Scenario: A classifier nothing installed declares is refused with a typed error

上のポートの三つの実装です。それぞれが独立したディストリビューションで、それぞれ ID で有効化されます。三つは一つのタクソノミーファイル形式を共有するので、運用者が保守するラベル表は一つです。

単語シグネチャ words、日本語・英語のビジネス文書タクソノミー付き

素のインストールが持つ分類器です。ラベルは単語リストとしきい値で、テキストに十分な数の単語が現れれば文書はそのラベルを持ちます — インボイスのシグネチャの規則を、運用者が書ける表に一般化したものです。モデルなし、エクストラなし、CPU 上での casefold した部分文字列走査。それが認めるべき限界でもあります。「金融」は「金融庁」の中でも発火し、確信度は一致したリストの割合であって、較正された確率ではありません。

三つのタクソノミーが同梱され、各ラベルは両言語で名付けられています。document_type(請求書、見積書、契約書、有価証券報告書、稟議書ほか 15 種)、business_unit(営業、経理・財務、人事、法務ほか 8 種)、industry(製造業、金融、不動産、建設、官公庁・自治体ほか 9 種)。INDX_CLASSIFIER_WORDS_LABELS はそれらを置き換える JSON ファイルを指し、他の二つの分類器も同じファイルを読んで名前だけを使います。

$ uv run indx encode --classifier words file://$PWD/tests/fixtures/invoice-japanese.pdf
document classification: {"document_type": [{"label": "invoice", "confidence": 0.38}]}

packages/indx-classifier-words/src/indx_classifier_words/classifier.py

packages/indx-classifier-words/src/indx_classifier_words/taxonomy.py

packages/indx-classifier-words/tests/test_classifier.py

tests/bdd/features/classification.feature — Scenario: An enabled word-signature classifier labels an invoice on the document block

tests/bdd/features/classification.feature — Scenario: The word-signature classifier labels a Japanese invoice

ゼロショット分類 zeroshot –extra zeroshot

MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7 — MIT、0.3B パラメータ、日本語 XNLI で正解率 0.80、学習に英語 MNLI を含む — を、transformers のゼロショットパイプラインを onnxruntime 上に書き下ろした形で実行します。各ラベルは渡されたテキストに対する一つの仮説(This document is a invoice (請求書).)になり、含意確率がそのラベルの確信度です。Xenova ミラーが出荷する ONNX エクスポートのおかげでロックに torch は入らず、ランタイムは OCR と埋め込みがすでに使っているものです。重みは最初の使用時にダウンロードされ、検出中には決してダウンロードされません。

認めるべき限界が二つ。トークン窓はたいていの文書より狭いので、モデルは渡されたものを切り詰めてその先頭を読みます。そして日本語と英語のインボイスを分類する同梱テストは、重みがキャッシュされている場所でしか動きません。CI はエクストラをインストールせず、何もダウンロードしません。

$ uv run indx encode --classifier zeroshot file://$PWD/tests/fixtures/invoice-japanese.pdf
document classification: {"document_type": [{"label": "invoice", "confidence": 0.83}, …]}

packages/indx-classifier-zeroshot/src/indx_classifier_zeroshot/engine.py

packages/indx-classifier-zeroshot/src/indx_classifier_zeroshot/classifier.py

packages/indx-classifier-zeroshot/tests/test_classifier.py

tests/bdd/features/classification.feature — Scenario: A zero-shot NLI model labels an invoice in either language

tests/bdd/features/classification.feature — Scenario: A zero-shot NLI model labels a Japanese invoice

LLM 分類 llm –extra llm

LiteLLM が到達できる任意のモデルに、タクソノミーとテキストを渡し、ファセットごとの JSON オブジェクト一つを求めます。エクストラと INDX_CLASSIFIER_LLM_MODEL が必要で、未設定なら分類器をまったく公表しません。ホスト型モデルは external デバイスであり、それを有効にした data_residency 付きのリクエストは、よそで分類されるのではなく、ソースの取得前に拒否されます — 飛ばすのではなく拒否です。呼び出し側が求めたのですから。ベンダーが請求した額は usage.cost_usd に届きます。

Terminal window
INDX_CLASSIFIER_LLM_MODEL=anthropic/claude-sonnet-5 uv run indx encode --classifier llm file://$PWD/report.pdf

packages/indx-classifier-llm/src/indx_classifier_llm/classifier.py

packages/indx-classifier-llm/tests/test_classifier.py

tests/bdd/features/classification.feature — Scenario: An LLM classifier reaching a local endpoint labels an invoice

tests/bdd/features/classification.feature — Scenario: An LLM classifier reaching a local endpoint labels a Japanese invoice

tests/bdd/features/classification.feature — Scenario: An LLM classifier with no opinion leaves the document unlabelled

ページごと・チャンクごとの分類器

同じ分類器の回答を、より細かい単位で。PageClassifier はページごとに一度、ChunkClassifier はチャンクごとに一度尋ねられ、同じ classify(text) を通じて同じランク付きラベルのファセットを返します。変わるのは単位とプロバイダーのフックだけです。表紙が申請書で本文が往復書簡である一件書類には単一の文書種別がなく、それこそ文書レベルのポートが述べられない事例です。

ページの回答は各ページブロックの metadataclassification として、チャンクの回答は文書ブロックに chunk_classification としてチャンクブロック ID で引ける形で載ります。チャンクブロックは自前のメタデータを持たないからです。あるファセットについて意見を持つ最初の分類器が、文書についてではなくその単位についてそれを取ります — これは target フィールドを持つ単一のポートでは正しくできなかったマージ規則です。

チャンク分類器はチャンク粒度を要します。CHUNK を求めずにこれを名指しすることは、ソース取得前の 422 です。分類すべきものが存在しないからです。

出荷済みの 3 つの分類器 wordszeroshotllm はすべて、文書用のフックに加えてページ用とチャンク用のフックからも宣言されています。1 つのオブジェクト、3 つの単位で、呼び出し側は名指すフィールドで単位を選びます。プレイグラウンドの「分類の単位」も同じことをします。

developer/protocols/classifiers

developer/decisions — ADR-0029

ページとチャンクにまたがる固有表現抽出

PageEntityExtractorChunkEntityExtractor は、テキストの中の名前を見つけ、それがどこにあるかを述べます。ラベルから EntitySpan への写像で、各項目は表層のテキスト、[start, end) の文字範囲、信頼度を持ちます。これはポートの格子の 2 つ目の軸です — 分類器はテキストが何であるかを述べ、抽出器はその中で何が名指しされているかを述べます。文書レベルの抽出器は意図的にありません。ページがすでに隙間なく文書を敷き詰めているからです。

スパンは、それを名指しするブロックの text への文字オフセットです。 どのブロックもすでに text を持つので、呼び出し側はすでに受け取ったデータで任意のスパンを解決できます。オフセット表も、座標の書き換えも、ワイヤー上の新しいものも要りません。バイトではなく文字であることは効いています。UTF-8 の日本語では両者が 3 倍違います。

スパンは 3 つ目の予約キー entities として文書ブロックに載り、それぞれが見つかったブロックで引けます。チャンクブロックは不活性のままです。その傍らにエグゼキューターは集約を書きます。(ラベル, NFKC 正規化した表層形) ごとの数と、残された生の表層形です。正規化は「ABC株式会社」と ABC株式会社 を統合し、そこで止まります — 数は同一性ではありません。 共参照解析はスコープ外で、「山田太郎」と「山田」は 2 つの項目のままです。

{"id": "document", "metadata": {"entities": {
"by_block": {"page:1": {"organization": [{"text": "ABC株式会社", "start": 12, "end": 19, "confidence": 0.94}]}},
"counts": {"organization": [{"surface": "ABC株式会社", "count": 3, "forms": ["ABC株式会社", "ABC株式会社"]}]}}}}

developer/protocols/entity-extractors

extend/entity-extractor

developer/decisions — ADR-0030

developer/decisions — ADR-0032

日本語と英語の固有表現抽出、4 つのレーン

抽出器ポートの 4 つのディストリビューション。埋め込みと分類器のファミリーがすでにそうであるように、依存のあるところで切り分けます。

indx-ner-patterns — エクストラなし、両言語、素のインストールに載ります。金額、日付、郵便番号、請求番号と登録番号、メールアドレス、電話番号、そしてデプロイ自身の辞書。indx-ner-vibrato--extra ja、日本語、形態素解析に vibrato を使います。固有名詞 が人名・組織・地域を直接タグ付けし、Token.start() / .end() はすでに文字オフセットです。indx-ner-onnx--extra ner、両言語、onnxruntime 上のトークン分類モデル。indx-ner-llm--extra llm、両言語、LLM 分類器がすでに持つレーンを再利用します。

現在地。 indx-ner-patterns は出荷済みです。ページ用とチャンク用の両方で宣言された 1 つの抽出器 patterns が、メールアドレス、登録番号、請求番号、どちらの暦の日付も、金額、郵便番号、電話番号、そしてデプロイが INDX_NER_PATTERNS_GAZETTEER に列挙したものを見つけます。indx-ner-vibrato--extra ja の背後で出荷済みです。IPADIC の固有名詞タグが personorganizationlocation のスパンになり、接する姓と名は 1 人にまとめられ、辞書は INDX_NER_VIBRATO_DICTIONARY が指定されない限り INDX_NER_VIBRATO_CACHE に一度だけ取得されます。indx-ner-onnx--extra ner の背後で出荷済みです。onnxruntime 上のトークン分類チェックポイントで、既定は 10 言語で人名・組織・地名・日付に微調整された多言語 BERT です。日本語は事前学習には含まれ微調整には含まれないので、そこで見つかるものは何も測っていない転移です。テキストは INDX_NER_ONNX_WINDOW_CHARACTERS の窓で見せ、すべてのスパンは入力の座標で返ります。indx-ner-llm--extra llm の背後で抽出器 ner-llm として出荷済みで(llm は分類器がすでに持ち、5 つのポートは 1 つの名前空間を共有します)、INDX_NER_LLM_MODEL があるときだけ公表されます。INDX_NER_LLM_LABELS が名指すラベルの下でチャットモデルに表層形を尋ね、テキスト中のすべての出現を自分で探します。スパンはブロックのテキストを正確に指さねばならず、モデルの数え方はそうではないからです。4 つのレーンはすべて完了で、ベンチマークがいまは互いに対して測っています。6 つのスコープの 30 のラベル付き固有表現に対し、各レーンを同じ計画の上で単独に走らせ、ner-llm が 80%、patterns が 50%、onnx が 27%、vibrato が 3% を読み取りました。

何も作る前に述べておくべき制約が 3 つ。組織をタグ付けする vibrato の同梱辞書は IPADIC だけです — どの UniDic ビルドも人名と地名を持ち、組織 をまったく持ちません — なので事実上 IPADIC が確定で、ダウンロード 7.7 MB、常駐 47.8 MB です。vibrato の tokenize()GIL を解放しません。トークナイズはスレッド間で直列化されるので、プロセスごとに 1 インスタンスか、スレッドごとに辞書を丸ごと複製するかです。そして linux aarch64 のホイールがありません。ARM Linux は sdist に落ち、Rust ツールチェーンを要します。

zeroshot の ONNX スタックはこれに一般化できません。その run(premise, hypotheses) は骨の髄まで NLI です。entailment / contradiction のラベルキー、ペアエンコード、only_first の切り詰め、そして [batch, num_labels] を前提とするロジットのループ — トークン分類が出すのは [batch, seq_len, num_labels] です。ダウンロード、セッション、フィードフィルタの配管は流用できますが、ヘッドは新規です。

正確さの順序は実測になりました。帳票の上で。 benchmarks/case.schema.json のスキーマ 1.4 は expected_entities を持ち、レポートはインストール済みの各レーンを単独でそれに対して採点します。ホストされたモデルが最も多く読み、費用のかかる唯一のレーンで、その取りこぼしの 6 つは既定のラベルが尋ねない電話番号と登録番号です。無料のレーンの中でこのコーパスで patterns レーンが勝つのは、領収書や提出書類の表紙が日付・金額・電話番号・登録番号を固定の形で述べるからです。人名や組織名が散文の中で名指されるところではモデルのレーンが勝ち、vibrato の低い数字の大半は粒度の問題です。IPADIC は 任天堂 と 株式会社 を 2 つの名詞としてタグ付けしますが、人は 1 つの組織として読みます。6 スコープ 30 個は意図的に小さな集合です。economics.quality は今もテーブルの定数です。

developer/protocols/entity-extractors

benchmarks

developer/decisions — ADR-0035

言語ごとの抽出レーン 計画中

リクエストが言語をレーンに対応づけ、実行器は検出器がその言語を与えたページにだけそのレーンを走らせます。その答えはすでにブロックの languages に書かれています。今は名指しされたすべてのレーンがすべてのページで走り、ラベルごとに最初に答えたレーンが勝ちます。

developer/decisions — ADR-0050

機能一覧 — ページ単位と文書単位の言語検出

サンプリングは契約のものではなく実装のもの

ここにあった DocumentSampler ポートは取り下げられました。 分類器に何を見せるかを独自のポートにし、spreadheadrandomwholefixed をそのディストリビューションとして出荷し、リクエストが分類器ごとに組み合わせる、という計画でした。ポートの分割は逆方向に進みました。理由は、その縛りがページで述べられていてチャンク対象の実装には何も意味せず、それが生む連結された出力は文字スパンをそもそも運べないからです。

そこでサンプリングは、契約から昇格するのではなく契約を離れました。TextSampleclassification.sample、契約上の上限 2 つ、INDX_CLASSIFICATION_SAMPLE_* の 5 変数、エグゼキューターの sample()--classification-sample は無くなりました。分類器は自分の単位の全文 — 読めたすべてのページを、丸ごと順に — を渡され、自分の設定から、indx-interfacesexcerpt を使って自分で縛ります。近くの空白境界で切ったテキストの先頭で、文書が自分が何であるかを述べる場所です。

代償は実装に降り、請求があるところでは 1 つの数です。indx-classifier-llm は呼び出しの前に INDX_CLASSIFIER_LLM_MAX_CHARACTERS(既定 6,000)で切ります。indx-classifier-zeroshot はもともとトークン窓より先を読まないので、モデルは以前どおりテキストの先頭を見ます。indx-classifier-words はすべてを走査します。単語リストには上限をかける価値のある文字あたりの費用がないからです。

packages/indx-executor/src/indx_executor/classification.py

developer/decisions — ADR-0033

developer/decisions — ADR-0034

CPU でのコーパス分類 計画中

分類の半分は上で構築済みで、単語シグネチャ分類器は構造上 CPU のみです。残るのはコーパスの半分です。標準的な入力をコーパス全体で整理し、コーパス規模の実行ごとに CPU のみの割合と総費用を報告することは、下記のコーパス実行を待ちます。

developer/core-user-features

エンリッチメント: 文書・ページ・チャンクごとの要約とタグ

ラベルとスパンの隣に立つ 3 つ目の戻り値の形です。Enrichmentsummary(テキスト自身の言語で書かれた散文)と tags(タクソノミーから選ぶのではなくエンリッチャーが作る開いた語彙)を運び、各部分は省略可能です。DocumentEnricherPageEnricherChunkEnricher はその形の上に立つ分類器と同じ 3 つの単位で、enrichment.{document,page,chunk}_ids で単位ごとに有効化され、取得前に分類器と同じ 3 通りで拒否され、ハッシュの外の snapshot.enrichers に公表されます。各部分は意見を持つ最初の有効化済みエンリッチャーが取るので、["extractive", "enrich-llm"] を名指しするリクエストは要約を床から、タグをモデルから得ます。

{"enrichment": {"document_ids": ["extractive", "enrich-llm"]}}
"metadata": {"enrichment": {
"summary": "振込先 みずほ銀行 東京支店 普通 1234567 株式会社INDX 東京都港区六本木7丁目7-7",
"tags": [{"label": "billing", "confidence": 0.9}]}}

レーンは 2 つ。extractive はエクストラを必要とせず、単位自身の語彙を最も多く運ぶ文をそのまま文書の順に返すので、要約が述べることは文書が述べることです。タグは作りません。タグはテキストが含まなくてもよい語であり、語の頻度からでっち上げれば何も計測しない主張を公開することになるからです。enrich-llm –extra llm はチャットモデルに 1 回の呼び出しで両方の部分を尋ねます。独自の INDX_ENRICH_LLM_* 接頭辞なので、デプロイは分類とは別のモデルで要約でき、デバイスの規則は LLM 分類器に従います。ページエンリッチャーは各ページブロックに、チャンクエンリッチャーは文書ブロックの chunk_enrichment に書き、どちらのキーも予約されています。

developer/protocols/enrichers

developer/decisions — ADR-0037

packages/indx-enrich-extractive/src/indx_enrich_extractive/enricher.py

packages/indx-enrich-llm/src/indx_enrich_llm/enricher.py

文書メタデータ(導出分と呼び出し側指定分)

1 つの穴に 2 つの半分があり、その両方が埋まりました。文書ブロックは、自分が何であり、どこから来たのかを述べるようになり、呼び出し側自身のラベルは往復を生き延びます。

導出される半分は、それぞれの辞書が何のためのものかで分かれます。provenance は、すべてのブロックが既に運んでいた計画の束縛に加えて media_typesource_uri — インライン投稿が名乗った filename — を得ます。エクスポートされた行は計画が傍にない状態で出所を述べられなければならず、ダイジェストは誰かが検索するものではないからです。metadata は呼び出し側のラベルと検出された言語を運びます。この分け方は用語集自身の規則です。provenance はブロックの出どころ、metadata はそこから抽出されたものです。

呼び出し側の半分は EncodeRequestmetadata です。所有者、テナント、機密度の分類、任意の JSON オブジェクト。indx はそれを運び、いずれも強制しません。そのベクトルを誰が読めるかはルーターではなくインデックス側の問いです。意図的に PlanRequest には置いていませんplan_id は決定される計画のすべてのフィールドのハッシュなので、テナントラベルがあると、同一の文書二つが、何もルーティングしない値のせいで異なる二つの計画を持つことになります。

sensitivity は呼び出し側のラベルであり、indx が導出するものではありません。メディアタイプを超えた文書「種別」は分類であり — その後、上で構築され、ここではなく独自の予約キー classification の下に書かれます。POLICY_VERSION は動かず、機能スナップショット ID は前後でバイト単位まで同一です。これらのどれもルーティングの決定ではありません。

{
"id": "document", "kind": "document",
"metadata": {"tenant": "acme", "sensitivity": "internal",
"languages": [{"language": "ja", "confidence": 0.98}]},
"provenance": {"media_type": "application/pdf", "source_uri": "s3://filings/2025/report.pdf",
"plan_id": "sha256:…", "source_digest": "sha256:…"}
}

packages/indx-executor/src/indx_executor/service.py

packages/indx-executor/tests/test_executor.py

tests/bdd/features/metadata.feature — Scenario: A caller’s own labels travel with the source

ページ単位と文書単位の言語検出 –extra lang

indx-language-lingualingua を通じて、読み取れた各ページがどの言語で書かれているかを名指しします。すべてのページブロックが自分の答えを運び、文書ブロックはその平均を確信度の高い順に運びます。

これはインポートではなく新しいプラグインポートを通じて届き、バイト列ではなくテキストで尋ねられます。これがオブザーバーではない理由です。プリフライトはコンテンツをデコードせず、言語は文字についての事実です。language_hint がまさにその理由で 2 番目のスライスからこのスライスまでルーターのプライベートなプリフライトコンテキストに未代入のまま置かれていました。それは、ついに埋められるのではなく削除されました。

文書の平均は、各ページのスコアがどれだけのテキストに対して算出されたかで重み付けされます。単純平均では、六語しかない扉ページが一章分を上回ってしまいます — OCR の自己申告確信度についてすでに重み付けが正した誤りと同じものです。

認めた限界が二つ。20 文字未満のテキストには答えが一切返りません。統計的検出器はほんの数文字を自信たっぷりに、そして誤って採点するからです。そして INDX_LANGUAGE_MINIMUM_CONFIDENCE(既定 0.05)は飾りではありません。エンジンは構築時に与えられた 75 言語すべてについて値を返すため、自分のコーパスを知っているデプロイは INDX_LANGUAGE_CANDIDATES でそれを名指しし、メモリも誤りの余地も余分に払わずに済みます。

$ uv run indx encode --metadata '{"tenant":"acme"}' file://$PWD/report.pdf
document languages: [{"language": "ja", "confidence": 0.98}]
page:1 languages: [{"language": "ja", "confidence": 1.0}]

packages/indx-language-lingua/src/indx_language_lingua/detector.py

packages/indx-executor/src/indx_executor/language.py

developer/protocols/language-detector

フォーマットがすでに宣言している言語 計画中

OOXML は dc:language を述べますし、PDF のカタログは /Lang を持てます。フォーマットがそう述べているなら、それは文字に対する統計的な推測より良いシグナルです。そしてそれを読むのはポートの変更ではなく 2 つ目の LanguageDetector ディストリビューションであり、だからこそ安上がりです。

短い文書で統計的な答えが弱いと分かったときの後続であって、統計的な方を飛ばしてよい理由では決してありませんでした。読み取り可能な 11 形式のうち 10 は何も宣言せず、20 文字未満のテキストは設計上そもそも統計的な答えを得られません。

先送り、2026-09-08 時点、その計測ができるまで。ラベル付きセットはまだ言語検出を採点しておらず、統計的な答えが弱いと述べるものは何もありません。この検出器は、どの計測も求めていないシグナルのために ID、広告、リクエストフィールドを負うことになります。作る順のフェーズ 4 を離れ、他の未定項目とともにトリガーを待ちます。

developer/protocols/language-detector

developer/decisions — ADR-0017

ブロックごとの定義語 計画中

法令は語を初めて使うところで定義し(勧告保健所 は入院勧告の通知を行った保健所)、以後のすべての条項はその意味に依存します。今日これを読むものはありません。目標は定義しているブロックの defined_terms で、それぞれが term とその means を持ちます。定義語や重要な語を使うブロックには marks を付け、後の条項から答えるエージェントが定義を一緒に運べるようにします。

developer/decisions — ADR-0060、セールスページが描くもの

参照先へ解決された相互参照 計画中

次表法第22条。条項は表、別の条項、法令を指しますが、今日その語はテキストでしかありません。目標は指す側のブロックの references で、それぞれが印字された text と、解決先の target(文書内のブロック ID か、名前のある外部の条文)を持ち、指される側のブロックには referenced_by を付けます。指す先の見出しブロックと表ブロックが必要で、参照はスパンなのでスパンの固定も決着している必要があります。

developer/decisions — ADR-0060、セールスページが描くもの

キャプションと写っているものを持つ画像ブロック 計画中

indx-chunker-pptx はスライドの写真を画像チャンクに切り出し、clip-vit-b32 はそれを埋め込めますが、写真が何を写しているかは何も言いません。目標は caption(文書がその横に置いたテキスト)、descriptiondepicts の一覧(真空含浸機コイル)を持つ image ブロックで、写真を言葉で見つけられ、エージェントが引用できるようにします。説明はモデル呼び出しなので、エンリッチャーと同じくエクストラの後ろに置き、リクエストが名指ししたときだけ動きます。

developer/decisions — ADR-0060、セールスページが描くもの

エージェント向けエクスポート 計画中

処理済みコンテンツを利用者管理のインデックスへエクスポートします。後からベクトルを検索できるよう、埋め込み空間とクエリエンコーダーも特定されます。

developer/core-user-features

Draw.io と Mermaid の図の出力 .pptx

テキストとボックスだけでなく、構造化された図を出力します。工程系統図パーサーは、コネクタで結ばれた記号のグラフをページブロックの metadata["process_chart"] に書き、indx_capability_process_chart.drawio.to_drawio() がそのグラフを、人が diagrams.net で開いて編集し続けられる .drawio ファイルとして書き出します。記号は diagrams.net のプリセットになり、contains の辺は親子関係になるので領域をドラッグすればまとめた線も動き、矢じりなしで描かれたコネクタは矢じりのないままです。ファイルはチャートだけの関数です。タイムスタンプもエージェント文字列も圧縮もないので、tests/fixtures/office-process-chart.drawio をバイト単位で比較でき、形はずれません。その隣の to_mermaid() は同じグラフを Mermaid のソースとして書きます。tricirclediam がこの convention の三角形、円、ひし形です。プレイグラウンドのチャートタブは Mermaid で図を描き、2 つのファイルと、図の SVG と PNG を、デモホストの POST /chart/{format} 経由で提供します。indx serve のデプロイにエクスポートのエンドポイントはなく図も描かれません。その理由といつ変わるのかは ADR-0058 と ADR-0059 が述べます。レシピが教えるべき顧客固有の出力形式の最初のものです。

developer/decisions — ADR-0058

developer/decisions — ADR-0059

機能一覧 — レシピと類似形式の再利用

モデルベンダーは自由に選べる

モデルを使う 2 つのレーンは LiteLLM を経由します。Anthropic、OpenAI、Ollama をはじめ、LiteLLM が対応するベンダーは今日から使えます。INDX_VLM_MODEL が視覚モデルを、INDX_EMBED_MODEL がホスト型埋め込みモデルを選びます。indx 側のコード変更は不要です。モデル ID と認証情報だけです。

Terminal window
INDX_VLM_MODEL=anthropic/claude-sonnet-5 # openai/gpt-4o、ollama/llava なども可
INDX_EMBED_MODEL=openai/text-embedding-3-small
INDX_EMBED_DIMENSION=1536

packages/indx-capability-generic-vlm/src/indx_capability_generic_vlm/settings.py

packages/indx-capability-generic-vlm/tests/test_provider.py

packages/indx-capability-embedding-hosted/tests/test_hosted.py

許可されたモデルごとに 1 つの ID 計画中

各 LLM レーンがリスト設定のモデルごとに分類器、抽出器、エンリッチャーの ID を 1 つずつ広告するので、classification.document_ids とその仲間がレーンを選ぶのと同じ仕方でモデルを選び、デプロイのリストが青写真の Deployment 画面が見せる許可リストになります。今は各レーンが 1 つの変数(INDX_CLASSIFIER_LLM_MODELINDX_NER_LLM_MODELINDX_ENRICH_LLM_MODEL)から 1 つのモデルを取ります。

developer/decisions — ADR-0050

機能一覧 — LLM 分類

ターゲットごとのクラウドネイティブ AI サービス 計画中

同じレーンを、顧客のクラウドがすでに提供する AI サービスに向けます。AWS なら Bedrock、Azure なら Azure OpenAI、GCP なら Google AI。LiteLLM は 3 つすべてと話せるので、それぞれはコードではなく上の変数のモデル ID であり、デプロイテンプレートはそれを変数として運びます。すべての構成を作る必要はなく、それぞれが動くことを示す必要があります。それが基盤をクラウドに縛られたものではなく、クラウドに柔軟なものにします。

デプロイ — イメージとターゲット

スプリント — スプリント 1

4 つの操作、3 つのインターフェース

同じ契約を Python、CLI、HTTP から使えます。実際の encode 結果(抜粋):

Terminal window
uv run indx capabilities
uv run indx plan file:///absolute/path/report.pdf
uv run indx encode file:///absolute/path/report.pdf
uv run indx embed --space default-text "annual recurring revenue"
{
"blocks": [{"id": "document", "kind": "document", "text": "Invoice\nInvoice number MRFL4LVY-0006\n…", "status": "completed"}],
"trace": {"events": [{"planned_capability_id": "invoice-parser", "actual_capability_id": "invoice-parser",
"device": "cpu", "status": "completed", "fallback_index": 0, "latency_ms": 3}]},
"usage": {"bytes": 68446, "pages": 1, "cost_usd": 0.0, "latency_ms": 8}
}

developer/api-contract

packages/indx/tests/test_facade.py

packages/indx-app-cli/tests/test_cli.py

packages/indx-app-server/tests/test_app.py

tests/bdd/features

型付きの失敗

エンベロープは 1 種類、拒否は区別可能です。どのローダーも解決しない URI スキームは 415 unsupported_source。どの機能も読めないメディアタイプは、そのコードなしの 415。どのオブザーバーも観察できないメディアタイプは 422。インストール済みのどれも生成しないブロック粒度は 422 unsupported_granularity。ほかに 409 plan_conflict413 input_too_large503 capability_unavailable があります。

{"error": {"type": "unsupported_media_type", "code": "unsupported_source",
"message": "no installed loader resolves ftp:; this installation resolves data, file, http, https, s3",
"param": "source.uri.uri"}}

packages/indx-interfaces/src/indx_interfaces/errors.py

packages/indx-interfaces/tests/test_contracts.py

packages/indx-app-server/tests/test_app.py

tests/bdd/features/errors.feature — Scenario: An invalid request names the field and echoes the request ID

どのコンポーネントが答えたのかを、すべてについて述べる

トレースが計画対実際を行うのは機能についてで、actual_capability_id の隣の planned_capability_id こそがフォールバックを可視にしているものです。機能は比較すべき計画側を持つ唯一のコンポーネントで、かつては帰属されている唯一のコンポーネントでした。いまは trace.components が残りを名指しします。バイト列を取得したローダー、メディアタイプを名付けた sniffer、各ページを切ったチャンカー、各ページの言語を答えた検出器、各ファセットを勝ち取った分類器を、それぞれを宣言したディストリビューションの名で。ディストリビューションは運用者がインストールする単位であり、その多くが持つ唯一の名前です。

{"trace": {
"events": [{"planned_capability_id": "native-extraction",
"actual_capability_id": "generic-ocr", "fallback_index": 1}],
"components": [{"role": "loader", "distribution": "indx-loader-file"},
{"role": "sniffer", "distribution": "indx-observer-pdf"},
{"role": "chunker", "distribution": "indx-chunker-pdf", "pages": [1, 2]},
{"role": "chunker", "distribution": "indx-chunker-page", "pages": [3]},
{"role": "classifier", "distribution": "indx-classifier-words",
"id": "words", "facets": ["document_type"]}]}}

2 つのチャンカーが 1 つの文書を分担し、結果はそう述べます。各チャンクブロックは今も provenance に native-extraction — ページを読んだ機能 — を報告し、チャンクの始まりを決めたディストリビューションはその隣のトレースにあります。宣言された型のインライン入力はローダーも sniffer も名指ししません。何も取得せず何も認識していないからです。拒否されたエンコードは、ルーティングできなかった入力を取得し認識したものを名指しします。

オブザーバーは観察が起きる計画側で名指しされます。plan()PlanResult を返します。ハッシュされた RoutePlanplan に、その隣にそれを生み出したローダー、sniffer、オブザーバーが同じ components の形で載ります。自分で計画したエンコードはプランナーのオブザーバーを自分のトレースに運ぶので、計画なしのエンコードは端から端まで帰属できます。計画を渡されたエンコードは何も名指ししません。その観察はすでに起きたものだからです。帰属は選択に先立ちます。呼び出し元はシステムが同定しないものを名指しできないからです。いまは両側が名指しされています。

packages/indx-interfaces/src/indx_interfaces/encoding.py

developer/decisions — ADR-0027

developer/refactoring

デッドライン、キャンセル、相関

INDX_REQUEST_TIMEOUT_SECONDS がリクエストを縛ります。未設定または 0 はデッドライン無しを意味し、それを過ぎたリクエストは 504 request_timeout を答えます。接続を切ったクライアントは、反対側から同じ機構を作動させます — ウォッチャーが request.is_disconnected() をポーリングし、コード client_disconnected でデッドラインを倒します。これはファサード呼び出しがスレッドプールで走り、イベントループがポーリングのために空いているからこそ機能します。

デッドラインは協調的であって、プリエンプティブではありません。走っているコールを途中で中断するものはありません。check_deadline() は、ソースがロードされた後の Router.plan、エグゼキューターのラダーでの各機能試行の前、各埋め込み器コールの前で走ります — リーダーの中では決して走らないので、上限が過ぎたときすでに走っていた機能は、中断されずに完走します。

相関とリクエスト同一性は、2 つの異なる場所にある 2 つの異なるものです。X-Request-ID — 呼び出し元自身のヘッダー値か、無ければ生成されたもの — は 500 を含むすべてのレスポンスに載り、各リクエストが書く 1 行の JSON ログにも載ります。エンベロープの request_id はリクエストボディにあった呼び出し元の値で、クライアントがレスポンスと送信内容を突き合わせられるように反射されます。どちらも他方の代わりにはなりません。

packages/indx-interfaces/src/indx_interfaces/context.py

packages/indx-app-server/src/indx_app_server/app.py

packages/indx-app-server/tests/test_app.py

developer/decisions — ADR-0023

設定はディストリビューションごとに 1 ファイル

設定は環境変数で、すべて INDX_* です。そのひとつひとつが、所有するディストリビューションの settings.py にある IndxSettings サブクラスのフィールドであり、他のどこにもありません — だから「これは何で設定できるのか」は、15 パッケージにまたがる grep ではなく 1 ファイルです。os.environos.getenv は lint 規則で禁止されているので、新しいリーダーはそこから始めるほかありません。

load() は、フィールド位置と理由だけからすべてのメッセージを 1 箇所で組み立て直すので、認証情報が呼び出し元の読む 422 へ紛れ込むことはありません。文言も 3 種類から 1 形式になりました。12 個の手書きリーダーに散っていた “must be positive”、“must be at least”、“must be a number” が、今は 1 つの形です。空白のみの値はどこでも既定を意味します。これは 1 つのリーダーでの振る舞いであり、4 つではパースエラーでした。

サードパーティの機能は INDX_<VENDOR>_* を主張し、欠けている変数を名指しする場所は利用不可ディスクリプターです — generic-vlmhosted-text がすでに作った先例です。

INDX_OCR_DPI: Input should be greater than 0
unavailable_reason: "no vision model configured; set INDX_VLM_MODEL"

packages/indx-interfaces/src/indx_interfaces/settings.py

developer/api-contract

developer/decisions — ADR-0020

外部通信なしで動作する

既定のインストールはどこにも接続しません。indx-loader-file はディスクから解決し、2 つのオブザーバーはネットワークを開かず、ネイティブ抽出・OCR・ハッシュ空間はローカル CPU で動きます。ベンダーに到達するレーンは、利用者が自分でインストールして名前を与えたものだけです。スキームで選ばれる indx-loader-http、エクストラとモデル環境変数の背後にある generic-vlmhosted-text がそれにあたります。さらに data_residency は external デバイス上の能力を拒否するため、制約付きのリクエストが誤ってそこへエスカレーションすることはありません。

packages/indx-router/src/indx_router/policy.py

packages/indx-loader-http/tests/test_loader.py

packages/indx-source/tests/test_source.py

コーパス実行と逐次的な結果 計画中

単一文書向けの 4 操作ではコーパスを分類できません。コーパス実行は進捗を報告し、キャンセル可能で、1 件の失敗が実行全体を落とさず、ベンチマークが求める CPU のみの割合と 1,000 文書あたりの費用を集計します。読み取れなかった内容はマニフェストから消えるのではなく、そこに名前として残ります。同じ穴は 1 文書の規模でも現れます。長い encode が最後のページまで何も返さない状態です。ブロックは完了した順に届くようにします。

developer/core-user-features

Web アプリとはプロダクトの青写真で、このサイトからモックとして配信しています。画面ごとに小節を置き、その下に画面が提供する機能ごとの項目を、人が何をできるかとして述べます。計画中 の機能は背後に使えるものがなく、進行中 の機能はサービスが API かプレイグラウンドを通じてすでに答えるものの、画面は存在しません。Web アプリがまだ存在しないので、実装済みのものはありません。何を先に作るかはロードマップにあります。

サインイン後の最初の画面。ワークスペースと、その中身と、どう改善されるか。 青写真 — Overview · ロードマップ

ワークスペースと実行環境 計画中

テナント名、動いている環境、すべてのシステムが稼働中だと示すピル。ピルには今も /health が答えます。テナントとサインインは運用の成熟です。

機能一覧 — 運用の成熟

空間のサマリーカード 進行中

最初の 3 つの埋め込み空間をレコード数、次元数、宣言された特徴つきで示し、それぞれ詳細を開きます。空間と次元数はケイパビリティスナップショットにあり、数にはデータストアが必要です。

機能一覧 — 4 つの埋め込み空間

試すリンクつきのエンドポイント一覧 進行中

ワークスペース上のすべてのエンドポイント、そのパス、提供する空間を、プレイグラウンドへのリンクつきで。4 つの操作は存在し、ユーザーが作るエンドポイントは存在しません。

機能一覧 — 4 つの操作、3 つのインターフェース

改善サイクル 計画中

取り込む、意味を加えて教える、検証して使う、をタイムラインとして描き、few-shot ライブラリを開きます。ライブラリが必要です。

Layer 01。モデル、データ、特徴、バージョンを 1 か所で管理します。 青写真 — Embedding spaces · ロードマップ

空間カード 進行中

空間ごとのカード。名前、バージョン、中身、モダリティ、レコード数、次元数、特徴、計算場所(CPU とローカル、または承認済みモデル)。indx が出荷する 4 つの空間がモデルの半分を満たし、レコードとバージョンにはストアが必要です。

機能一覧 — 4 つの埋め込み空間

空間を作る 計画中

空間に名前をつけ、環境の許可リストから使ってよいモデルを選びます。

空間の詳細 進行中

1 つの空間の距離、モデル、バージョン方針。距離とモデルは今も空間ごとに広告されています。評価の後にだけ切り替える方針にはバージョンが必要です。

機能一覧 — 4 つの埋め込み空間

空間ごとのバージョン 計画中

空間バージョンは、その下に埋め込まれたレコードに対するモデルと特徴スキーマの束ねを保存したもので、候補がより良く採点されたときだけ切り替わります。今あるバージョンはインストールのスナップショット ID だけです。

Layer 01。文書、ベクトル、属性、関係を同じレコードから辿ります。 青写真 — Data store · ロードマップ

レコード表 計画中

取り込んだすべての文書を種類、取引先、モダリティ、索引済みの状態つきで。今は何も永続化されず、エクスポートとコーパス実行がこれを満たすものです。

機能一覧 — エージェントが読めるエクスポート

機能一覧 — コーパス実行と逐次結果

名前、タグ、取引先でフィルタ 計画中

ファイル名、タグ、取引先の値で表を絞る 1 つの入力欄。

来歴の連鎖 進行中

レコードをソースファイルからページとチャンクを経て、埋め込んだ空間バージョンまで辿ります。ブロック ID、ソースダイジェスト、トレースのコンポーネント名がすでにレスポンスごとにこれを言います。連鎖が指す先にストアが必要です。

機能一覧 — どのコンポーネントが答えたかを言う

関連レコード 計画中

同じ取引先や同じ契約など属性を共有するレコードを、開いているレコードの横に並べます。

データを取り込む 計画中

同じ画面からストアに文書を追加します。アップロードはプレイグラウンドにあり、ストアへの取り込みはありません。

プレイグラウンド — アップロード

Layer 01。空間に宣言した業務の軸で、フィルタ、分類、抽出します。 青写真 — Features & tags · ロードマップ

空間ごとの特徴スキーマ 計画中

空間の特徴の表。名前、型(カテゴリ、数値、エンティティ、テキスト、ベクトル)、値、由来。今はどこにもスキーマは宣言されていません。

特徴を追加する 計画中

特徴に名前をつけ、型を選び、値を説明します。

特徴の由来 進行中

各特徴はどこから来るかを名指しします。人、分類器、抽出、ビジョンエンコーダ。4 つとも呼び出し元のメタデータ、分類ファセット、エンティティスパン、シグネチャとしてすでにリクエストごとに答えます。画面はそれを宣言済みの特徴に結びつけます。

機能一覧 — 分類器、エンティティ抽出、エンリッチメント、文書メタデータ

機能一覧 — シグネチャ

特徴の値でフィルタ 計画中

supplier = 北川製作所document_type = 請求書 でレコードを絞ります。同じ軸を分類と抽出に使い回します。ストアが必要です。

Layer 02。文書に対して機能を試し、結果を検証し、残します。 青写真 — Playground · ロードマップ

機能の選択 進行中

セマンティック検索、情報抽出、自動タグ付け、レイアウト検出、few-shot 検出、関係、OCR ルーティング、パーサールーティングを名前で選びます。プレイグラウンドはその大半を計画とエンコードのオプションとしてリクエスト起点で実行します。関係と few-shot 検出にはストアとライブラリが必要です。

プレイグラウンド — 試す

空間とバージョンの選択 進行中

実行がどの空間の、どのバージョンで埋め込むか。空間は今 embedding_space_ids で、バージョンには空間が必要です。

機能一覧 — 4 つの埋め込み空間

サンプルかアップロード 進行中

コミット済みのサンプルか、ページに置いたファイルに対して実行します。

プレイグラウンド — サンプルとアップロード

根拠つきの結果 進行中

スコアつきのランク付きヒット、由来の領域つきの抽出フィールド、ページに描かれた検出ボックス、理由とフォールバックつきのルーティング判断。それぞれ使った空間と環境を名指しします。

プレイグラウンド — レビュータブ

承認済みの例として追加 計画中

検証した結果を 1 クリックで few-shot ライブラリの例として残します。

エンドポイントにする 計画中

実行(機能、空間、バージョン)を名前つきのエンドポイントとして公開します。

実行は上に、ページとその出力は横に並べる 計画中

プレイグラウンドは実行のフォームを一方の列に、結果をもう一方の列に置き、結果はページ、チャンクの一覧、ルートを縦に積みます。セールスページは、プレイグラウンドが持つべきレイアウトを示しています。入力は上に横一列、その下にページを左、構造化された出力を右に、同じ高さで並べます。フォームは 1 行(サンプルかアップロード、レーン、実行)になり、タブ(ページ、埋め込み、検索、エンティティ、タグ、図)は出力の列へ移ります。

プレイグラウンド — 試す

developer/decisions — ADR-0060、セールスページが描くもの

ページ上のブロックに合わせて、その出力を読む 計画中

プレイグラウンドはページにチャンクのボックスを描き、その下にチャンクを並べますが、両者は連動しません。セールスページでは、ブロックにカーソルを合わせると indx がそのブロックについて渡す JSON が表示され、出力側の項目に合わせるとそのボックスが光ります。プレイグラウンドは今日 encode が返すもの、つまり行チャンクとその来歴の上で同じことを行い、ブロックが増えるにつれて表のセル、図のノードや接続線といったより細かい対象を得ます。

プレイグラウンド — 試す

developer/decisions — ADR-0060、セールスページが描くもの

ページごとのルートの見出し 計画中

今日、ルートはページの下の 1 行です。セールスページはそれをページの上に、起きた順で述べます。ページが走査されたこと、何が見つかったか(テキスト層がある、テキスト層がない、工程系統図のシグネチャ)、読んだパーサー、保持したフォールバック。どのフィールドもすでにプランとページブロックの来歴にあります。

ルーティング — ラダーとその理由

developer/decisions — ADR-0060、セールスページが描くもの

Layer 02。昇格の前に評価される承認済みの例。OCR、分類、パーサーで共有されます。 青写真 — Few-shot library · ロードマップ

承認済みの例、評価セット、有効バージョン 計画中

3 つのタイル。承認済みの例の数、切り離した評価セットの大きさ、基準となるバージョン。評価セットはベンチマークのラベル付きケースとして、プロダクトではなくリポジトリに存在します。

ベンチマーク — ラベル付きセット

文書種類ごとの例のライブラリ 計画中

教えるケースごとにまとめた例。標準的な請求書、スキャン、手書き伝票、複数表のもの。それぞれ人がレビュー済み。認識される帳票ごとの例とスキーマであるレシピが、1 つのグループの正体です。

機能一覧 — レシピと類似帳票の再利用

例を追加する 計画中

文書を選び、期待する値か判断を登録します。

候補を評価する 計画中

現行バージョンと候補を評価セット上で並べて採点し、退行を数えます。

承認して昇格する 計画中

勝った候補を空間の基準バージョンにします。

Layer 02。基盤と各アプリケーションをそれぞれ独立した API として提供します。 青写真 — Endpoints · ロードマップ

埋め込みエンドポイント 進行中

空間バージョンに束ねた /v1/embed。エンドポイントは存在し、バージョンの固定には空間が必要です。

機能一覧 — 4 つの操作、3 つのインターフェース

検索エンドポイント 計画中

特徴フィルタつきでストアを検索します。プレイグラウンドは今、エンコードした 1 文書の中を検索します。ストア横断の検索にはストアが必要です。

抽出と検出のエンドポイント 進行中

情報抽出とレイアウト検出の名前つきエンドポイント。どちらも今は encode のオプションとして動きます。

機能一覧 — エンティティ抽出

機能一覧 — チャンクの位置

エンドポイントを作る 計画中

エンドポイントに名前をつけ、空間バージョン、機能、実行環境に束ねます。

SDK と HTTP のサンプル 進行中

エンドポイントごとのコードパネル。Python と HTTP、コピーつき。HTTP コントラクトと生成 TypeScript クライアントは存在し、Python クライアントは提案です。

インターフェース — 今の API

ワークスペース。同じ基盤を、マネージドクラウドでも、自社クラウドでも、自社のマシンでも。 青写真 — Deployment · ロードマップ

ターゲットを選ぶ 進行中

INDX Cloud、プライベートクラウド、セルフホスト。イメージとプライベートクラウドのターゲットは存在し、マネージドクラウドにはテナントが必要です。

デプロイ — イメージとターゲット

クラウドごとの汎用 Terraform テンプレート 計画中

infra/aws の隣に、同じ形の infra/azureinfra/gcp を。レジストリ、1 つのイメージ、1 つのポートの 1 つのコンテナ、境界、そして変数としてのモデルレーン。設計上汎用です。顧客のネットワーク、セキュリティ、AI サービスの設定は有償の実装作業であり、テンプレートには含めません。3 つを CI で検証し、10 月 1 日のチェックポイントに向けて 1 つを手でデプロイします。

デプロイ — AWS

機能一覧 — ターゲットごとのクラウドネイティブ AI サービス

スプリント — スプリント 1

実行ポリシー 進行中

ターゲットごとに、データ境界、既定は CPU、外部モデルは許可リストのみ、未承認のフォールバックは拒否して人手レビューへ。data_residencydevice は今もリクエストごとに守られ、ラダーは人で終わります。画面はそれを /v1/capabilities から読みます。

ルーティング — 制約とラダー

モデル許可リスト 進行中

埋め込み、OCR、LLM を別々に許可し、それぞれ状態つきで。今はどのエクストラが入っているかがインストールごとに決め、青写真は空間ごとに決めます。

機能一覧 — モデルベンダー

モデル切り替え前の再埋め込み 計画中

モデル更新は新しい空間バージョンを作り、再埋め込みして評価し、それから切り替えます。バージョンが必要です。

横断的な作業です。認知済みですが未スケジュールです。成果としての位置づけはゴールと非ゴールにあります。

運用面の成熟 計画中

認証、レート制限、リクエストごとのログ 1 行を超えるメトリクス、パッケージ配布(PyPI、コンテナイメージ、チャート)、そしてリモート API が負うべきクライアント側の作法、すなわちリトライと冪等キー。認知済みですが未スケジュールです。バックボーンのベースラインが計測されるまで、HTTP アダプターは薄いトランスポートのままです。

goals

目的別ベンチマークと比較表 計画中

技術的優位性を、1 つのスコアではなく 7 つの軸の数値で表します。解析精度、ルーティング品質、速度、リソース要件、コスト、few-shot の性能、複雑な文書と図です。ベンチマークはすでにラベル付きケースですべてのリーダーと抽出器を採点し、usage からレイテンシとコストを報告しています。比較表は各軸を今日動くものに対応づけ、数値化できる軸は最も近い代替と並べ、残りの軸に必要なラベル付きデータを名指しします。

ベンチマーク

スプリント — スプリント 1

チャットモデル呼び出しのディスクキャッシュ 計画中

チャットモデルと話すすべてのレーンは indx-llm を通して到達するので、そこに置いた 1 つのキャッシュが LLM 分類器、LLM 抽出器、LLM エンリッチャー、ビジョンリーダーに同時に効きます。開発のためのものです。同じモデルに同じプロンプトで尋ねた同じページは 2 度目にはディスクから答えるので、テストスイートやベンチマークの再実行はベンダーを待たず、2 度払いません。既定では無効です。キャッシュは本番のデプロイが最も気づくべきこと、つまり答えが変わったモデルを隠すからです。

SQLite ファイル 1 つ、標準ライブラリのみ。それが indx-llm を依存なしに保つものです(ADR-0038)。キーはモデル ID、メッセージ、呼び出しに届くすべての引数のコンテンツダイジェストなので、温度が違えば、画像が違えば、別の項目です。値は LiteLLM が返したままの返答です。最近最も使われていないものから追い出し、設定モデルの INDX_LLM_CACHE_MAX_ENTRIES(既定 10000)で上限を与え、ファイルは INDX_LLM_CACHE で名指しし、ファイルがなければキャッシュもありません。ヒットは元の呼び出しの費用を cached フラグと並べて報告するので、再実行の actual_cost_usd はベンダーが請求したはずの額を今も述べ、読む人は無料の答えと安い答えを区別できます。

developer/decisions — ADR-0038

packages/indx-llm/src/indx_llm/chat.py

後回しにしたアーキテクチャ 計画中

領域最適化、幅広いモダリティ対応、RAG エクスポート、永続化、ルーターとエグゼキューターの分離デプロイ。これらは最初のスライスの計測済みベースラインの後にのみ続きます。永続化の判断には明確なトリガーができました。最初に状態を必要とするのはレシピストアとエクスポート先です。フックフレームワークも後回しです。

developer/target-architecture