mcpField
L
lemonade-router-builder

lemonade-router-builder

github.com/amd開発470848 回インストール2 日前に更新
リポジトリ

lemonade-router-builder について

自然言語で記述されたリクエスト振り分け要件を `collection.router` ポリシーの JSON に変換し、オフライン検証するスキルです。Lemonade のポリシー作成向けで、ファイルの生成と検証のみを行い、登録やリクエスト実行はしません。

主な機能

  1. 01ルーターポリシー JSON の生成
  2. 02LLM またはルール方式の選択
  3. 03分類器と条件指定への対応
  4. 04JSON のオフライン検証
  5. 05登録・テスト用コマンドの提示

活用シーン

  1. 01キーワードや長さによる振り分け
  2. 02画像やツール要求の候補モデルへの割り当て
  3. 03サーバー未稼働時のポリシー作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/amd/skills --skill lemonade-router-builder

インストール後、Claude Code で/lemonade-router-builderを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.federated.json
examples.md
reference.md
skill-card.md
evals/
evals.json
scripts/
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-routerrouting.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_nameuser.<default-candidate-slug>-Router(この会話で前に使用した名前は再利用しない)
default_model「小型/ローカル/安全」な候補。それがなければ最初に言及された候補
mode具体的なシグナルが指定されている場合は rules、そうでなければ LLM-as-router
classifier id / rule idclf-N / rule-N
on_errormatch_false
default_label最初のラベル/概念
min_score0.5
outputs省略
router promptインテントのみ。返答形式の指示は含めない(ステップ 4)
router.model(モード A)最も高性能な候補。最も安価な候補ではない(ステップ 4)

NL → JSON の例は examples.md にあります。完全なスキーマ、パーサーのエラー一覧、およびモデルの性能表は reference.md にあります。オフラインバリデーターは scripts/validate.py です(実行してください。ステップ 8 を参照)。

コメント