コンテンツにスキップ

CapabilityProvider

すべての拡張ディストリビューションが実装するプラグイン契約。

英語版が原文です。

プロトコル:

  • CapabilityProvider.descriptors() -> tuple[CapabilityDescriptor, ...]
  • CapabilityProvider.create(capability_id: str) -> Any

CapabilityProvider は、処理機能を公開宣言し、そのランタイム実装を生成するための、プラグイン側に向いた境界です。インストール済みディストリビューションは indx.capabilities という Python エントリーポイントグループを通じてプロバイダーを公開します。これにより indx は、直接インポートすることなく拡張を検出できます。

descriptors() は、プロバイダーの機能に関する軽量な公開メタデータを返します。create() は、その機能 ID の一つを、実際の処理を行うオブジェクトへ解決します。

検出と計画は、どんな処理が利用可能かを知る必要があります。しかし、OCR エンジン、モデルの重み、GPU ランタイム、リモートクライアントといった高コストな依存を読み込むべきではありません。記述と生成を分離することで、レジストリは完全なインベントリを安価に構築でき、重い初期化は実行が機能を選択するまで先送りできます。

安定した機能 ID は、スナップショットと計画にプロバイダー中立なキーも与えます。ルーティングは、パッケージや具体的な実装クラスに依存せずに機能を参照できます。

descriptors()CapabilityDescriptor 値のイミュータブルな列を返します。各記述子は次を宣言します。

  • 安定した ID とバージョン。
  • 機能の kind。
  • 対応デバイスとメディアタイプ。
  • ランタイム要件。
  • 可用性。利用不能な場合は明示的な理由。

create(capability_id) は公開宣言済みの機能 ID を一つ受け取り、そのランタイム実装を返します。実装は PageReader であり、一つのロード済みソースから指名されたページを読み取り、各ページの PageOutput を返します。

特化できる機能は SignatureDetector も実装します。その detect() は、手にしている文書がどんな種類かを認識します。これは計画時に走るため、事前観察(preflight)の予算を継承します。安価、ローカル、決定的で、OCR、モデル、レンダリング、ネットワークを使わないこと。その予算内で答えられない検出器は、何も返さないという答えを返します。抽出済みテキストではなくバイト列を受け取るため、プランナーはテキスト抽出器を抱え込む必要がなく、検出器は文書のどこまでを見るかを自由に決められます。

シグネチャは SignatureMatch として計画上で呼び出し側に届きます。シグネチャが指名した機能は、汎用ルーティングラダーの前に置かれるのであって、ラダーの代わりに置かれるのではありません。シグネチャは推測です。専門家の後ろにラダーを残しておくことが、推測の外れを文書全体ではなく一回の安価な試行で済ませる仕組みです。

signal は文書タイプの性質であって、一つの文書の性質ではありません(ADR-0045)。形式が述べる構造か、タイプが定義上持つ語彙(インボイスは “invoice” と書きます)であり、出荷前に独立した 2 つの文書で発火することを示します。ある著者がある文書に置いた語は signal ではなく、デプロイごとに変えたくなる名前は出力上のデータであって証拠ではありません。

kind は処理を記述するものであり、入口を記述するものではありません。そのメディアタイプを汎用的に読み取る機能は、背後の機構がどれほど特化していても native_extraction を宣言します。parser は、何かが先に認識した文書についてだけ正しい機能であることを意味し、指名を通じてのみルートに到達します。実装例は意図的にこの二つを対にしています。acme-plaintext はフォーマットを読み取り、acme-purchase-order は自身のシグネチャが一致したときに特化します。パーサーだけが宣言するメディアタイプを、シグネチャ検出なしで計画すると、そのパーサーとフラグを名指しした未充足の計画が返ります。

一つのプロトコルがすべての読み取り kind をカバーします。ネイティブ抽出、OCR、ビジョンモデル、人間のキューは、コストと失敗の仕方では大きく異なりますが、何を求められるかではまったく違いがありません。kind ごとのプロトコルは、ルーティングラダーのラングを型システムに埋め込むことになります。ポリシーが kind の上でルーティングするのは、まさにサードパーティの OCR ディストリビューションがファーストパーティのものとまったく同じに振る舞うためです。

kind の集合は閉じており、その上のラダーも閉じています。CapabilityKind のメンバーは 6 つで、インストールが 7 つ目を追加することはできません。kind とはポリシー上の位置 — 価格、品質、フォールバック順の中の場所 — であり、kind を宣言できるディストリビューションは、ルーティング先を供給するのではなく、indx が何にルーティングするかを決めることになるからです。分類、マスキング、翻訳、音声、表構造など、6 つのどれでもない処理を行うディストリビューションは、最も近い kind を宣言し、そのラダー位置を継承し、INDX_ROUTING_ECONOMICS を通じて数値を補正するようデプロイメントに求めます。kind を追加すること、あるいはオブザーバーがすでに発している事前観察シグナルに意味を持たせることは、POLICY_VERSION の更新を伴うファーストパーティのポリシー変更です。

