lemonade-router-builder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Lemonade Router Config Generator

Lemonade Router 配置生成器

Generate a
collection.router
policy JSON
from a plain-English description of how requests should be routed. The skill produces and validates the JSON only - it does not call the live server, register the policy, or run requests through it. The JSON is accepted by the strict server-side parser on the first try and stays editable in the desktop app's Hybrid Router editor.
根据请求路由方式的自然语言描述,生成**
collection.router
策略JSON**。该技能仅生成并验证JSON,不调用实时服务器、注册策略或通过它运行请求。生成的JSON可直接通过严格的服务器端解析器,且可在桌面应用的混合路由器编辑器中编辑。

Prerequisites

前提条件

  • Lemonade Server v10.1.0+ running locally (
    lemonade server start
    ). Required only to register and test the generated policy - the skill itself (JSON generation + offline validation) works without a live server.
  • No GPU or ROCm dependency for authoring. The router policy is a JSON document; no hardware is needed to generate or validate it.
  • Python (any 3.x) in PATH - used by the bundled offline validator (
    scripts/validate.py
    ). No extra packages required.
The router picks one candidate model per request. Two authoring modes exist, and choosing the right one is the first decision:
ModeJSON shapeWhen
LLM-as-router
routing.router
block
The user describes intent only by meaning ("sensitive", "hard questions", "creative writing") with no concrete signals. A small LLM reads each prompt and picks the candidate.
Rules
routing.rules
(+ optional
routing.classifiers
)
The user names any concrete signal: keywords, regex, length, tools, images, metadata, PII/topic classifiers, thresholds, "first match", fallback logic. Deterministic, no extra LLM call for simple conditions.
routing.router
is mutually exclusive with
routing.rules
and
routing.classifiers
- never emit both.
  • Lemonade Server v10.1.0+ 本地运行(
    lemonade server start
    )。仅在注册和测试生成的策略时需要——技能本身(JSON生成+离线验证)无需实时服务器即可运行。
  • 无GPU或ROCm依赖。路由策略是一个JSON文档,生成或验证它不需要硬件。
  • Python(任意3.x版本)已加入PATH——用于捆绑的离线验证器(
    scripts/validate.py
    )。无需额外安装包。
路由器会为每个请求选择一个候选模型。存在两种创作模式,选择合适的模式是第一步:
模式JSON结构适用场景
LLM-as-router
routing.router
用户仅通过语义描述意图(如“敏感内容”、“难题”、“创意写作”),无具体信号。小型LLM读取每个提示并选择候选模型。
规则模式
routing.rules
(可选
routing.classifiers
用户指定任何具体信号:关键词、正则表达式、长度、工具、图片、元数据、PII/主题分类器、阈值、“首次匹配”、回退逻辑。确定性规则,简单条件无需额外调用LLM。
routing.router
routing.rules
routing.classifiers
互斥——绝不能同时输出两者。

Step 1 - Extract from the user's words

步骤1 - 提取用户需求

  • Candidates: the models that may answer requests. Verbatim model names (e.g.
    Gemma-3-4b-it-GGUF
    ). If the user names none, ask - never invent model names.
    lemonade list
    or
    GET /api/v1/models
    shows what's available.
  • Default / fallback: which candidate gets everything that matches nothing. If unstated, use the model the user framed as "local", "small", or "safe"; otherwise the first candidate mentioned.
  • Signals: every condition mentioned (keywords, patterns, length, images, tools, topics, PII, safety) and which model each one routes to.
  • Classifier models: models named for detection rather than answering (BERT-style encoders, embedding models, an LLM used as judge).
  • 候选模型:可处理请求的模型。需使用准确的模型名称(如
    Gemma-3-4b-it-GGUF
    )。若用户未指定模型名称,需询问——切勿自行编造。
    lemonade list
    GET /api/v1/models
    可查看可用模型。
  • 默认/回退模型:匹配不到任何规则时的候选模型。若未指定,使用用户描述为“本地”、“小型”或“安全”的模型;否则使用第一个提及的候选模型。
  • 触发信号:用户提到的所有条件(关键词、模式、长度、图片、工具、主题、PII、安全规则)及对应的路由模型。
  • 分类器模型:用于检测而非回答的模型(如BERT风格编码器、嵌入模型、用作判断的LLM)。

Step 2 - Scaffold

步骤2 - 搭建基础框架

Always exactly this envelope (the parser rejects unknown or missing keys):
json
{
  "version": "1",
  "model_name": "user.MyHybridRouter",
  "recipe": "collection.router",
  "components": [],
  "routing": { }
}
  • version
    is the literal string
    "1"
    .
  • model_name
    must start with
    user.
    ; slug from the user's description if they gave a name (
    user.<Name>
    using only
    [A-Za-z0-9._-]
    ). If they didn't name it, derive one from context instead of a fixed literal - e.g.
    user.<slug-of-default-candidate>-Router
    - so two different policies don't collide by default.
    /pull
    is idempotent per
    model_name
    : registering a second policy under the same name silently overwrites the first.
    If this conversation already produced an unnamed router, don't reuse the same derived name for the next one - ask, or pick a visibly different name.
