mcpField
C
classifier.dev docs

classifier.dev docs

リモート
classifier.dev開発ツール4341 週間前に更新
リポジトリ公式サイト

classifier.dev docs について

classifier.dev は、プレーンテキストからラベルと校正済みの信頼度を返すゼロショットのテキスト分類 API です。キーや登録は不要で、1 リクエストあたり最大 1,000 件のテキストを処理できます。Cloudflare Worker として動作し、classify コマンドラインツールとドキュメントも提供します。

主な機能

  1. 01APIキー不要のゼロショットテキスト分類
  2. 021リクエストあたり最大1,000件のテキスト
  3. 03可能性順に並んだラベルを返すマルチラベル分類
  4. 04校正済みの信頼度と、より強力なモデルによる再判定オプション
  5. 05classifier-dev として公開された classify コマンド
  6. 06feedback.now プロトコルに従うエージェントフィードバックのエンドポイント

活用シーン

  1. 01ユーザーフィードバックやニュース見出しの一括分類
  2. 02シェルスクリプトで classify にテキストをパイプで渡す処理
  3. 03問題発生時にエージェントが構造化されたレポートを送信する仕組み

MCP 対応

MCP ツールとしてプロジェクトのドキュメントを公開し、クライアントがリファレンス、ベンチマーク、ガイドの一覧取得、読み取り、検索を行えるようにします。

設定

リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。

エンドポイントhttps://classifier.dev/mcp/docs
インストールコマンドをコピー
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

ツール

4
パラメーター型必須説明
filterstringいいえ任意:ID、タイトル、またはセクション見出しにこのテキストを含むドキュメントのみを返します(大文字と小文字は区別されません)。

詳細

classifier.dev

ゼロショットテキスト分類です。プレーンテキストを入力すると、ラベルと調整済みの信頼度を返します。API キーも登録も不要です。1 回のリクエストで最大 1,000 件のテキストを処理できます。

curl https://classifier.dev/spam,not+spam/Win+a+free+iPhone
spam

curl "https://classifier.dev/?labels=spam,not+spam&text=Win+a+free+iPhone"   # 同じ呼び出しのクエリ形式
spam

Cloudflare Worker は 1 つだけです。データベースもフレームワークもなく、esbuild 以外のビルド手順もありません。

CLI

npm i -g classifier-dev
classify bug,feature,praise < feedback.txt

cli/ は独立した npm パッケージ(classifier-dev、bin は classify)です。依存関係のない Node ファイル 1 つで、モック API に対するテスト(npm test)、独自の CHANGELOG を持つ semver を使用しています。npm run release patch|minor|major でリリースすると cli-v<version> タグが付けられ、.github/workflows/publish-cli.yml によって公開されます(リポジトリのシークレット NPM_TOKEN が必要です)。API との通信方法は curl とまったく同じです。

構成

src/index.ts    ルーティング、バリデーション、ティア、LLM フォールバックチェーン、分析
src/query.ts    GET クエリ形式。nuqs で読み書きし、エラー時に提示する URL を生成
src/jev.ts      TypeSafe の Jev:入力をリクエストにまとめ、確率を読み取る
src/limiter.ts  Durable Object:IP ごとのレート制限
src/report.ts   ダイジェスト — Analytics Engine SQL から Resend へ送信し、モデルのフォールバックを検出
src/alerts.ts   15 分ごとに実行し、問題がある場合にのみメールを送信
src/feedback.ts エージェントからのフィードバック。feedback.now プロトコルを使ってメールで通知
src/privacy.ts  キー付き仮名:呼び出し元を特定できる情報は一切保持しない
src/cost.ts     プロバイダー独自の会計情報に基づく、リクエストごとの上流コスト
src/docs.ts     サイト(GET / および GET /benchmark)、プレーンテキスト
src/home.ts     同じ 2 つのドキュメントをブラウザー向けにレンダリング
src/ui.ts       共通の外観:ターミナル内の Markdown
cli/            npm で classifier-dev として公開される `classify` コマンド
eval/           ベンチマーク。数値を引用する前に eval/README.md をお読みください
finish-dns.sh   一度だけ実行する DNS 設定。詳細は以下を参照
wrangler.example.toml  Worker の設定。アカウント固有の ID は除外

サイト

