コンテンツにスキップ

分類器

リクエストが有効にしたときだけ尋ねられる、文書・ページ・チャンクが何であるかを述べる分類器を追加します。

分類器は、テキストが何であるか — その種別、作った部門、属する業種 — を、ランク付きラベルのファセットとして述べます。すべての読み取りがページを生成した後に呼ばれ、渡されるのはバイト列ではなくテキストです。単位ごとに 3 つあります。DocumentClassifier は文書全体を見て、PageClassifier はページごとに一度、ChunkClassifier はチャンクごとに一度尋ねられます。プロトコルは 3 つとも同一で、変わるのは単位とプロバイダーのフックだけなので、このページは文書のものについて書き、他との差分は末尾に置きます。言語検出器と違い、リクエストが名指ししたときだけ動きます。分類器は呼び出しに対価がかかり、箱の外に出ることもあるからです。indx-classifier-wordsindx-classifier-zeroshotindx-classifier-llm は、あなたの配布物とまったく同じエントリーポイントグループを通じて届きます。パッケージ化とインストールは概要の手順に従います。

from collections.abc import Mapping
from typing import Any
from indx_interfaces import (
CapabilityDescriptor,
CapabilityId,
ClassifierId,
Device,
DocumentClassifier,
LabelScore,
)
class PurchaseOrderClassifier:
# What a request names in `classification.document_ids`. IDs are one
# namespace across all five classifier and extractor ports, so no two may
# share this string.
id = ClassifierId("acme-document-type")
device = Device.CPU
def classify(self, text: str) -> Mapping[str, tuple[LabelScore, ...]]:
# `text` is the full text of your unit. Bound it yourself if you need it
# bounded. A facet left out is "no opinion": the next enabled classifier
# is asked for it.
if "purchase order" not in text.lower():
return {}
return {"document_type": (LabelScore(label="purchase_order", confidence=0.8),)}
class Provider:
def descriptors(self) -> tuple[CapabilityDescriptor, ...]:
# This distribution annotates what other capabilities read; it reads nothing.
return ()
def create(self, capability_id: CapabilityId) -> Any:
raise ValueError(f"this provider declares no capabilities, so not {capability_id}")
def document_classifiers(self) -> tuple[DocumentClassifier, ...]:
return (PurchaseOrderClassifier(),)

DocumentClassifierProviderLanguageDetectorProvider と同じく任意です。分類器はリクエストが名前で有効にするため ID を持ち、インストール済みの ID は機能スナップショットに公表されます — コンテンツハッシュの外なので、あなたのものをインストールしても計画は動きません。呼び出し側は encode{"classification": {"document_ids": ["acme-document-type"]}}、または CLI の --classifier acme-document-type で有効にします。名指しされた分類器はその順に動き、あるファセットについて意見を持つ最初のものがそれを取ります。

あなたが受け取るのは自分の単位の全文であり、それを縛るのはあなたの仕事です。 これを切り詰めるリクエストフィールドもデプロイの既定値も、もうありません。サンプリングは契約を離れ、縛りを必要とする実装 — トークン窓、コストの上限 — は自分の設定モデルから自前のものを、indx-interfaces の依存のないヘルパーを使って適用します。渡されたものをすべて読む分類器は、300 ページの報告書に対して 3 ページのものより多く払っており、それは今やあなたが下す決定であって、代わりに下された決定ではありません。

変えるのは 2 つです。同じ classify(text) から同じ Mapping[str, tuple[LabelScore, ...]] を返し、document_classifiers() の代わりに page_classifiers()chunk_classifiers() から宣言します。するとリクエストはあなたの ID を classification.page_idsclassification.chunk_ids で名指しします。あなたの回答は各ページブロックの metadata に、チャンク分類器の場合は文書ブロックの chunk_classification にチャンクブロック ID で引ける形で書かれます。チャンクブロックは自前のメタデータを持たないからです。

想定しておくべきことが 2 つ。チャンク分類器はチャンク粒度を要します。 CHUNK を求めていないリクエストでこれを名指しすることは、ソース取得前の 422 です。分類すべきものが存在しないからです。そして単位ごとの分類器は文書ごとに一度ではなくページごと・チャンクごとに一度呼ばれるので、1 コール要するモデルは数百コール要することになります。呼び出しごとの準備は、呼び出しの中ではなく最初の呼び出しの背後に置いてください。

raise は「自分のもので、壊れている」を意味し — ソースではなく分類器についての判定なので — エグゼキューターはログに残して次を尋ね、実行がすでに対価を払ったエンコードを失敗させません。三つの属性が既定値付きでクラスから読まれます。device(不在なら Device.CPU。ホスト型モデルには Device.EXTERNAL を宣言してください。data_residency を運ぶリクエストは、黙ってよそで分類されるのではなく、ソースの取得前に拒否されます)、classify() の後に設定され実行の使用量に加算される cost_usd、そして答えられるファセット名の tuple[str, ...] である facets です。これを宣言すれば、先の分類器がそのすべてをすでに取っているときには呼ばれもしません。尋ねられることと課金されることの差です。宣言しなければ常に尋ねられます。

エグゼキューターは統合された回答を文書ブロックの metadataclassification として書き込みます。このキーは予約されています。EncodeRequest に渡した呼び出し側は、上書きされるのではなく拒否されます。

  • 推測するくらいならファセットを省く。ラベルは高い順に並べる。
  • 動けないとき — エクストラなし、モデル未設定 — は raise する分類器ではなく、何も公表しない。
  • モジュールスコープを安価に保つ。エンジンのインポートは classify() の内側に、モデルの構築は最初の呼び出しの背後に。