数値がコア所有であるのも同じ理由からで、そのコストは発見に委ねるのではなく明記しておく価値があります。記述子はコスト、品質、レイテンシを持たず、唯一のオーバーライドはオペレーターが設定する環境変数です。したがって、1 ページ $0.05 のホスト型 OCR は kind ごとのデフォルト $0.0005 を継承し、100 倍低い数値で呼び出し側の maximum_cost_usd に対して受理され、デプロイメントが実際の値を書き込むまで、計画の estimates.cost_usd はその数値を価格として公表します。自己申告の価格は、機能がルートに買い込むための手段になってしまいます。請求を払う当事者こそが、それについて正直である理由を持つのです。

  • 記述子の検出を軽量に保ち、モデルの初期化を含めない。
  • 安定した空でない ID とバージョンを公開宣言する。
  • 可用性を一貫して報告する。利用可能な機能は利用不能理由を持たず、利用不能な機能は理由を提供する。
  • 要求された公開宣言済み ID に対応する実装を生成する。
  • 認証情報、非公開エンドポイント、実装専用の設定を記述子に含めない。
  • エンジンが位置を知っているなら、見つけた各行を PageOutput.lines に述べる。generic-ocr が認識器の返す枠からそうしているように、TextLine(text, bbox) で、枠は Block.bbox と同じ正規化です。行はワイヤに届きません。indx-chunker-lines がそれを矩形付きのチャンクに切り、何も述べないリーダーのページはチャンカーの床に残ります(ADR-0047)。
  • read() 呼び出しに渡されたすべてのページについて PageOutput を一つ返す。ページを読み取れなかったと伝える方法は二つあり、エグゼキューターはどちらにも計画の次の機能へ降りることで応じます。そのページについて何も生成しないか、status failedreason を載せた出力を一つ生成するかです。後者を推奨します — 理由がトレースに届き、呼び出し側から見えるからです。空の text はその両方の反対を意味します。ページは読み取られ、何も載っていなかった、と。
  • status unreadable は読み取りの失敗ではなく内容についての判定であり、フェイルオーバーされずに保持されます。それが終端の手動レビューラングを終端たらしめるものです。「他を試してほしい」を意味する機能は failed と言います。
  • エンジンがページごとの信頼度を生成する場合は、課金される機能が COST_METADATA_KEY を報告するのと同じように、CONFIDENCE_METADATA_KEY で報告する。それは自己申告として ExecutionActuals.quality に届き、INDX_VALIDATION_MIN_CONFIDENCE を設定したデプロイメントは、それを下回る出力を拒否します。較正された数値を持たない機能は何も報告しません。欠落は意見なしであり、決してゼロではありません。
  • プロバイダーパッケージを、ルーター、エグゼキューター、アプリケーションの実装に結合させない。

埋め込みを行うプロバイダーは EmbeddingSpaceProvider も満たします。その embedding_spaces() は空間全体を宣言します。次元、距離、正規化、そしてその空間に属するすべての埋め込み器です。各埋め込み器は同時に、そのプロバイダーの EMBEDDER kind の記述子の一つでもあり、可用性と create() はそこを通じて埋め込み器に到達します。それらの ID に対して create()VectorEncoder を返します。テキストにも画像にも一つの encode() で、埋め込み器が宣言したモダリティが渡されます。空間は、その埋め込み器が今日実行できるかどうかにかかわらず宣言されます。そして一つのプロバイダーが一つの空間全体を宣言します。二つの実装が空間 ID を共有できるのは一つのディストリビューションの内側だけで、そこではベクトルが比較可能であることをテストで実証できます。

新しいメディアタイプを計画可能にするプロバイダーは SourceObserverProvider も満たします。その source_observers() は同梱するオブザーバーを宣言します。これも同じように任意です。indx がすでに観察するフォーマットを読み取るプロバイダーは、決してこれを実装しません。空間と違い、オブザーバーは ID を名乗らず記述子にも加わらないため、スナップショットを動かしません — descriptors() が空を返すのは、観察はするが何も読み取らないディストリビューションにとって正直な宣言であり、indx-observer-pdfindx-observer-imageindx-observer-officeindx-observer-textindx-observer-email はまさにそれです。リーダーは別ディストリビューションとして出荷されます(indx-capability-native-extractionindx-capability-office-extractionindx-capability-text-extractionindx-capability-email-extraction)。フォーマットを計画可能にするものと、ルーティング可能にするものは二つのインストールであり、運用者は片方だけを持つことができます。

機能レジストリはプロバイダーを検出し、その記述子を決定的なスナップショットへ結合します。プランナーはそのスナップショットから記述子 ID を選択します。実行時には、選択されたプロバイダーが対応するランタイム実装を遅延生成します。