curl classifier.dev は、これまでどおりプレーンテキストを出力します。ブラウザーからは Accept: text/html が送信され、src/home.ts によって同じドキュメントがレンダリングされます。見出し、角括弧付きリンク、コピーボタンなどが含まれます。重複はありません。ページはリクエスト時に DOCS と BENCHMARK から生成されるため、テキストは常に正規の内容であり、両者にずれが生じることもありません。?format=text を指定すると手動で HTML 表示を無効にできます。また、どちらのレスポンスにも Vary: accept が付与されます。

エージェントからのフィードバック

feedback.now プロトコル(スキーマ 1.1)を実装しているため、これに対応するエージェントであれば、方法を教えられなくても問題を報告できます。

GET  /.well-known/agent-feedback.json   このホストが受け付ける内容
GET  /api/v1/policy                     カテゴリー、重大度、制限
POST /api/v1/feedback                   構造化された詳細なレポート
POST /api/v1/observations                より簡易なシグナル
POST /api/v1/feedback/{id}/attachments  後から追加する証拠
GET  /api/v1/receipts/{id}              受信されたか、また内容は有用だったか

受け付けた送信内容は REPORT_TO にメールで送られます。レポートは KV に 90 日間保存されます。同じドメイン、サーフェス、カテゴリー、タイトルのレポートが再送された場合は保存され、重複として受領確認されますが、再度メールでは送信されません。これにより、ループするエージェントが受信トレイに大量のレポートを送り続けることを防ぎます。IP ごとの 1 時間あたりの上限は 100 件で、残りの枠は各受領確認に記載されます。

お客様の声には signal.category: "testimonial" を使用します。reporter.agent_type と reporter.agent_description が必須のため、どのようなエージェントがどのように役立ったのかを理解するのに十分な情報とともに、称賛が届きます。

quality_score はレポートの完全性に基づく決定論的な関数です。エージェントはそのルールを読み、次回はよりよいレポートを作成できます。ここでは分類器も Analytics Engine も呼び出しません。これらが壊れているという報告が届く場所なので、実際に壊れているときでも動作しなければなりません。

デプロイ

main にマージするとデプロイされます。.github/workflows/deploy.yml は型チェックを行い、Worker と CLI のテストを実行し、wrangler deploy を実行してから、稼働中のサービスに /v1/health と分類を1件リクエストします。そのため、壊れた Worker をアップロードするデプロイは、誰かのターミナルで発覚するのではなく、CI で失敗します。

マージ前に手動で試すには、次のようにします。

cp wrangler.example.toml wrangler.toml     # 初回のみ。その後、自分の ID を入力します
npx wrangler deploy

wrangler.toml は gitignore 対象で、特定の Cloudflare アカウントに固有の2つの値、account_id と、npx wrangler kv namespace create STATS が返す STATS KV 名前空間 ID を保持します。追跡対象の wrangler.example.toml には、それ以外のすべて(cron、バインディング、マイグレーション)が含まれているため、デプロイの構成はリポジトリにあり、識別子だけが含まれていません。

CI には wrangler.toml がないため、.github/render-wrangler.mjs がサンプルと3つのリポジトリシークレットからファイルを生成します。これにより、デプロイ時の構成そのものがサンプルになります。wrangler.toml だけでバインディングを変更すると、CI は古い構成をデプロイし続けます。

デプロイに必要なリポジトリシークレット:

CLOUDFLARE_API_TOKEN    dash.cloudflare.com > My Profile > API Tokens >
                        Create Token > "Edit Cloudflare Workers"
CLOUDFLARE_ACCOUNT_ID   wrangler.toml の account_id
STATS_KV_ID             wrangler.toml の STATS 名前空間 ID
REPORT_TO               日次ダイジェストの送信先

wrangler secret put で設定したシークレットは、スクリプトバンドルではなく Worker 上に保存されるため、デプロイしても変更されず、CI がシークレットを把握する必要もありません。

Worker が読み取るシークレット:TYPESAFE_API_KEY、AI_GATEWAY_API_KEY(Vercel の AI Gateway 用。Jev に無料の月間クレジットを提供します。設定されている場合は最初に問い合わせ、拒否された内容は TypeSafe が処理します)、OPENROUTER_API_KEY、CONTEXT_API_KEY(context.dev 用。チャットのウェブ検索とページ読み取りに使います)、RESEND_API_KEY、CF_ANALYTICS_TOKEN、REPORT_KEY、PRIVACY_SALT。npx wrangler secret put NAME で追加します。これらがリポジトリから読み取られることはありません。残りのシークレットは src/index.ts の Env インターフェースに記載されています。