必须使用以下固定结构(解析器会拒绝未知或缺失的键):
json
{
  "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
    实现幂等:使用相同名称注册第二个策略会静默覆盖第一个。**若本次对话已生成过未命名的路由器,请勿为下一个策略重复使用相同的派生名称——需询问用户,或选择明显不同的名称。

Step 3 - Candidates and default

步骤3 - 候选模型与默认模型

json
"candidates": ["<answering models>"],
"default_model": "<one of candidates>"
default_model
MUST be listed in
candidates
. Candidates should be chat-capable LLMs - not embedding, classification, or image models.
json
"candidates": ["<answering models>"],
"default_model": "<one of candidates>"
default_model
必须在
candidates
列表中。候选模型应为支持对话的LLM——不能是嵌入、分类或图像模型。

Step 4 - Mode A: LLM-as-router

步骤4 - 模式A:LLM-as-router

json
"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
    defaults to the smallest candidate (it may be a candidate; it also works as a separate small model).
  • Write intent only - never specify a reply format and never use imperative "Pick X" phrasing. The engine unconditionally appends its own contract after your prompt: it lists the candidate names and demands a strict JSON reply
    {"model": "<name>", "rationale": "<one sentence>"}
    , then falls back to
    default_model
    on any deviation. A prompt that says "reply with ONLY the model name", "Pick Model-A", "respond with the model name", or similar is wrong about the wire format and causes weaker judge models to reply with a bare string that fails to parse - silently falling back to
    default_model
    on every request with no visible error.
    Bad (do not write):
    "Pick Qwen3.5-9B-GGUF for sensitive queries, pick Qwen3.5-9B-NoThinking for everything else."
    Good:
    "Route to Qwen3.5-9B-GGUF when the request appears sensitive or contains personal information. Route to Qwen3.5-9B-NoThinking for all other requests."
    Only describe when each candidate is appropriate. Never say "pick", "output", "reply with", or "respond with".
  • NEVER emit
    rules
    or
    classifiers
    in this mode.
    The
    routing
    object in Mode A must contain exactly:
    candidates
    ,
    default_model
    , and
    router
    . Adding
    rules
    or
    classifiers
    alongside
    router
    is a schema violation that the server parser rejects. If you catch yourself writing both, stop and remove
    rules
    /
    classifiers
    entirely.
json
"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
    默认使用最小的候选模型(它可以是候选模型之一,也可以是单独的小型模型)。
  • **仅描述意图——绝不要指定回复格式,也不要使用命令式的“选择X”表述。**引擎会在你的提示后无条件追加自身的约定:列出候选模型名称,并要求返回严格的JSON格式
    {"model": "<name>", "rationale": "<one sentence>"}
    ,若出现任何偏差则回退到
    default_model
    。如果提示中包含“仅回复模型名称”、“选择Model-A”、“返回模型名称”等类似内容,会导致性能较弱的判断模型返回纯字符串,无法被解析——最终所有请求都会静默回退到
    default_model
    ,且无可见错误。
    错误示例(请勿使用):
    "Pick Qwen3.5-9B-GGUF for sensitive queries, pick Qwen3.5-9B-NoThinking for everything else."
    正确示例
    "Route to Qwen3.5-9B-GGUF when the request appears sensitive or contains personal information. Route to Qwen3.5-9B-NoThinking for all other requests."
    仅描述每个候选模型的适用场景。绝不要使用“选择”、“输出”、“回复”等词汇。
  • **此模式下绝不能输出
    rules
    classifiers
    。**模式A中的
    routing
    对象必须仅包含:
    candidates
    default_model
    router
    。在
    router
    旁添加
    rules
    classifiers
    属于 schema 违规,会被服务器解析器拒绝。若发现自己同时编写了两者,请立即停止并删除
    rules
    /
    classifiers

Step 5 - Mode B: classifiers

步骤5 - 模式B:分类器

Only declare classifiers the rules actually reference. Three types:
json
{ "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" }
Hard constraints (parser-enforced - see
reference.md
for the full matrix):
  • classifier
    type: model should be a text-classification model (an
    onnxruntime
    encoder like
    Bert-Phishing-ONNX
    );
    labels
    must match the model's actual output labels. A chat LLM here is legal (LLM-as-classifier via chat) but prefer
    type: "llm"
    for that - it is explicit and prompted.
  • semantic_similarity
    :
    reference_phrases
    is
    {concept: [phrases...]}
    , at least one concept, each with at least one phrase. Concept names ARE the labels - a
    labels
    key is rejected for this type. Model must be an embedding model. Give 3–5 varied phrases per concept when inventing them.
  • llm
    :
    prompt
    AND non-empty
    labels
    are both required. Write intent only - never tell the model how to format its reply. The engine appends its own
    {"model": "<chosen_label>", "rationale": "..."}
    contract after your prompt (the same contract as
    routing.router
    ). An authored line like "Reply with exactly one label: SAFE or RISKY" causes weaker models to output bare
    SAFE
    , which the parser rejects - the score comes back empty and the rule silently never fires. Describe what makes a request belong to each label; leave the reply format to the engine.
  • default_label
    , when present, must be one of the labels/concepts.
  • Defaults when unspecified:
    id
    =
    clf-1
    ,
    clf-2
    , …;
    on_error
    =
    "match_false"
    (fail-open: a broken classifier doesn't match, so requests fall through - use
    "match_true"
    only when the user wants fail-closed safety);
    default_label
    = the first label.
仅声明规则实际引用的分类器。共有三种类型:
json
{ "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
    必须与模型的实际输出标签匹配。此处使用对话LLM是合法的(通过对话实现LLM-as-classifier),但更推荐使用
    type: "llm"
    ——该类型更明确且支持自定义提示。
  • semantic_similarity
    reference_phrases
    格式为
    {concept: [phrases...]}
    ,至少包含一个概念,每个概念至少包含一个短语。概念名称即为标签——此类型不允许使用
    labels
    键。模型必须是嵌入模型。自行编写时每个概念提供3-5个不同的短语。
  • llm
    :必须同时提供
    prompt
    和非空的
    labels
    。**仅描述意图——绝不要告诉模型如何格式化回复。**引擎会在你的提示后追加自身的
    {"model": "<chosen_label>", "rationale": "..."}
    约定(与
    routing.router
    使用相同的约定)。若编写的提示包含“仅回复一个标签:SAFE或RISKY”,会导致性能较弱的模型输出纯
    SAFE
    ,无法被解析——最终得分为空,规则永远不会触发。只需描述每个标签对应的请求特征,回复格式交由引擎处理。
  • 若存在
    default_label
    ,必须是标签/概念之一。
  • 未指定时的默认值:
    id
    =
    clf-1
    ,
    clf-2
    , …;
    on_error
    =
    "match_false"
    (故障开放:分类器故障时不匹配,请求会继续流转——仅当用户需要故障关闭的安全机制时使用
    "match_true"
    );
    default_label
    = 第一个标签。

Step 6 - Mode B: rules

步骤6 - 模式B:规则

json
"rules": [
  { "id": "rule-1", "match": { ... }, "route_to": "<candidate>",
    "outputs": { "reason": "<optional free-form>" } }
]
  • Order matters - first match wins. Put the most specific / privacy-critical rules first (a "sensitive stays local" rule must precede a "code goes to the big model" rule, or coding prompts with PII leak).
  • route_to
    MUST be a candidate.
    id
    uses only
    [A-Za-z0-9._-]
    ; default
    rule-1
    ,
    rule-2
    , ….
  • No rule for the "everything else" case - that is
    default_model
    .
Match conditions - combine with
all
(AND),
any
(OR),
not
; one condition per leaf object; nesting is allowed:
LeafExampleNotes
keywords_any
/
keywords_all
{ "keywords_any": ["SSN", "Email"] }
case-insensitive substring -
"hi"
matches inside
"this"
,
"shipping"
,
"high"
, etc. Use
regex
with
\b...\b
when word-boundary precision is needed
regex
{ "regex": "\\b\\d{3}-?\\d{2}-?\\d{4}\\b" }
ECMAScript flavor
min_chars
/
max_chars
{ "min_chars": 4000 }
input length, UTF-8 bytes, non-negative integer
has_tools
/
has_images
{ "has_images": true }
booleans
classifier
{ "classifier": "clf-1", "label": "PII", "min_score": 0.5 }
band test;
min_score
/
max_score
in [0,1]; default
min_score
0.5; omit
label
only if the classifier has
default_label
metadata
{ "metadata": { "key": "consent", "equals": "denied" } }
exactly one of
equals
/
any
/
exists
; note: not editable in the desktop UI yet - use only when the user asks for metadata routing
json
"rules": [
  { "id": "rule-1", "match": { ... }, "route_to": "<candidate>",
    "outputs": { "reason": "<optional free-form>" } }
]
  • **顺序至关重要——首次匹配生效。**将最具体/隐私敏感的规则放在前面(例如“敏感内容保留在本地”的规则必须优先于“代码请求路由到大型模型”的规则,否则包含PII的代码提示会泄露)。
  • route_to
    必须是候选模型之一。
    id
    仅允许使用
    [A-Za-z0-9._-]
    字符;默认值为
    rule-1
    ,
    rule-2
    , …。
  • 无需为“其他所有情况”编写规则——这是
    default_model
    的作用。
匹配条件——可通过
all
(逻辑与)、
any
(逻辑或)、
not
(逻辑非)组合;每个叶子对象对应一个条件;允许嵌套:
条件类型示例说明
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字节计算,非负整数
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
中的一个;注意:目前桌面UI不支持编辑此规则——仅当用户明确要求元数据路由时使用

Step 7 - Components

步骤7 - 组件

components
= union of: all
candidates
+ every classifier
model
+ the
router.model
(Mode A). Deduplicate, keep order stable. The parser rejects any referenced model that is not declared here.
components
= 所有
candidates
+ 每个分类器的
model
+
router.model
(模式A)的并集。需去重,保持顺序稳定。解析器会拒绝任何未在此处声明的引用模型。

Step 8 - Validate and output curl commands

步骤8 - 验证并输出curl命令

These two actions are a single mandatory step. Do not stop between them.
8a. Run the offline validator before presenting anything to the user:
bash
python scripts/validate.py router.json    # Windows
python3 scripts/validate.py router.json   # macOS/Linux
It exits 0 with
"ready": true
when there are no errors. If it reports errors, fix the JSON and re-run. Do not present a policy that fails this check.
8b. Immediately after validation passes, print these three curl commands as plain text for the user to copy and run. This is not optional. Fill in
<model-id>
and
<model_name>
from the policy, and a short
<test prompt>
that should hit the first rule. Do not execute these with Bash or any tool — print them as text only.
bash
undefined
这两个操作是必须的单一步骤,请勿中途停止。
8a. 运行离线验证器,然后再向用户展示结果:
bash
python scripts/validate.py router.json    # Windows
python3 scripts/validate.py router.json   # macOS/Linux
验证通过时会返回状态码0及
"ready": true
。若报告错误,修复JSON后重新运行。请勿向用户展示未通过此检查的策略。
8b. 验证通过后立即输出以下三条curl命令,以纯文本形式供用户复制运行。此步骤为必填项。将策略中的
<model-id>
<model_name>
替换为实际值,并提供一个应触发第一条规则的简短
<test prompt>
。请勿通过Bash或其他工具执行这些命令——仅以文本形式打印。
bash
undefined

1. Check a model exists before registering

1. 注册前检查模型是否存在

2. Register the policy (idempotent - re-POST to update)

2. 注册策略(幂等——重新POST即可更新)

curl -X POST http://localhost:13305/api/v1/pull
-H "Content-Type: application/json" --data-binary @router.json
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

3. 路由请求并查看决策过程

curl -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>"}]}'

The `x-lemonade-route` response header carries the matched rule id (or
`default`). With `"route_trace": true` the body also carries
`x_lemonade_route`: `{ route_to, matched_rule, default_used, outputs,
trace[] }` - useful for verifying each rule fires as expected.
curl -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[] }`——可用于验证每条规则是否按预期触发。

Defaults summary

默认值汇总

FieldDefault when the user doesn't say
model_name
user.<default-candidate-slug>-Router
(never reuse a name already used earlier in this conversation)
default_model
the "small/local/safe" candidate, else first mentioned
moderules if any concrete signal is named, else LLM-as-router
classifier
id
/ rule
id
clf-N
/
rule-N
on_error
match_false
default_label
first label / concept
min_score
0.5
outputs
omit
router promptintent only - no reply-format instruction (Step 4)
Worked NL → JSON pairs live in
examples.md
; the full schema, parser error matrix, and model-capability table live in
reference.md
; the offline validator is
scripts/validate.py
(run it - see Step 8).
字段用户未指定时的默认值
model_name
user.<default-candidate-slug>-Router
(请勿重复使用本次对话中已用过的名称)
default_model
“小型/本地/安全”的候选模型,否则为第一个提及的模型
模式若指定了具体信号则使用规则模式,否则使用LLM-as-router
分类器
id
/ 规则
id
clf-N
/
rule-N
on_error
match_false
default_label
第一个标签/概念
min_score
0.5
outputs
省略
路由器提示仅描述意图——不包含回复格式说明(步骤4)
自然语言转JSON的示例可参考
examples.md
;完整schema、解析器错误矩阵和模型能力表可参考
reference.md
;离线验证器为
scripts/validate.py
(请运行它——见步骤8)。