Typecast について
Typecastは、LLMによる抽出処理を評価する開発者向けのプロジェクトです。求人情報をスキーマ検証済みJSONに変換し、エラーを返す再試行と実験結果の集計で有効性や精度を測定します。
主な機能
- 01モデルから生成したスキーマでJSON検証
- 02最大3回のエラーフィードバック再試行
- 03スキルと勤務地の決定的な整形
- 04モデルの有効性と精度の測定
活用シーン
- 01求人情報から型付きデータを抽出
- 02スキーマ制約と再試行方式の比較
- 03複数回の実行で抽出失敗を測定
MCP 対応
PyPIで `typecast-mcp` MCP サーバーを提供しています。MCPインターフェースの詳細は入力に記載されていません。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
2掲載時のツール一覧 · 3 日前に更新
このツールにパラメーターはありません。
詳細
Typecast
雑然とした求人情報をスキーマ検証済みの型付き JSON に変換し、その変換がどの程度失敗するかを測定する LLM エージェントです。
主な調査結果(OpenRouter 経由の 2 つの Qwen モデル、実際の求人情報 30 件):
- スキーマモードは出力を有効にしますが、正確にするわけではありません。 セットアップ C では、両モデルとも 98.9% の割合で有効な出力を生成し、再試行よりも低コストでしたが、エンドツーエンドの精度は 67.1%(7B)と 84.7%(235B)でした。
- エラーフィードバックによる再試行は小型モデルには有効でしたが、大型モデルには有効ではありませんでした。 セットアップ B では、7B は 98.9% の割合で有効でしたが、235B は 88.9% でした。235B はセットアップ B の 4 回すべての実行で jd_002 に失敗しました。件数を考慮した再試行メッセージでも変化はありませんでした。
- フィールド精度 90% の目標に達した実行はなく(最高:87.3%)、実行ごとにばらつきがあります。 同じセットアップでも、実行間でエンドツーエンドの精度が約 8 ポイント変動しました。そのため、単一の実行同士の差だけでは結論を出せません。
60 秒の概要:
https://github.com/user-attachments/assets/c4a2572c-88a2-49a3-8e20-176010877154
CLI とパッケージ名:soa · PyPI の MCP サーバー:typecast-mcp
結果
OpenRouter 経由で 2 つのオープンウェイトモデルを使用し、実際の求人情報 30 件を 4 つのセットアップで処理しました。フィールド精度は、手作業で検証した 20 件の正解ラベルに基づいて評価しています(作成方法については制限事項を参照)。3 回実行した場合、数値は平均値で、括弧内は実行間の最小値~最大値です。1 回実行した場合は、その単一の実行結果です。A = 通常のプロンプト、再試行なし、B = 通常のプロンプト+エラーフィードバックによる再試行、C = プロバイダーのスキーマモード、再試行なし、D = スキーマモード+再試行です。
| モデル | セットアップ | 実行回数 | 最終的な有効率 | エンドツーエンドの精度 | 30 件を処理した場合のコスト |
|---|---|---|---|---|---|
| qwen-2.5-7b-instruct | A · 通常 | 1 | 83.3% | 50.4% | $0.0081 |
| qwen-2.5-7b-instruct | B · 通常+再試行 | 3 | 98.9% (96.7%-100.0%) | 67.1% (62.9%-70.4%) | $0.0103 ($0.0101-$0.0105) |
| qwen-2.5-7b-instruct | C · スキーマ | 3 | 98.9% (96.7%-100.0%) | 67.1% (65.0%-69.8%) | $0.0076 ($0.0076-$0.0076) |
| qwen-2.5-7b-instruct | D · スキーマ+再試行 | 1 | 100.0% | 70.0% | $0.0080 |
| qwen3-235b-a22b-2507 | A · 通常 | 1 | 70.0% | 60.4% | $0.0120 |
| qwen3-235b-a22b-2507 | B · 通常+再試行 | 3 | 88.9% (86.7%-93.3%) | 75.6% (72.8%-80.9%) | $0.0211 ($0.0199-$0.0234) |
| qwen3-235b-a22b-2507 | C · スキーマ | 3 | 98.9% (96.7%-100.0%) | 84.7% (82.8%-85.8%) | $0.0136 ($0.0134-$0.0140) |
| qwen3-235b-a22b-2507 | D · スキーマ+再試行 | 1 | 100.0% | 85.4% | $0.0143 |
完全な表:初回の有効率、平均呼び出し回数、フィールド精度も掲載
| モデル | セットアップ | 実行回数 | 初回の有効率 | 最終有効率 | 平均呼び出し回数 | フィールド精度(有効な出力) | エンドツーエンド精度 | 30 件の文書を 1 回実行するコスト | |---|---|---|---|---|---|---|---|---| | qwen-2.5-7b-instruct | A:プレーンプロンプト、再試行なし | 1 回実行 | 83.3% | 83.3% | 1.00 | 62.9% | 50.4% | $0.0081 | | qwen-2.5-7b-instruct | B:プレーンプロンプト+エラーフィードバックによる再試行 | 3 回の平均(最小~最大) | 82.2%(80.0%-83.3%) | 98.9%(96.7%-100.0%) | 1.20(1.17-1.23) | 68.2%(66.0%-70.4%) | 67.1%(62.9%-70.4%) | $0.0103($0.0101-$0.0105) | | qwen-2.5-7b-instruct | C:スキーマモード、再試行なし | 3 回の平均(最小~最大) | 98.9%(96.7%-100.0%) | 98.9%(96.7%-100.0%) | 1.00(1.00-1.00) | 68.2%(66.5%-69.8%) | 67.1%(65.0%-69.8%) | $0.0076($0.0076-$0.0076) | | qwen-2.5-7b-instruct | D:スキーマモード+再試行 | 1 回実行 | 96.7% | 100.0% | 1.03 | 70.0% | 70.0% | $0.0080 | | qwen3-235b-a22b-2507 | A:プレーンプロンプト、再試行なし | 1 回実行 | 70.0% | 70.0% | 1.00 | 86.6% | 60.4% | $0.0120 | | qwen3-235b-a22b-2507 | B:プレーンプロンプト+エラーフィードバックによる再試行 | 3 回の平均(最小~最大) | 76.7%(73.3%-80.0%) | 88.9%(86.7%-93.3%) | 1.34(1.33-1.37) | 85.6%(85.1%-86.0%) | 75.6%(72.8%-80.9%) | $0.0211($0.0199-$0.0234) | | qwen3-235b-a22b-2507 | C:スキーマモード、再試行なし | 3 回の平均(最小~最大) | 98.9%(96.7%-100.0%) | 98.9%(96.7%-100.0%) | 1.00(1.00-1.00) | 86.1%(85.6%-87.0%) | 84.7%(82.8%-85.8%) | $0.0136($0.0134-$0.0140) | | qwen3-235b-a22b-2507 | D:スキーマモード+再試行 | 1 回実行 | 100.0% | 100.0% | 1.00 | 85.4% | 85.4% | $0.0143 |読み方:
- 「3 回の平均(最小~最大)」(B と C)は、同じ 30 件の文書で 3 回実行した平均で、最小値と最大値を括弧内に示しています。これは実行間のばらつきであり、区間推定ではありません。B の実行は、以下で説明する再試行フィードバックの修正後に行った 3 回です。C の実行は、最初の C の実行と 2 回の再実行です。「1 回実行」(A と D)はそれぞれ 1 回の結果です。再実行の結果から、同じセットアップでも実行間でエンドツーエンド精度が約 8 ポイント変動することがわかります(235B のセットアップ B:72.8%~80.9%)。各実行の 95% 区間(有効性の列は Wilson 法、精度の列はブートストラップ法)は、このセクション末尾の実行ごとの表にあります。A と D の単独実行には区間がありません。区間を算出するコードが存在する前に実行したためです。
- 初回/最終有効率:初回の応答、または 3 回の試行以内のいずれかの応答が検証に合格した文書の割合です。平均呼び出し回数:文書あたりのモデル呼び出し回数です。
- **フィールド精度(有効な出力)**は、有効な投稿が生成された文書のみを対象に測定するため、失敗が多いセットアップでも実際より良く見えます(235B のセットアップ A ではフィールド精度が 86.6% ですが、ラベル付けされた文書のうち採点可能な有効な出力があったのは 70.0% だけです)。エンドツーエンド精度では、失敗した文書のすべてのフィールドを誤りとして数えます。有効率が異なるセットアップを比較する場合は、この数値を重視してください。
- コストは、30 件の文書を 1 回実行した場合のプロバイダー報告コストです。温度は設定していません(プロバイダーのデフォルトが適用されましたが、値は不明です)。
目標と達成状況
セットアップ B について、実行前に 3 つの目標を設定しました。最終有効率 30/30、文書あたりの平均呼び出し回数 1.3 以下、フィールド精度 90% 以上です。実行ごとの結果(再試行の修正後に行った 3 回)は次のとおりです。
| 目標(セットアップ B) | qwen-2.5-7b-instruct | qwen3-235b-a22b-2507 |
|---|---|---|
| 最終有効率 30/30 | 3 回中 2 回達成(100.0%、100.0%、96.7%) | 3 回すべて未達(86.7%、93.3%、86.7%) |
| 平均呼び出し回数 <= 1.3 | 達成(1.20、1.17、1.23) | 未達(1.37、1.33、1.33) |
| フィールド精度 >= 90% | 未達(70.4%、68.1%、66.0%) | 未達(85.7%、85.1%、86.0%) |
両モデルのすべてのセットアップで、フィールド精度 90% の目標はすべての実行で未達でした。全体で最も高い数値は 87.3% です(235B、セットアップ B、1 回目の実行)。
95% 区間を含むすべての実行(results/summary.md より、そのまま掲載)
1~8行目:最初のラウンドです。モデルと設定ごとに1回実行しています(7BはA、B、C、Dの順で、その後235B)。インターバルコードがまだなかった時点で記録されたため、インターバルはありません。9~18行目:反復ラウンドです。7B Bを3回、7B Cを2回、235B Bを3回、235B Cを2回の順です。インターバルは角括弧内に記載しています。列の説明:`field acc` は有効な出力を対象とした精度、`coverage` はラベル付けされた20件の文書のうち有効な出力が得られた割合、`e2e acc` は失敗を誤りとして数えた精度です。| model | setup | n | first-try | final | avg calls | field acc (norm) | coverage | e2e acc (norm) | cost |
|---|---|---|---|---|---|---|---|---|---|
| openrouter/qwen/qwen-2.5-7b-instruct | A | 30 | 83.3% | 83.3% | 1.00 | 62.9% | 80.0% | 50.4% | $0.0081 |
| openrouter/qwen/qwen-2.5-7b-instruct | B | 30 | 86.7% | 96.7% | 1.17 | 68.3% | 95.0% | 64.8% | $0.0102 |
| openrouter/qwen/qwen-2.5-7b-instruct | C | 30 | 100.0% | 100.0% | 1.00 | 69.8% | 100.0% | 69.8% | $0.0076 |
| openrouter/qwen/qwen-2.5-7b-instruct | D | 30 | 96.7% | 100.0% | 1.03 | 70.0% | 100.0% | 70.0% | $0.0080 |
| openrouter/qwen/qwen3-235b-a22b-2507 | A | 30 | 70.0% | 70.0% | 1.00 | 86.6% | 70.0% | 60.4% | $0.0120 |
| openrouter/qwen/qwen3-235b-a22b-2507 | B | 30 | 83.3% | 86.7% | 1.33 | 87.3% | 85.0% | 74.3% | $0.0186 |
| openrouter/qwen/qwen3-235b-a22b-2507 | C | 30 | 100.0% | 100.0% | 1.00 | 85.8% | 100.0% | 85.8% | $0.0140 |
| openrouter/qwen/qwen3-235b-a22b-2507 | D | 30 | 100.0% | 100.0% | 1.00 | 85.4% | 100.0% | 85.4% | $0.0143 |
| openrouter/qwen/qwen-2.5-7b-instruct | B | 30 | 80.0% [62.7-90.5] | 100.0% [88.6-100.0] | 1.20 | 70.4% [64.4-76.9] | 100.0% | 70.4% [64.4-76.9] | $0.0104 |
| openrouter/qwen/qwen-2.5-7b-instruct | B | 30 | 83.3% [66.4-92.7] | 100.0% [88.6-100.0] | 1.17 | 68.1% [61.4-74.7] | 100.0% | 68.1% [61.4-74.7] | $0.0101 |
| openrouter/qwen/qwen-2.5-7b-instruct | B | 30 | 83.3% [66.4-92.7] | 96.7% [83.3-99.4] | 1.23 | 66.0% [59.6-72.2] | 95.0% | 62.9% [54.5-70.5] | $0.0105 |
| openrouter/qwen/qwen-2.5-7b-instruct | C | 30 | 100.0% [88.6-100.0] | 100.0% [88.6-100.0] | 1.00 | 66.5% [59.6-73.8] | 100.0% | 66.5% [59.6-73.8] | $0.0076 |
| openrouter/qwen/qwen-2.5-7b-instruct | C | 30 | 96.7% [83.3-99.4] | 96.7% [83.3-99.4] | 1.00 | 68.4% [61.3-75.0] | 95.0% | 65.0% [55.2-73.3] | $0.0076 |
| openrouter/qwen/qwen3-235b-a22b-2507 | B | 30 | 76.7% [59.1-88.2] | 86.7% [70.3-94.7] | 1.37 | 85.7% [81.3-89.6] | 85.0% | 72.8% [58.4-84.8] | $0.0234 |
| openrouter/qwen/qwen3-235b-a22b-2507 | B | 30 | 73.3% [55.6-85.8] | 93.3% [78.7-98.2] | 1.33 | 85.1% [81.1-88.9] | 95.0% | 80.9% [71.1-87.9] | $0.0199 |
| openrouter/qwen/qwen3-235b-a22b-2507 | B | 30 | 80.0% [62.7-90.5] | 86.7% [70.3-94.7] | 1.33 | 86.0% [82.0-89.6] | 85.0% | 73.1% [58.7-84.9] | $0.0202 |
| openrouter/qwen/qwen3-235b-a22b-2507 | C | 30 | 96.7% [83.3-99.4] | 96.7% [83.3-99.4] | 1.00 | 87.0% [83.7-89.9] | 95.0% | 82.8% [72.3-89.0] | $0.0136 |
| openrouter/qwen/qwen3-235b-a22b-2507 | C | 30 | 100.0% [88.6-100.0] | 100.0% [88.6-100.0] | 1.00 | 85.6% [82.0-88.8] | 100.0% | 85.6% [82.0-88.8] | $0.0134 |
メインテーブルのAおよびDの数値は、上記の1~8行目(AとD)です。最初のラウンドのBの行(7B:初回/最終が86.7%/96.7%、235B:初回/最終が83.3%/86.7%)は、再試行の修正前に実行したものです。最初のラウンドのCの行は、メインテーブルにある3回のC実行のうち最初のものです。反復実行の集計はresults/aggregate.mdに、実行ごとの生レポートはresults/2026-11-04_*.jsonにあります。
text ──► build prompt (schema + instructions)
│
▼
LLM call ──► raw string
│
▼
JobPosting.model_validate_json(raw)
│ ok │ ValidationError
▼ ▼
log success log failure (error + raw)
return model attempt < MAX? ──► append error to messages, retry
│ no
▼
raise ExtractionFailed (logged)
モデルに提示されるスキーマは Pydantic モデル(JobPosting.model_json_schema())から生成され、手書きされることはありません。また、応答も同じモデルで検証されます。応答の検証に失敗すると、検証エラーをエラーごとに1行でモデルに返し、最大3回まで試行します。有効な応答が得られた後、2つの決定論的なツールで結果を整えます(スキル名の正規化、ドキュメントヘッダーに対する勤務地の確認)。これらはモデル呼び出しではなく通常のコードなので、LLM の呼び出し回数は増えず、以下のセットアップを比較可能な状態に保てます。詳細はdocs/ARCHITECTURE.mdをご覧ください。
4つのセットアップは次のとおりです。A 通常のプロンプト、再試行なし。B 通常のプロンプト、エラーをフィードバック(最大3回試行)。C プロバイダーの厳密な JSON スキーマモード、再試行なし。D スキーマモードと再試行。
ネイティブの構造化出力だけではだめなのはなぜですか?
スキーマモードは大きく役立ち、コストも低くなりました。しかし、それだけですべてを解決できるわけではありません。
解決できたこと。 セットアップ C では、両モデルとも初回で有効な応答が得られた割合は98.9%(96.7%~100.0%)でした。単一の通常プロンプト実行である A では、7B が83.3%、235B が70.0%でした。通常モードで実際にモデルが破った制約は、skills の30項目上限でした。スキーマモードでは、レポートにスキーマで表現される制約を破った応答数が記録されますが、C と D のすべての実行で0件でした(Cを6回、Dを2回実行)。C のコストも B より低く、7B では0.0076ドル対0.0103ドル、235B では0.0136ドル対0.0211ドルでした(1実行あたりの平均)。
できないこと:
- 保証するのは形式であり、意味ではありません。 C で残った失敗は、JSON スキーマでは表現できないルールによるものでした。C の6回の実行のうち2回では、1つのドキュメントでスキル名の重複(「LangChain」と「Langchain」、「Python」が2回)が原因で却下されました(どちらの却下もresults/failure_examples.jsonlに記録されています)。そのため、C の最終的な有効率は98.9%(96.7%~100.0%)であり、100%ではありません。「インターンなら経験なし」というルールも同様です(スキーマでは表現できません)。このプロジェクトで記録された637行の試行ログでは一度も発生していません(そのファイルの最終行に件数が記録されています)。そのため、このルールについてはどちらとも言える証拠がありません。
- 有効であることは、正しいことを意味しません。 スキーマモードでは、235B の有効率は98.9%(96.7%~100.0%)でしたが、フィールド精度は86.1%(85.6%~87.0%)、エンドツーエンド精度は84.7%(82.8%~85.8%)で、私の目標である90%を下回りました。C の7Bも有効率は同じですが、フィールド精度は68.2%(66.5%~69.8%)です。3回の C 実行におけるシニオリティ精度は、それぞれ55.0%、55.0%、52.6%でした。有効であることは、シニオリティが正しいかどうかを示しません。
- 有効性と引き換えに再現率が下がる可能性があります。 上限は、モデルがより短いスキルリストを出力することで守られますが、スキルは精度が最も低い項目です。235B のスキル F1 は、3回の C 実行で66.9%、72.1%、65.7%でした。3回の B 実行では71.4%、71.6%、68.3%でした(平均は68.2%対70.4%、範囲は重複)。初回の単一実行では72.1%(A)、73.8%(B)に対し、66.9%(C)、63.4%(D)でした。再実行の結果から分かるのは、これは明確な効果ではなく傾向にすぎないということです。また、ラベル付きドキュメントが20件なので、それ以上の主張はしません。
- プロバイダーに依存します。 ここでは両モデルとも厳密な
json_schemaを受け付けましたが、これは OpenRouter のrequire_parametersを指定してリクエストを送信し、対応プロバイダーにのみルーティングされるようにしたためです。7B は常に1つのプロバイダーから提供され、235B は3つのプロバイダーに分散していました。
よくある失敗の種類と、その解決方法
通常のプロンプトでの実行と再試行での実行から(件数は、そのフィールドで最初にエラーが発生した失敗試行の数です。レポートの failures.by_first_loc を参照してください):
| 失敗 | setup A(1回の実行) | setup B、3回の実行 | 解決方法 |
|---|---|---|---|
skills が上限の30件を超過 | 7B:失敗したドキュメント5件中3件、235B:失敗したドキュメント9件中8件 | 7B:失敗試行が5件、5件、3件、235B:14件、11件、14件 | 再試行時のフィードバックで大半を解決できます。スキーマモードでは発生しません。 |
スキル名が40文字を超過(skills.*.name) | 7B 2件、235B 1件 | 7B:0件、0件、3件、235B:1件、1件、0件 | 再試行 |
true/false ではなく null(skills.*.required、salary_mentioned) | なし | 7B:ある実行で skills.*.required に2回、別の実行で salary_mentioned に1回の失敗試行 | 再試行 |
スキル名の重複(エラーが報告される場所が skills であるため、skills にも計上) | 個別には集計せず | 個別には集計せず | 再試行で解決します。setup B で重複名が原因で拒否された5件は、すべて次の試行で有効な結果になりました(拒否された試行と、それに続く有効な結果はそれぞれ results/failure_examples.jsonl に記録されています)。スキーマモードでは解決できません。C に残った唯一の失敗で、2つのドキュメントが同じファイルでした。 |
| 不正な JSON、Markdown のコードフェンス、散文、余分なフィールド、無効な列挙値 | なし:ルート、seniority、mode、その他の列挙値や余分なフィールドで失敗した実行はありません | なし | 対応不要 |
問題のほとんどは件数の上限であり、形式の問題ではありません。最初の11ドキュメントのベースラインでは、両モデルの失敗はすべて skills リストが長すぎることが原因でした(results/2026-11-03_baseline_*.md)。最初に試したのは、フィードバックをより具体的にすることです。デフォルトのフィードバック行には、バリデーター自体の文言が使われます(例:skills: List should have at most 30 items after validation, not 42)。リストが長すぎる場合は、この文言を件数と修正方法に置き換えました。たとえば skills: has 39 items, maximum is 30 - remove 9, starting with required=false ones です。結果はまちまちです。
- setup B での235Bの最終的な有効率は上がりませんでした。変更前は86.7%で、その後の3回の実行では86.7%、93.3%、86.7%でした。各実行の信頼区間には以前の数値が含まれているため、n=30 では効果は確認できません。
- 最初のBの実行で失敗した235Bの4つのドキュメント(jd_002、jd_003、jd_005、jd_023)は、その後のドキュメント単位の実行12回中6回で成功しました(jd_002 は3回中0回、jd_003 は3回中1回、jd_005 は3回中2回、jd_023 は3回中3回)。しかし、別のドキュメント(jd_029、jd_030、jd_004)が代わりに失敗しました。修正の効果と実行ごとのばらつきを切り分けることはできません。最初の試行は修正前後で同一ですが、初回の有効率は83.3%から76.7%、73.3%、80.0%へと変動しました。このばらつきは実行ごとの差によるものです(サンプリングと、最初のラウンドでは記録していなかったプロバイダーの構成が影響しています)。
- jd_002 は setup B の4回すべてで235Bが失敗しました。 各試行はすべて results/failure_examples.jsonl に記録されています。最初の実行(修正前)では42件、31件、31件で、その後の3回の実行では70件、31件、31件、47件、31件、31件、44件、31件、31件でした。モデルは最初の再試行で42~70件から31件まで減らし、その後は31件のままです。31件の回答に対する新しいフィードバック行は
skills: has 31 items, maximum is 30 - remove 1, starting with required=false onesです(コードが生成するもので、プロンプト本文は記録されていません)。これが最も明確な未解決の失敗です。モデルは最後の1件を削除できません。
設計上の決定事項
- すべてのモデルで
strict=Trueを指定します。 Pydantic のデフォルトの寛容モードでは、"3"が3に、"yes"がTrueにひそかに変換されるため、雑な応答が通過し、リトライループで対処することがありませんでした。厳格モードでは、どちらも拒否されます。 - 必須だが null を許容するフィールド(
company、city、country、min_years_experience)です。デフォルト値はありません。モデルは 明示的にnullと記述する必要があるため、「記載なし」はモデルが下した判断となり、キーの記述漏れは暗黙のnullではなくエラーになります。 - リトライ用プロンプトではエラーごとに1行とし、
loc: msg (got: <input, clipped to 60 chars>)の形式で、str(error)ではなくValidationError.errors()から生成します。生の文字列にはバージョン固有の接頭辞、切り詰められたinput_value、ドキュメントの URL が含まれており、モデルにとってはノイズになります。例外は長すぎるリストで、上記の件数を示す行を使います。エラー文なしでリトライする比較実験(エラーのフィードバックなしでリトライ)を行っていないため、フィードバックと単純な再サンプリングの差は測定できていません。セットアップ A と B の比較は、フィードバックありのリトライと、リトライなしの比較です。 - ツールはモデルから呼び出すのではなく、決定論的な後処理として実行します。 検証後に、正規化されたスキル名と場所のチェックを通常のコードとして実行します。これにより、すべてのセットアップでプロンプトは1つのままとなり、リトライ数・試行数・コストを明確に保てます。また、モデル自身の出力は
raw_postingに保存されるため、妥当性はモデルが実際に出力した内容に基づいて測定されます。モデルにツールを呼び出させるのは別の実験であり、まだ実施していません。 - API エラーは検証エラーとは別に扱います。 429 や 5xx はバックオフ(2、4、8 秒)を入れてリトライし、3回の検証試行には含めません。
- スキル数の上限30は維持します。 最初のプレーンプロンプトによるベースラインでは、すべての失敗がこの制約によるものだったため、数値を良く見せるために上限を緩めるのではなく、リトライとスキーマモードで何ができるかを試す条件として維持しました。
制限事項
- 求人情報は30件、ゴールドラベルは20件です。 1件の文書で有効性率は3.3ポイント、フィールド精度は5ポイント変動します。そのため、上記の信頼区間は幅が広くなっています(たとえば、235B のセットアップ B では、ある実行で最終有効性が93.3%[78.7~98.2]でした)。30件のうち5件は、意図的にドメイン外のデータとして含めた、非技術系または準技術系の職種です。そのうち2件(jd_012、jd_019)は、ラベル付けした20件に含まれています。
- ゴールドラベルはモデル(Claude)が作成し、その後、私が確認しました。 すべてのフィールドを検証し、17件の修正を行いました(docs/label_corrections.log)。確認したのは1人で1回のみのため、私の体系的な誤りがあれば、ラベルにも反映されています。ラベル付けのルールは最初のベースライン実行後にスキーマのフィールド説明へ記載したため、モデルとラベルには同じルールが適用されています。それ以前のプロンプトによる実行結果とは比較できません。
- ブラインドチェックによる精度のノイズフロアです。 別の5件の文書(jd_004、005、008、014、015。いずれもゴールドセットには含まれていません)をブラインドでラベル付けし、事前に作成されたClaudeの封印済みラベルと比較しました。評価と同じフィールド別指標を使い、私のラベルを参照データとしました。結果は、title 80.0%、company 100.0%、seniority 80.0%、mode 80.0%、salary_mentioned 100.0%、min_years_experience 100.0%、city 80.0%、country 100.0%、skills F1 61.0%(適合率52.9%、再現率72.0%)、全体86.8%でした(results/label_agreement.md)。5件の文書は妥当性確認であり、割合を示すものではありません。1件の不一致でフィールドの値が20ポイント変わります。この結果は、スキルについては慎重にラベル付けした2人の間でも、モデルとゴールドラベルの間と同程度の不一致があること、また235Bのフィールド精度がおよそ86%で、2人のラベル付け担当者同士の一致度に近いことを示唆しています。そのため、目標の90%に届かない残りの差は、モデルの誤りではなく、ラベルのノイズに一部起因している可能性があります。ただし、それを実証したわけではありません。
- 実行は1日、同じファミリーの2モデル、1つのプロバイダーレイヤーによるものです。 Temperature は設定していません。7Bは常に1つのプロバイダーにルーティングされ、235Bは3つのプロバイダーにルーティングされました。また、第1ラウンドではプロバイダーを記録していなかったため、プロバイダー構成とサンプリングノイズが実行ごとのばらつきに混在しています。
- 後処理ツールは最終的なラベル付けルールに合わせて調整されていないため、一部の文書では場所のチェック結果とラベルが一致しないことがあります。
- セットアップ D はモデルごとに1回のみ実行し、B と C のみ複数回実行しました。
- データです。
data/jds/にある30件の求人情報は、評価のみを目的として公開求人情報からコピーしたものです(data/jds/manifest.csvに基づく内訳:LinkedIn 13件、Work at a Startup 11件、Wellfound 6件)。依頼があれば削除します。manifest.csvにソース URL が記録されていない行が5件あります:jd_001、jd_002、jd_003、jd_004、jd_010(すべてLinkedIn。メモには「URL not recorded」と記載されています)。
自分で実行する
Python 3.11+ と uv が必要です。
git clone https://github.com/nilayp-codes/typecast.git
cd typecast
uv sync
cp .env.example .env # then set OPENROUTER_API_KEY in .env (never commit it)
uv run soa extract data/jds/jd_001.txt # the JSON on stdout, a status line on stderr
uv run soa extract data/jds/jd_001.txt --json-mode # provider schema mode
uv run soa extract data/jds/jd_001.txt > out.json
uv run soa eval --model openrouter/qwen/qwen-2.5-7b-instruct --setup B --limit 5
uv run soa eval --model openrouter/qwen/qwen3-235b-a22b-2507 --setup all
既知の問題:Windowsでは、短いパスにクローンしてください。LiteLLM が .venv 内にキャッシュファイルを書き込むため、パスが長すぎると Windows のパス長制限を超え、soa extract が APIConnectionError ... [Errno 2] で失敗します。
soa eval は実際にモデルを呼び出します。計画と推定呼び出し回数を表示し、送信前に確認を求めます(--yes を指定すると確認を省略します)。私の実行では、30件の文書に対する1つのセットアップの費用は $0.0076~$0.0234 でした。結果は results/<date>_<model>_<setup>.json に保存され、results/summary.md に行が追加されます。中断した実行は --resume <eval id> で再開できます。複数回の実行結果をまとめるには、uv run python scripts/aggregate_runs.py results/<files>.json を実行します(results/aggregate.md に書き込みます)。30件の求人情報は data/jds/ に、ゴールドラベルは data/labels/ にあります。
テストと型チェック(テストでは実際のモデル呼び出しは行いません):
uv run pytest
uv run mypy --strict src/ packages/typecast-mcp/src/
AI エージェントから使う(MCP サーバー)
typecast-mcp は、求人情報の JSON を JobPosting スキーマに照らして検証し、エージェントが対応できる1行形式のエラーを返す、小規模なMCP サーバーです。LLM を搭載しておらず、API キーも不要で、ネットワークへの接続も行いません。独立したパッケージ(packages/typecast-mcp/)で、依存関係は pydantic と mcp の2つだけです。そのため、インストールしても LiteLLM や評価ハーネスの残りの部分はインストールされません。
ツールは2つあります。
get_job_posting_schema:求人情報の JSON Schema です。すべてのルールが含まれているわけではありません(スキル名の重複とインターン/経験年数のルールは、バリデーターでのみチェックされます)。validate_job_posting:引数postingを1つ受け取ります。求人情報の JSON 全体を1つの文字列として渡します。valid、エラー行のリスト(field: problem (got: value))、および有効な場合は解析済みの求人情報を返します。
インストール(uv が必要です)。Claude Desktop、Cursor、その他のクライアント:
{
"mcpServers": {
"typecast": {
"command": "uvx",
"args": ["typecast-mcp"]
}
}
}
Claude Code:claude mcp add typecast -- uvx typecast-mcp。Windows でクライアントが uvx を見つけられない場合は、command に uvx.exe のフルパスを指定します(where.exe uvx で確認できます)。Microsoft Store 版の Claude Desktop は %APPDATA%\Claude\claude_desktop_config.json を読み込みません。Settings → Developer → Edit Config を開き、実際に読み込まれるファイルを開いてください。uv を使わない場合は、pip install typecast-mcp を実行し、コマンドに typecast-mcp を指定します。
クローンから実行する場合。 リポジトリには、作業ツリーからサーバーを実行する .mcp.json(uv run typecast-mcp)が含まれています。Claude Code でフォルダーを開き、確認を求められたら typecast を許可し、/mcp で確認してください。許可すると、このフォルダー内で uv run が実行され、固定された依存関係がインストールされてサーバーが起動します。信頼できるクローンでのみ許可してください。
実際の例。 jd_002 に対する Qwen3-235B の回答には、スキルが70個含まれていました。エージェントが validate_job_posting で検証したところ、次の1行が返されました。
skills: has 70 items, maximum is 30 - remove 40, starting with required=false ones
エージェントは1回のやり取りで求人情報を修正しました。これは Claude Sonnet 5.5 を使った1回の実行結果であり、ベンチマークではありません。
Claude Desktop でのデモ(デモ用にエラーを仕込んでいます)。Claude は validate_job_posting を呼び出し、2行のエラーを受け取って修正し、再検証します。求人情報を有効にするために、年数を推測する必要があった点に注意してください。
https://github.com/user-attachments/assets/4190ef12-fc7c-46f8-ad48-dd128d80aa54
検証対象外のもの: 値が正しいかどうかです。求人情報が有効でも、シニアリティやスキルが誤っている場合があります。なぜネイティブの構造化出力だけを使わないのか?を参照してください。
詳細、制限事項、設計上の判断については、docs/MCP.mdを参照してください。
次に取り組むこと
バリデーター専用の MCP サーバー:完了。AI エージェントから使うを参照してください。PyPI で公開する:MCP サーバーについては完了。パッケージはtypecast-mcpです。- データが示している残りの失敗を修正します。たとえば、jd_002 で最後の選考を通過できないモデルに、どの項目を削除するか伝えます。温度を固定し、プロバイダーを固定して再測定し、繰り返しごとの差を小さくします。
- 正解データセット用の2つ目のラベラー、より大きな文書セット、セットアップ D の繰り返し実行を追加し、項目の正確度が n=20 に制限されないようにします。
ライセンス
MIT。詳細はLICENSEを参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。