シークレットの比較には === ではなく secretEquals(src/secrets.ts)を使います。通常の比較は最初に異なるバイトで処理を終了するため、推測がどこまで当たっていたかを呼び出し元に漏らしてしまいます。

モデル

どちらのティアも、言語モデルではなく意思決定モデルであるTypeSafe's Jevを使って回答します。Jevは状態と型付きの質問を受け取り、選択肢ごとに較正済みの確率を約150msで返します。この仕組みにより、このAPIではLLM版ではできなかった次の3つのことが可能です。

1リクエストあたり1,000件の入力。 状態は{id, text}の配列で、各入力にそれぞれ質問を割り当てるため、バッチ全体を1回の上流呼び出しで処理できます。ドキュメントに記載されている上限は1リクエストあたり64kトークンです。jev.tsは保守的な予算内に収まるようにパッキングし、生成されたリクエストを8件ずつ並行して実行します。実測では、ニュース見出し400件の分類がエンドツーエンドで650ms、100件のパッキングによるスコアは、1件ずつ送信した場合と同じでした。

意味のある信頼度。 6つの感情分類を行う400件の項目では、信頼度が0.9以上の回答は82%の確率で正解し、0.5未満の回答は29%の確率で正解しました。以前のモデルでは、logprobによる「信頼度」がニュース項目の87%を0.9以上と判定していましたが、そのうち正解だったのは68%でした。そのため、現在のtier: "smart"では、高速推論モデルで信頼度が0.7未満の単一ラベル回答だけを再質問して置き換え、escalated: trueを付けます。それ以外は変わりません。どのモデルを使うかが重要です。Jevが確信を持てなかった項目に限ると、deepseek-v4-flash、qwen3.7-flash、mercury-2.5はいずれもJevより良い結果を出せませんでした。gemini-3.8-flashはニュースのトピック分類を87.5%から90.0%に、感情分類を61.8%から63.7%に引き上げたため、このモデルをつなぐことにしました。最先端モデル(claude-fable-5.1)では72.3%/90.7%の精度が得られますが、料金は約3倍です。このトレードオフに価値があると思えた場合は、/benchmarkに数値を掲載しています。

マルチラベル分類を1回で実行。 ラベルごとに1つのはい/いいえの質問を行い、スコアマップ全体とともに、0.7以上のラベルを可能性の高い順に返します。7ケースのセットでのF1は0.887でした。置き換え前の、LLMを使ったスイープと検証のカスケードの0.799を上回り、処理時間も1.5秒から230msに短縮されました。推論モデルで候補を再判定すると結果は悪化し(処理時間も23秒かかるため)、マルチラベル分類ではティアを無視します。

index.tsのLLMチェーンは、TypeSafeを利用できない場合のフォールバックとして残しています。入力ごとに1回の呼び出しが必要なため、入力は20件に制限されます。ダイジェストには、実際に回答したモデルがFALLBACKマーカー付きで表示されます。以前のプライマリモデルは上流で提供終了になった後、バックアップが数週間にわたってF1 0.546で応答していましたが、そのことを示す表示がなかったためです。

更新リスト

フォームと POST /subscribe は、Resend 経由で確認メールを送信します。 API は 202 {"ok":true,"status":"pending_confirmation"} を返します。メールで送られたトークンを {"token":"..."} として POST /subscribe/confirm に送信するか、確認フォームを使用するまで、ニュースレターデータベースには何も書き込まれません。 GET はフォームを表示するだけなので、メールスキャナーが購読を確定することはありません。

トークンは NEWSLETTER_CONFIRMATION_SECRET で署名され、有効期限は24時間以内です。 トークンが登録時に返されることはありません。Resend の冪等性キーにより、同じ受信箱への重複リクエストは各時間内で重複排除されます。既存の IP ごとの制限も適用されます。 署名キーをローテーションすると、未使用のリンクは無効になります。

デプロイ前に npm run db:migrate を実行して migrations/postgres/ を適用してください。購読者テーブルはアプリケーションのデータベースを共有します。既存の未確認購読者は未確認のままにしてください。confirmed_at を遡って入力したり、確認が完了するまで更新情報を送信したりしないでください。確認処理や古いトークンの再利用によって、既存の配信停止が解除されることはありません。

必須の Worker シークレットは、DATABASE_URL、NEWSLETTER_RESEND_API_KEY、および32バイト以上のランダムな NEWSLETTER_CONFIRMATION_SECRET です。wrangler.example.toml の NEWSLETTER_FROM には、Resend で検証済みの送信ドメインを設定する必要があります。REPORT_TO は返信先アドレスで、新たに確認された行についてのみ通知を受け取ります。

