lemonade-router-builder について
自然言語で記述されたリクエスト振り分け要件を `collection.router` ポリシーの JSON に変換し、オフライン検証するスキルです。Lemonade のポリシー作成向けで、ファイルの生成と検証のみを行い、登録やリクエスト実行はしません。
主な機能
- 01ルーターポリシー JSON の生成
- 02LLM またはルール方式の選択
- 03分類器と条件指定への対応
- 04JSON のオフライン検証
- 05登録・テスト用コマンドの提示
活用シーン
- 01キーワードや長さによる振り分け
- 02画像やツール要求の候補モデルへの割り当て
- 03サーバー未稼働時のポリシー作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ amd/ skills --skill lemonade-router-builderインストール後、Claude Code で/lemonade-router-builderを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.federated.jsonexamples.mdreference.mdskill-card.mdevals/evals.jsonscripts/validate.py詳細
Lemonade ルータ設定ジェネレーター
リクエストのルーティング方法を平易な英語で説明すると、collection.router ポリシー JSONを生成します。このスキルは JSON の生成と検証のみを行います。稼働中のサーバーへの呼び出し、ポリシーの登録、ポリシーを使用したリクエストの実行は行いません。生成される JSON はサーバー側の厳格なパーサーで最初から受け入れられ、デスクトップアプリの Hybrid Router エディターで編集できます。
前提条件
- ローカルで Lemonade Server v11.5.0+ が実行されていること(
lemonade server start)。 生成したポリシーの登録とテストにのみ必要です。このスキル自体(JSON の生成とオフライン検証)は、稼働中のサーバーがなくても動作します。 - 作成時に GPU や ROCm は不要です。ルーターポリシーは JSON ドキュメントであり、生成や検証にハードウェアは必要ありません。
- Python(任意の 3.x)が PATH にあること。付属のオフラインバリデーター(
scripts/validate.py)で使用します。追加パッケージは必要ありません。
ルーターはリクエストごとに候補モデルを 1 つ選びます。作成方法は 2 種類あり、適切な方法を選ぶことが最初の判断です。
| モード | JSON の形式 | 使用する状況 |
|---|---|---|
| LLM-as-router | routing.router ブロック | ユーザーが具体的なシグナルを指定せず、意味(「機密性の高い内容」「難しい質問」「創作」など)だけで意図を説明する場合。小規模な LLM が各プロンプトを読み、候補を選びます。 |
| ルール | routing.rules(+任意の routing.classifiers) | ユーザーがキーワード、正規表現、長さ、ツール、画像、メタデータ、PII/トピック分類器、しきい値、「最初に一致したもの」、フォールバックロジックなど、具体的なシグナルを指定する場合。決定論的で、単純な条件では追加の LLM 呼び出しはありません。 |
routing.router と routing.rules および routing.classifiers は相互排他的です。両方を出力しないでください。
ステップ 1 - ユーザーの説明から情報を抽出する
- 候補:リクエストに回答する可能性のあるモデル。モデル名はそのまま使用します
(例:
Gemma-3-4b-it-GGUF)。ユーザーがモデル名を指定していない場合は、尋ねてください。モデル名を決して作り出さないでください。利用可能なモデルはlemonade listまたはGET /api/v1/modelsで確認できます。 ユーザーが指定した名前でも、対象ホストに存在しない可能性があります。オフラインバリデーターでは確認できません(ステップ 8b でこの問題を解決します)。 - デフォルト/フォールバック:どの候補を、どの条件にも一致しないリクエストに割り当てるか。指定がない場合は、ユーザーが「ローカル」「小規模」または「安全」と表現したモデルを使用します。それ以外の場合は、最初に挙げられた候補を使用します。
- シグナル:言及されたすべての条件(キーワード、パターン、長さ、画像、ツール、トピック、PII、安全性)と、それぞれの条件でルーティングするモデル。
- 分類器モデル:回答ではなく検出用として挙げられたモデル(BERT 系エンコーダー、埋め込みモデル、判定役として使われる LLM)。
ステップ 2 - ひな形を作成する
必ず以下の形式にしてください(パーサーは不明なキーや不足しているキーを拒否します)。
{
"version": "1",
"model_name": "user.MyHybridRouter",
"recipe": "collection.router",
"components": [],
"routing": { }
}
versionは文字列リテラル"1"です。model_nameはuser.で始まる必要があります。ユーザーが名前を指定した場合は、その説明からスラッグを作成します(user.<Name>。使用できる文字は[A-Za-z0-9._-]のみです)。名前が指定されていない場合は、固定の文字列ではなく、文脈に基づいて名前を付けてください。たとえば、user.<slug-of-default-candidate>-Routerのようにします。こうすることで、既定で異なるポリシー同士が重複しなくなります。**/pullはmodel_nameごとに冪等です。同じ名前で 2 つ目のポリシーを登録すると、1 つ目が通知なく上書きされます。**この会話ですでに名前なしのルーターを作成している場合、次のルーターには同じ名前を再利用しないでください。ユーザーに尋ねるか、明確に異なる名前を選んでください。
ステップ 3 - 候補とデフォルト
"candidates": ["<answering models>"],
"default_model": "<one of candidates>"
default_model は必ず candidates に含めてください。候補には、チャットに対応する LLM を指定してください。埋め込みモデル、分類モデル、画像モデルは指定しないでください。
ステップ 4 - モード A:LLM をルーターとして使用
"router": {
"type": "llm",
"model": "<small chat LLM>",
"prompt": "You route user requests to the best model. <one sentence per candidate: when to pick it, using the exact model name>."
}
-
modelのデフォルトは、最も安価な候補ではなく、最も高性能な候補です。 ルーターはポリシーを通過するすべてのリクエストを判定するため、性能の低い判定モデルが気づかれないまま誤った振り分けを繰り返すよりも、高性能なモデルによる追加コストを許容するほうが適切なデフォルトです。ユーザーに提示する概要には、選択内容を明記してください(router.model: <chosen>。利用可能な候補の中で最も高性能なものです。リクエストごとのコストを下げたい場合は、失敗につながる可能性を承知のうえで、より小規模な専用の判定モデルを自分で選んでください)。ユーザーがこの役割向けに別のモデルをすでに指定している場合は、候補ではなくそのモデルを使用してください。 -
プロンプトを修正した後も(下の箇条書きとステップ 9 を参照)、さまざまなテストプロンプトで
default_usedがtrueのままであれば、プロンプトをさらに変更するのではなく、より高性能なrouter.modelを使用してください。これは判定モデルの能力上の限界であり、プロンプトの表現だけでは解決できません。 -
意図だけを記述してください。返信形式を指定せず、命令形の「X を選択」も使わないでください。 エンジンはプロンプトの後に独自の契約を必ず追加します。その契約には候補名が列挙され、厳密な JSON 形式で
{"model": "<name>", "rationale": "<one sentence>"}と返信するよう求められます。形式に合わない場合はdefault_modelにフォールバックします。「モデル名だけを返信してください」「Model-A を選択してください」「モデル名で回答してください」などと書くのは、実際の通信形式に反しており、性能の低い判定モデルが解析できない単独の文字列を返す原因になります。その場合、目に見えるエラーもなく、すべてのリクエストでdefault_modelにフォールバックします。悪い例(書かないでください):
"機密性の高いクエリには Qwen3.5-9B-GGUF を選択し、それ以外には Qwen3.5-9B-NoThinking を選択してください。"良い例:"リクエストが機密性の高い内容と思われる場合や個人情報を含む場合は、Qwen3.5-9B-GGUF に振り分けてください。それ以外のすべてのリクエストは Qwen3.5-9B-NoThinking に振り分けてください。"各候補が適切となる「条件」だけを記述してください。「選択」「出力」「返信」「回答」といった表現は使わないでください。
-
このモードでは、
rulesまたはclassifiersを絶対に出力しないでください。 モード A のroutingオブジェクトに含めるのは、candidates、default_model、routerのみです。routerと並べてrulesまたはclassifiersを追加するとスキーマ違反となり、サーバーのパーサーに拒否されます。両方を書いていることに気づいたら、作業を止めてrules/classifiersを完全に削除してください。
ステップ 5 - モード B:分類器
ルールで実際に参照される分類器のみを宣言してください。3 種類あります。
{ "id": "clf-1", "type": "classifier", "model": "<classification model>",
"labels": ["PII", "Jailbreak"], "default_label": "PII", "on_error": "match_false" }
{ "id": "clf-2", "type": "semantic_similarity", "model": "<embedding model>",
"reference_phrases": { "shopping": ["I want to shop for pants", "add to cart"] },
"default_label": "shopping", "on_error": "match_false" }
{ "id": "clf-3", "type": "llm", "model": "<chat LLM>",
"prompt": "Classify the request into only labels SAFE, RISKY",
"labels": ["SAFE", "RISKY"], "default_label": "SAFE", "on_error": "match_false" }
厳格な制約(パーサーによる検証 — 全体の一覧は reference.md を参照してください):
classifierタイプ:モデルはテキスト分類モデル(Bert-Phishing-ONNXのようなonnxruntimeエンコーダー)である必要があります。labelsはモデルの実際の出力ラベルと 一致していなければなりません。オフラインでは検証できず、不一致があると スコアは常に警告なく0.0になります(理由についてはreference.mdの分類器に関する 注記を、検出方法についてはステップ 8b を参照してください)。ここでチャット LLM を使うことも できます(チャットを介した LLM-as-classifier)が、その場合はtype: "llm"を優先してください。明示的に指定され、プロンプトも設定されます。semantic_similarity:reference_phrasesは{concept: [phrases...]}形式で、 概念が 1 つ以上あり、それぞれにフレーズが 1 つ以上必要です。概念名が ラベルになります。このタイプではlabelsキーは拒否されます。 モデルは埋め込みモデルである必要があります。概念を新たに作成する場合は、 概念ごとにバリエーションのあるフレーズを 3~5 個指定してください。llm:promptと空でないlabelsの両方が必須です。意図だけを書き、 モデルに返信の形式を指示しないでください。 エンジンは、あなたのプロンプトの後に 独自の{"model": "<chosen_label>", "rationale": "..."}コントラクトを追加します (routing.routerと同じコントラクトです)。「SAFE または RISKY のどちらか 1 つのラベルだけを 返してください」のような記述があると、性能の低いモデルはSAFEだけを出力することがあり、 パーサーに拒否されます。その結果、スコアが空になり、ルールは警告なく 発動しません。各ラベルに該当するリクエストの特徴を説明し、返信形式はエンジンに任せてください。 それでも発動しない場合は、上記のステップ 4 にある判定機能に関する注記を参照してください。 同じ修正方法が適用されます(検出方法はステップ 9 を参照してください)。default_labelを指定する場合、ラベルまたは概念のいずれかである必要があります。- 省略時のデフォルト値:
id=clf-1、clf-2、…;on_error="match_false"(フェイルオープン:分類器が壊れている場合は一致せず、リクエストは次に進みます。 フェイルクローズの安全性をユーザーが求めている場合にのみ"match_true"を使用してください);default_label= 最初のラベル。
ステップ 6 - モード B:ルール
"rules": [
{ "id": "rule-1", "match": { ... }, "route_to": "<candidate>",
"outputs": { "reason": "<optional free-form>" } }
]
- 順序が重要です。最初に一致したルールが適用されます。 最も具体的なルール、 またはプライバシー上特に重要なルールを先に配置してください(「機密情報はローカルに留める」 ルールを「コードは大規模モデルに送る」ルールより先に配置しないと、 個人情報を含むコーディングプロンプトが漏えいします)。
route_toは候補のいずれかでなければなりません。idに使用できるのは[A-Za-z0-9._-]のみです。 デフォルトはrule-1、rule-2、…です。- 「その他すべて」のケースに対するルールは不要です。それは
default_modelで処理します。
一致条件 — all(AND)、any(OR)、not を組み合わせます。各リーフオブジェクトには
条件を 1 つだけ指定します。ネストも可能です。
| リーフ | 例 | 注記 |
|---|---|---|
keywords_any / keywords_all | { "keywords_any": ["SSN", "Email"] } | 大文字と小文字を区別しない部分文字列です。"hi" は "this"、"shipping"、"high" などの内部でも一致します。単語境界まで厳密に判定する必要がある場合は、\b...\b を使った regex を使用してください |
regex | { "regex": "\\b\\d{3}-?\\d{2}-?\\d{4}\\b" } | ECMAScript 形式 |
min_chars / max_chars | { "min_chars": 4000 } | 入力の長さ。UTF-8 バイト数で、0 以上の整数です |
has_tools / has_images | { "has_images": true } | ブール値です |
classifier | { "classifier": "clf-1", "label": "PII", "min_score": 0.5 } | 範囲テストです。min_score/max_score は [0,1] の範囲です。min_score のデフォルトは 0.5 です。分類器に default_label がある場合に限り、label を省略できます |
metadata | { "metadata": { "key": "consent", "equals": "denied" } } | equals/any/exists のいずれか 1 つを指定します。注:デスクトップ UI ではまだ編集できません。ユーザーがメタデータによるルーティングを求めた場合にのみ使用してください |
ステップ 7 - コンポーネント
components = candidates すべて + 各分類器の model + router.model(モード A)の
和集合です。重複を除き、順序は維持してください。ここで宣言されていないモデルを参照すると、パーサーに拒否されます。
ステップ 8 - curl コマンドを検証して出力する
この2つの操作は、必須の1つのステップです。途中で止めないでください。
8a. ユーザーに何かを提示する前に、オフラインバリデーターを実行します。
python scripts/validate.py router.json # Windows
python3 scripts/validate.py router.json # macOS/Linux
エラーがなければ、"ready": true を返して終了コード 0 で終了します。エラーが報告された場合は、JSON を修正して再実行してください。このチェックに失敗するポリシーは提示しないでください。
**8b. 検証に合格したら、直ちに以下の3つの curl コマンドをプレーンテキストで出力し、ユーザーがコピーして実行できるようにします。**これは必須です。ポリシーから <model-id> と <model_name> を埋め、最初のルールに一致する短い <test prompt> を指定します。これらを Bash やツールで実行しないでください。テキストとしてのみ出力してください。
バリデーターの ready: true が意味するのは、JSON がスキーマに適合していることだけです。対象ホストにこれらのモデルが存在するかどうかは示しません。#2 を実行する前に、候補モデル/分類モデルごとに #1 を実行してください。そうしないと、検証に合格したばかりのポリシーでも /pull が 400 を返します。
# 1. Check a model exists before registering
curl http://localhost:13305/api/v1/models/<model-id>
# 2. Register the policy (idempotent - re-POST to update)
curl -X POST http://localhost:13305/api/v1/pull \
-H "Content-Type: application/json" --data-binary @router.json
# 3. Route a request and inspect the decision (-i prints response headers)
curl -i -X POST http://localhost:13305/api/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "<model_name>", "route_trace": true,
"messages": [{"role": "user", "content": "<test prompt>"}]}'
x-lemonade-route レスポンスヘッダーには、一致したルール ID(または default)が含まれます。"route_trace": true の場合、レスポンス本文にも x_lemonade_route が含まれます。形式は { route_to, matched_rule, default_used, outputs, trace[] } です。
ステップ 9 - ユーザー自身でルーティングを確認する手順を出力する
**このステップは必須であり、任意のフォローアップではありません。**検証に合格し、問題なく登録されたポリシーでも、すべてのリクエストが誤ったモデルに送られたり、サーバーがどちらの場合もエラーなしで HTTP 200 を返しながら、スコアが常に 0.0 になったりする可能性があります。どちらの問題も JSON やステップ 8a のバリデーターからは確認できません。このスキルの設計上、ライブサーバーを自分で呼び出すことはありません。代わりに、ユーザーが実行して結果を確認できるよう、以下をテキストとして出力してください。
テスト用の curl コマンドを2つ出力します(どちらもステップ 8b のコマンド #3 を調整します)。1つは特定のルール(またはモード A のインテント)に明確に一致する表現にし、もう1つは何にも一致せず default_model に送られる表現にします。その直後に、以下の確認手順を出力してください。
- HTTP ステータスだけでなく、各レスポンスの
x_lemonade_routeを確認するようユーザーに伝えてください。 - 根拠ではなく
default_usedを確認してください。"default_used": trueと"matched_rule": ""の組み合わせがフォールバックのサインです。rationaleが空であることだけでは、フォールバックを示しません。最初の候補以外へのルーティングが成功した場合にも、よく空になります。 - **分類器に基づくルールでは、
trace[]内の条件ごとのscoreを確認してください。**そのラベルに明確に一致するよう設計されたリクエストでスコアが0.0のままなら、入力が一致しなかったのではなく、宣言されたlabelsエントリがモデルの実際の出力カテゴリと一致していません(ステップ 5)。 - どちらかの確認に失敗した場合、モード A の誤ルーティングには、より高性能な
router.modelを指定すること、分類器の不一致にはlabelsエントリを修正することが解決策です。結果を報告してもらい、それをもとにポリシーを修正して再検証できるよう、ユーザーに伝えてください。
デフォルトの概要
| フィールド | ユーザーが指定しなかった場合のデフォルト |
|---|---|
model_name | user.<default-candidate-slug>-Router(この会話で前に使用した名前は再利用しない) |
default_model | 「小型/ローカル/安全」な候補。それがなければ最初に言及された候補 |
| mode | 具体的なシグナルが指定されている場合は rules、そうでなければ LLM-as-router |
classifier id / rule id | clf-N / rule-N |
on_error | match_false |
default_label | 最初のラベル/概念 |
min_score | 0.5 |
outputs | 省略 |
| router prompt | インテントのみ。返答形式の指示は含めない(ステップ 4) |
router.model(モード A) | 最も高性能な候補。最も安価な候補ではない(ステップ 4) |
NL → JSON の例は examples.md にあります。完全なスキーマ、パーサーのエラー一覧、およびモデルの性能表は reference.md にあります。オフラインバリデーターは scripts/validate.py です(実行してください。ステップ 8 を参照)。