購読者テーブルには、メールアドレス、ソース、登録・確認・配信停止の日付、チェックされたロードマップ項目(ROADMAP キーを保持する wants text[]。チェック項目は確認トークンに含まれ、確認時に書き込まれます)、高速な推論を選択した場合に要求されたレイテンシー、内部 ID が保存されます。IP、リクエスト ID、分類トラフィックは保存されません。保留中の登録は保存されません。トークンやメールプロバイダーのエラー本文をログに記録してはいけません。 migrations/postgres/0007_newsletter.sql でテーブルを定義します。再確認時にチェック項目があればその項目を置き換えますが、配信停止を解除したり、初回確認日を変更したりすることはありません。

更新情報の送信時は、確認済みで配信停止していない受信者のみを読み取ってください。

SELECT email, wants, desired_latency_ms FROM subscriber
WHERE confirmed_at IS NOT NULL AND unsubscribed_at IS NULL;

最初に要望された項目を調べ、作業の優先順位を決めます。

SELECT unnest(wants) AS item, count(*) FROM subscriber
WHERE unsubscribed_at IS NULL GROUP BY 1 ORDER BY 2 DESC;

Worker はニュースレターとアカウントデータにアプリケーションの DATABASE_URL を使用します。購読者の行にはアカウント外部キーも API トラフィック識別子もありません。ストレージを共有しても、アカウント保有者が自動的に購読されることも、既存の同意が変更されることもありません。旧ニュースレタープロジェクトは、読み取り専用の移行アーカイブとして保持されています。検証済みの切り替え手順と復旧手順については、docs/postgres-setup.md を参照してください。

表示文は単一の定数である src/newsletter.ts の ROADMAP にあります。curl classifier.dev のプレーンテキスト、レンダリングされたページのフォーム、index.md はすべてこれを参照するため、ロードマップを変更すると、3つすべてに反映されるか、どれにも反映されないかのいずれかになります。

Jev の長いコンテキスト

デフォルトの Jev と、明示的に model: "jev" を指定した入力で 32,000 文字を超えるものには、Chonkie RecursiveChunker を使用して 600 トークンの cl100k_base チャンクに分割します。関連性がある、または不確かな証拠(反対の証拠や例外を含む)を並列で Jev にスクリーニングさせた後、対象となるチャンク全体をソースの順序どおりに最終的な Jev で処理します。 最終的な証拠の予算は 20,000 cl100k_base トークンで、これに保守的なプロバイダーコンテキストチェックが加わります。予算に達すると、対象チャンクが省略されることがあります。usage.long_context には、選択件数、トークン数、呼び出し回数、所要時間が報告されます。 この仕組みは、最終処理で文書全体が読まれることや、あらゆるケースで正確な結果が得られることを保証するものではありません。

有料残高があるか、有効な有料サブスクリプションがあるワークスペースキーで Fast を使用してください。匿名アクセスとサインアップクレジットは対象外です。同期処理の上限は、入力全体で合計 250,000 個の元のコンテキストトークン、20 文書、32 件の判定、リクエスト本文 1 MB です。料金は、元の cl100k_base コンテキストトークン 100 万個あたり $0.084 です。次元や実際の推論での使用量にかかわらず、入力全体で一度だけ計上されます。 連続する空白文字または非空白文字の各並びは、UTF-16 コード単位で 8,192 個までです。これを超える並びは、文書のトークン化やチャンク分割を行う前に 400 long_context_input を返します。これにより、異常な入力によるトークナイザーの処理負荷を抑えます。 対象となる証拠がない場合は、課金せずに 422 long_context_no_evidence を返します。明示的に model: "chunklaya" を指定した場合は、従来のオプトイン動作が引き続き適用されます。

有料のワークスペースキーを使い、最大 1,000 万個の入力トークン(100 MB)の文書を丸ごと POST /v1/classify に送信します。通常の JSON 入力とラベルを使用するか、labels クエリパラメーターを指定して text/plain を送信してください。大きなアップロードには 202 と status_url が返されます。小さな文書でも Prefer: respond-async が使用できます。サーバーが分割、スクリーニング、再試行を行い、自動的に処理を完了します。 リポジトリの CLI では、次のようにアップロードとポーリングを行えます。 node cli/classify.js a,b --document document.txt --json

文書全体の正確なトークン数に基づいて、予約額と $0.084/M の料金が決まります。キューにある間、ソーステキストは非公開で保存され、スクリーニングにかけられたものから削除されます。 最終的な Jev が読む証拠は、選択されたトークンのうち最大 20,000 個です。処理の完了、失敗、キャンセル、または 24 時間の期限切れ時に、残りのソースと証拠は削除されます。失敗またはキャンセルされた処理は返金されます。結果は期限切れまで利用できます。

分析

各リクエストについて、Analytics Engine に 1 件のデータポイント(ティア、ラベルセットのフィンガープリント、国、ステータス、件数、レイテンシー)を書き込みます。リクエストのテキストが分析データに書き込まれることはありません。

長いコンテキストの集計データは、コンテキストと文書の合計、チャンクのスクリーニング/選択/省略件数、フェーズごとの入力トークン数(不明な件数を示すフラグを含む)、回答済みの呼び出し件数、フェーズごとの所要時間とともに、classifier_long_context_events にも書き込まれます。 アカウントイベントには対応するフィールドが追加されます。列の順序に関する仕様は src/server/analytics/schema.ts にあります。 文書数と元のコンテキストトークン数はそれぞれ一度だけ数えられ、チャンクとフェーズのカウンターは次元ごとの処理をまたいで累積されます。専用のデータセットに文書テキストや呼び出し元の識別情報が書き込まれることはありません。

また、各リクエストについて、こちら側のコストも記録します。OpenRouter は要求に応じて呼び出しの料金を返し、Jev は報告する入力トークン数に基づき、eval/bench.py がベンチマークの料金計算に使うレートで課金されます。支出はリクエストごとのメーター(src/cost.ts)に累積され、double3 に記録されます。この列はリリース後に追加されたため、それ以前のデプロイ分は 0 と表示されます。

15:00 UTC に cron がこのデータを照会し、Resend を通じてダイジェストをメールで送信します。

アラート

15 分ごとに別の cron が実行され、何かが発生しない限り通知しません。アクションが設定された条件のみを監視します。Jev のキーが拒否される、Jev が応答しない(フォールバックチェーンがひそかに処理を続けます。実際に発生したことがあります)、5xx の発生率、スマートティアへのエスカレーションの失敗(OPENROUTER_API_KEY の残高が尽きたときの状態です)、平均レイテンシ、直近 1 日の支出急増、そしてトラフィックの完全な停止です。4xx は無視します。/wp-admin を探るスキャナーによるものであり、障害ではないためです。

Jev のクレジット。 TypeSafe は残高エンドポイントを公開していません。API は /v1/systemone と /v1/models のみで、それ以外はありません。そのため、監視する残高の数値はありません。代わりに、このチェックは 15 分ごとにキーを使って /v1/models を呼び出し、TypeSafe の応答をそのまま報告します。ここで 401、402、403 が返された場合は、クレジット切れ、キーの失効、またはキーの誤りを意味します。どのステータスが何を意味するか推測するのではなく、TypeSafe 自身のメッセージを引用した重大アラートを発報します。トラフィックを待たずにプローブするため、ホストがアイドル状態でもフォールバックチェーンに到達する呼び出し元が現れる前に発報し、Analytics Engine がダウンしている場合も実行されます。

各条件について、発生時に 1 回、継続中は 6 時間ごとに、解消時に 1 回メールを送信し、状態は alert: 配下の KV に保存します。しきい値は src/alerts.ts の先頭にある T オブジェクトで設定されています。

curl -H "authorization: Bearer $REPORT_KEY" https://classifier.dev/alerts
curl -H "authorization: Bearer $REPORT_KEY" "https://classifier.dev/alerts?demo=1&send=1"

最初のコマンドは送信も状態の変更もせずにプレビューします。2 つ目は実際の経路でサンプルメールを送信し、配信が機能することを確認します。

送信せずに、いつでもレポートのプレビューを確認できます。

curl -H "authorization: Bearer $REPORT_KEY" https://classifier.dev/report

Jev プロバイダーへの試行は、復旧した失敗、再試行、ゲートウェイのクールダウンによるスキップも含め、JEV_AE 経由で classifier_jev_attempts に個別に保存されます。レポートには各プロバイダーのステータス、理由、回数、平均レイテンシが含まれます。/alerts には、通知が抑制されている場合も現在のインシデントが表示されます。既存の 15 分ごとのアラートチェックでは、いずれかのプロバイダーで少なくとも 3 回失敗し、失敗率が 5% を超えた場合に警告します。回数には Analytics Engine のサンプリングが考慮されています。入力テキスト、呼び出し元の識別情報、上流からのメッセージは保存されません。成功したシンプル分類器名と複数ラベル分類器名は、呼び出し元やソーステキストのフィールドを含まない別の集計 KV レコードに 90 日間保持されます。キー付きフィンガープリントはリクエストごとのアナリティクスにも表示されるため、オペレーターはこれらの仮名化されたレコード内のラベル名を特定できます。

wrangler.example.toml の AI_GATEWAY_DISABLED = "true" は、9 月 19 日にゲートウェイが 429 を繰り返し返したため、本番環境で TypeSafe を直接使用する設定です。ゲートウェイキーは保持されています。ゲートウェイ優先のルーティングに戻すには、ゲートウェイのキャパシティを確認し、サンプル設定ファイルとローカル設定ファイルでこの変数を "false" に変更してデプロイしてください。再有効化した後、プロバイダーへの試行とライブ API テストを確認してください。

アクセス方法はヘッダーのみです。クエリ文字列はアクセスログ、ブラウザー履歴、次にクリックしたリンクの Referer ヘッダーに残るため、?key= は廃止されています。実際にメールを送信するには ?send=1 を追加してください。

Cloudflare の Analytics Engine SQL は ClickHouse の機能を限定的に実装したもので、uniq() と SELECT DISTINCT は使えません。また、SELECT col ... GROUP BY col のみのクエリは拒否されます。そのため、個別件数は SELECT col, count() ... GROUP BY col を実行し、返された行数を数えて求めます。各クエリは個別に処理されるため、1 つのクエリが失敗してもレポート全体が空になることはありません。

プライバシー

このデータセットの2つの列には、以前、呼び出し元の情報としてIPアドレスと、結合して小文字に変換したラベルセットが含まれていました。現在はどちらもキー付きハッシュ(src/privacy.ts)になっているため、呼び出し元と分類器の個数は引き続き集計できますが、アドレスや誰かの表現を復元することはできません。呼び出し元のハッシュにはUTCの日付も含まれるため、明日には同じ値ではなくなります。そのため、7日または30日間のユニークな呼び出し元数は、実際には呼び出し元の日数です。

キーは一度設定し、ほかの秘密情報と同様に扱ってください。

npx wrangler secret put PRIVACY_SALT     # 32 random bytes

キーをローテーションするとすべてのフィンガープリントが振り直されるため、ローテーションをまたぐと個別の件数が二重に数えられます。キーがない場合は、ADMIN_SIGNING_KEY、次にREPORT_KEY、それらもない場合は分離環境ごとのランダム値にフォールバックします。IPv4アドレスや一般的なラベルセットをキーなしでハッシュすると、数秒で元に戻せるためです。

評価

npm run bench                 # 複数ラベル、7ケース:jev と任意の OpenRouter モデルを比較
npm run single -- --dataset emotion --backend jev
npm run single -- --dataset ag_news --backend openrouter:qwen/qwen3.7-flash
python3 eval/escalate.py --dataset emotion     # スマートティアで得られる効果

single.pyは、初回実行時にAG Newsとdair-ai/emotionのテスト行をダウンロードし、生の結果をeval/data/results/にキャッシュするため、escalate.pyでバックエンドを組み合わせても追加の費用はかかりません。注意事項についてはeval/README.mdを参照してください。

レート制限

Durable ObjectでIPごとに分類数をカウントします。fastでは3,000回/分、20,000回/日、smartでは200回/分、2,000回/日です。

ほかに2つの方法を試しましたが、採用しませんでした。

  • Cloudflareのネイティブなratelimitバインディングは正常に登録されるものの、カウントが減りませんでした(上限60回に対して70回呼び出しても、すべてsuccess: trueが返されました)。
  • KVはエッジキャッシュされ、整合性が結果整合性であるため、この秒に書き込まれたカウンターが次の読み取りに反映されません。すべてのリクエストでremaining: 59が返されました。

Durable Objectはシングルスレッドで強整合性を備えており、カウンターに必要な性質を満たしています。元の60回/分の上限で検証した結果は、75件のリクエストに対して60件が200、15件が429でした。

DNS

ドメインはPorkbunで登録されており、WorkerはCloudflare上にあります。Cloudflare Workersのカスタムドメインを使うには、ゾーンがCloudflare上にある必要があります。また、ここで使っているどちらのAPIトークンにもzone.create権限がないため、この手順は手動で行います。

  1. https://dash.cloudflare.com -> ドメインを追加 -> classifier.dev -> Freeプラン
  2. ./finish-dns.sh — 割り当てられたネームサーバーを読み取り、Porkbun API経由でPorkbunの設定先を変更して、apexとwwwにWorkerを割り当てます。

エージェントスキル

npx skills add https://classifier.dev

RFC 8615のwell-knownディスカバリーを使って、このドメインから配信するため、途中にリポジトリは不要です。

src/SKILL.md                          Textモジュールとしてバンドルされたスキル
GET /skill.md                         アーティファクト
GET /.well-known/agent-skills/index.json   ディスカバリー、スキーマ v0.2.0

インデックスにはアーティファクトのsha256を含める必要があります。インデックスとファイルが一致しない場合、スキルをインストールできません。後の編集で気付かないうちに無効になるダイジェストをコミットする代わりに、src/skill.tsは実際に配信するバイト列を分離環境ごとに一度ハッシュします。そのため、SKILL.mdを編集するだけでよく、ほかの更新は不要です。

このスキルでは、APIの売り文句では見落とされるケースを説明しています。あなたはすでにモデルであり、目に見えるものなら何でも分類できます。したがって、呼び出す理由は能力ではなくコンテキストです。たとえば、40件の検索結果を40件すべて読まずに6件に絞り込むことです。

テストを通して苦労して分かったため、スキルで説明していることが2つあります。Cloudflareは、リクエストがWorkerに届く前にPython標準ライブラリのurllibのUser-Agentを403で拒否するため、手順では明示的に設定しています。また、フィルターには「迷ったら残す」と伝えるべきです。10件のスニペットを使った調査フィルターでは、余計なノイズを増やさずにシグナルを残す割合が4/6から6/6に向上しましたが、「関連性がある可能性」のラベルを3つ目に追加しても効果はありませんでした。

ディスカバリー用のエンドポイント

GET /openapi.json               OpenAPI 3.1。/.well-known/openapi.json からも利用できます
GET /llms.txt                   エージェント向けの簡潔なインデックス。robots.txt からリンクされています
GET /benchmark                  実測した精度、コスト、レイテンシ

どちらも GET / の3つ目の段落からリンクされているため、ランディングページを読むエージェントがすぐに見つけられます。

既知の問題:Cloudflare の管理対象 robots.txt

ゾーンを追加すると、Cloudflare AI Crawl Control が有効になり、管理対象のブロックが /robots.txt の先頭に追加されます。このブロックでは、GPTBot、ClaudeBot、CCBot、Google-Extended、Bytespider、Amazonbot、meta-externalagent が拒否されています。Worker 自身の robots.txt はその後に追加されるため、この設定を上書きできません。

これは実行時に API を利用するクライアントではなく、学習用のクローラーをブロックします。つまり、どのエージェントも引き続き API を呼び出せます。しかし、ドキュメントが将来のモデルのトレーニングデータに含まれなくなるため、ディスカバリーの妨げになります。 dash.cloudflare.com -> classifier.dev -> AI Crawl Control -> Security にあるトグルを切り替えても設定が保持されなかったため、プランレベルでの変更かサポートへの問い合わせが必要と思われます。

運用者のエージェントアクセス

運用者が所有する一括エージェント処理では、専用の AGENT_API_KEY Worker シークレットを設定し、Authorization: Bearer ... として送信します。分類の割り当て量は無制限ですが、推論には別途、運用者用の1日あたり $2 の上限(OPERATOR_DAILY_USD)が適用されます。この上限は IP 間で共有され、無料利用と同じリクエスト数および同時実行数の制限が適用されます。匿名利用の上限を消費したり拡大したりすることはなく、ENTERPRISE_API_KEY の代わりにもなりません。CLI では CLASSIFY_API_KEY または CLASSIFIER_API_KEY を通じて設定できます。非公開レポートや管理者アクセスの認可には使用できません。無視対象のシークレットファイルに保管し、公開クライアントには絶対に渡さないでください。認証されていないトラフィックには、引き続き匿名利用の上限が適用されます。

資金が補填されたワークスペースキーに、合意済みの一括利用上限を適用するには、API_KEY_RATE_LIMIT_MULTIPLIERS Worker シークレットに、各キーの SHA-256 16進ダイジェストを整数の倍率(1~1,000)に対応付ける JSON オブジェクトを設定します。上書きによって引き上げられるのは、そのキーの受付上限のみです。アカウントのカウンター、残高の請求、モデルの処理能力上限、その他のキーには影響しません。プランの上限に戻すには、そのエントリーを削除します。

分類コードの確認

ドメイン用語は CONTEXT.md で定義されています。

モジュール役割
src/index.tsHTTP 検証、上限、モデル選択、スマートなエスカレーション、フォールバック、レスポンスのフォーマット。
src/jev.tsJev の質問と回答、共有バッチ予算とリカバリー、ゲートウェイ/直接トランスポート、プロバイダーの検証。
src/dimensions.ts次元の定義と、入力と次元に関する判定から Jev の質問への変換、およびその逆変換。

通常の分類と次元分類は、どちらも同じ Jev バッチ処理モジュールを使用します。 質問グループは、リカバリー時にまとめて保持されるリクエストの最小単位です。通常の分類では1つの入力に対する質問、次元分類では1つの判定が単位になります。バッチの準備では、共有される入力テキストを1回だけ計上し、両方のプロバイダー予算を確認します。次元分類の場合は、上限の計上前に準備を実行します。実行時には同時リクエスト数を制限し、サイズ超過のバッチを質問グループ間で分割します。

プロバイダーの予算とリカバリーは src/jev.ts で変更し、次元の意味は src/dimensions.ts で維持してください。パッキングの内部実装には依存せず、jevClassify と Worker の HTTP インターフェースを通じて、送信されたリクエストと返された分類をテストしてください。

複数の次元

POST /v1/classify は items と dimensions も受け付けます。

{
  "items": ["Checkout charges me twice"],
  "dimensions": {
    "team": ["billing", "identity", "platform"],
    "urgency": {
      "labels": ["immediate", "normal", "low"],
      "instructions": "Active financial harm is immediate."
    },
    "kind": ["bug", "request", "question"]
  }
}

各 results[i].dimensions[name] には、それぞれのラベル、信頼度、スコア、モデル、レイテンシーが含まれます。Jev は項目と次元の組み合わせに関する質問間で状態を共有し、両方のコンテキスト制限に収まるように処理します。スマートエスカレーションはフィールドごとに行われ、元のスコアはクリアされます。Jev が利用できない場合のフォールバックは、最大 20 件の判定までです。API は最大 20 個の次元と 1,000 件の判定を受け付け、各判定はクォータに加算されます。classify_dimensions MCP ツールも同じ処理経路を使用します。

アナリティクスには、blob9(単一/複数/次元)、double6(成功した入力項目数)、double7(次元数)、double8(不確実またはスコアなしのフィールド数)、double9(フォールバックで処理されたフィールド数)が追加されます。double1 は成功した判定数をカウントします。次元設定はキー付きフィンガープリントとして保存され、設定そのものが保存されることはありません。管理パネルは、失敗と呼び出し元の日数を含む次元トラフィックを個別に照会します。既存のアラート用 cron は、1 時間あたりの次元リクエストの 10%を超える 5xx エラーが 3 件以上発生した場合に加え、次元のフォールバックが使用された場合も通知します。

Node 22.18 以降を使用し、実際の本番 API に対して npm run test:e2e を実行します。または、CLASSIFIER_BASE_URL にステージング環境のオリジンを設定します。これらは node:test を使用する、名前付きの TypeScript テストであり、実際の HTTP/モデル呼び出しを行います。fetch や推論のモックは使用しません。共有 IP クォータを使い切った場合は、認証済みのキーを CLASSIFIER_API_KEY に設定してください。設定は任意です。このテストスイートは推論を実行し、通常のクォータに加算されます。

テストでは、300 件のバッチにおける判定と順序、各フィールドで許可されるラベルと確率分布、実際のスマートエスカレーション、プロバイダーのスコア保持、エイリアス、検証エラー、MCP、OpenAPI、レガシー呼び出しを確認します。 レスポンスは unknown として開始し、型付き行列になる前に検証されます。スマートテストでは実際のエスカレーションが必要です。すべてのテストデータの信頼度が高くなるようなモデルのドリフトがあると、推論プロバイダーを使わずにテストを黙ってスキップするのではなく、テストが失敗します。

npm run typecheck は Worker と TypeScript のライブテストをチェックします。デプロイ CI は公開後にライブテストスイートを実行し、npm test はオフラインのままです。障害注入テストは test/dimensions.test.ts と test/dimensions-observability.test.ts に引き続きあります。

Worker をデプロイせずに本番環境を確認するには、GitHub Actions から ライブ API テスト ワークフローを手動で実行します(または gh workflow run e2e.yml を実行します)。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。