lemonade-router-builder
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLemonade Router Config Generator
Lemonade Router 配置生成器
Generate a 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可直接通过严格的服务器端解析器,且可在桌面应用的混合路由器编辑器中编辑。
collection.routerPrerequisites
前提条件
- Lemonade Server v10.1.0+ running locally (). Required only to register and test the generated policy - the skill itself (JSON generation + offline validation) works without a live server.
lemonade server start - 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
(). No extra packages required.
scripts/validate.py
The router picks one candidate model per request. Two authoring modes
exist, and choosing the right one is the first decision:
| Mode | JSON shape | When |
|---|---|---|
| LLM-as-router | | 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 | | 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.routerrouting.rulesrouting.classifiers- Lemonade Server v10.1.0+ 本地运行()。仅在注册和测试生成的策略时需要——技能本身(JSON生成+离线验证)无需实时服务器即可运行。
lemonade server start - 无GPU或ROCm依赖。路由策略是一个JSON文档,生成或验证它不需要硬件。
- Python(任意3.x版本)已加入PATH——用于捆绑的离线验证器()。无需额外安装包。
scripts/validate.py
路由器会为每个请求选择一个候选模型。存在两种创作模式,选择合适的模式是第一步:
| 模式 | JSON结构 | 适用场景 |
|---|---|---|
| LLM-as-router | | 用户仅通过语义描述意图(如“敏感内容”、“难题”、“创意写作”),无具体信号。小型LLM读取每个提示并选择候选模型。 |
| 规则模式 | | 用户指定任何具体信号:关键词、正则表达式、长度、工具、图片、元数据、PII/主题分类器、阈值、“首次匹配”、回退逻辑。确定性规则,简单条件无需额外调用LLM。 |
routing.routerrouting.rulesrouting.classifiersStep 1 - Extract from the user's words
步骤1 - 提取用户需求
- Candidates: the models that may answer requests. Verbatim model names
(e.g. ). If the user names none, ask - never invent model names.
Gemma-3-4b-it-GGUForlemonade listshows what's available.GET /api/v1/models - 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": { }
}- is the literal string
version."1" - must start with
model_name; slug from the user's description if they gave a name (user.using onlyuser.<Name>). If they didn't name it, derive one from context instead of a fixed literal - e.g.[A-Za-z0-9._-]- so two different policies don't collide by default.user.<slug-of-default-candidate>-Routeris idempotent per/pull: 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.model_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_modelcandidatesjson
"candidates": ["<answering models>"],
"default_model": "<one of candidates>"default_modelcandidatesStep 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>."
}-
defaults to the smallest candidate (it may be a candidate; it also works as a separate small model).
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, then falls back to
{"model": "<name>", "rationale": "<one sentence>"}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 todefault_modelon every request with no visible error.default_modelBad (do not write):Good:"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."Only describe when each candidate is appropriate. Never say "pick", "output", "reply with", or "respond with". -
NEVER emitor
rulesin this mode. Theclassifiersobject in Mode A must contain exactly:routing,candidates, anddefault_model. Addingrouterorrulesalongsideclassifiersis a schema violation that the server parser rejects. If you catch yourself writing both, stop and removerouter/rulesentirely.classifiers
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>"}。如果提示中包含“仅回复模型名称”、“选择Model-A”、“返回模型名称”等类似内容,会导致性能较弱的判断模型返回纯字符串,无法被解析——最终所有请求都会静默回退到default_model,且无可见错误。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。**模式A中的classifiers对象必须仅包含:routing、candidates和default_model。在router旁添加router或rules属于 schema 违规,会被服务器解析器拒绝。若发现自己同时编写了两者,请立即停止并删除classifiers/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 for the full matrix):
reference.md- type: model should be a text-classification model (an
classifierencoder likeonnxruntime);Bert-Phishing-ONNXmust match the model's actual output labels. A chat LLM here is legal (LLM-as-classifier via chat) but preferlabelsfor that - it is explicit and prompted.type: "llm" - :
semantic_similarityisreference_phrases, at least one concept, each with at least one phrase. Concept names ARE the labels - a{concept: [phrases...]}key is rejected for this type. Model must be an embedding model. Give 3–5 varied phrases per concept when inventing them.labels - :
llmAND non-emptypromptare both required. Write intent only - never tell the model how to format its reply. The engine appends its ownlabelscontract after your prompt (the same contract as{"model": "<chosen_label>", "rationale": "..."}). An authored line like "Reply with exactly one label: SAFE or RISKY" causes weaker models to output barerouting.router, 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.SAFE - , when present, must be one of the labels/concepts.
default_label - Defaults when unspecified: =
id,clf-1, …;clf-2=on_error(fail-open: a broken classifier doesn't match, so requests fall through - use"match_false"only when the user wants fail-closed safety);"match_true"= the first label.default_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必须与模型的实际输出标签匹配。此处使用对话LLM是合法的(通过对话实现LLM-as-classifier),但更推荐使用labels——该类型更明确且支持自定义提示。type: "llm" - :
semantic_similarity格式为reference_phrases,至少包含一个概念,每个概念至少包含一个短语。概念名称即为标签——此类型不允许使用{concept: [phrases...]}键。模型必须是嵌入模型。自行编写时每个概念提供3-5个不同的短语。labels - :必须同时提供
llm和非空的prompt。**仅描述意图——绝不要告诉模型如何格式化回复。**引擎会在你的提示后追加自身的labels约定(与{"model": "<chosen_label>", "rationale": "..."}使用相同的约定)。若编写的提示包含“仅回复一个标签:SAFE或RISKY”,会导致性能较弱的模型输出纯routing.router,无法被解析——最终得分为空,规则永远不会触发。只需描述每个标签对应的请求特征,回复格式交由引擎处理。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).
- MUST be a candidate.
route_touses onlyid; default[A-Za-z0-9._-],rule-1, ….rule-2 - No rule for the "everything else" case - that is .
default_model
Match conditions - combine with (AND), (OR), ; one
condition per leaf object; nesting is allowed:
allanynot| Leaf | Example | Notes |
|---|---|---|
| | case-insensitive substring - |
| | ECMAScript flavor |
| | input length, UTF-8 bytes, non-negative integer |
| | booleans |
| | band test; |
| | exactly one of |
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
匹配条件——可通过 (逻辑与)、(逻辑或)、(逻辑非)组合;每个叶子对象对应一个条件;允许嵌套:
allanynot| 条件类型 | 示例 | 说明 |
|---|---|---|
| | 不区分大小写的子字符串匹配—— |
| | ECMAScript 语法 |
| | 输入长度,按UTF-8字节计算,非负整数 |
| | 布尔值 |
| | 区间测试; |
| | 只能使用 |
Step 7 - Components
步骤7 - 组件
componentscandidatesmodelrouter.modelcomponentscandidatesmodelrouter.modelStep 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/LinuxIt exits 0 with when there are no errors. If it reports
errors, fix the JSON and re-run. Do not present a policy that fails this
check.
"ready": true8b. 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
and from the policy, and a short
that should hit the first rule. Do not execute these with Bash or any tool —
print them as text only.
<model-id><model_name><test prompt>bash
undefined这两个操作是必须的单一步骤,请勿中途停止。
8a. 运行离线验证器,然后再向用户展示结果:
bash
python scripts/validate.py router.json # Windows
python3 scripts/validate.py router.json # macOS/Linux验证通过时会返回状态码0及 。若报告错误,修复JSON后重新运行。请勿向用户展示未通过此检查的策略。
"ready": true8b. 验证通过后立即输出以下三条curl命令,以纯文本形式供用户复制运行。此步骤为必填项。将策略中的 和 替换为实际值,并提供一个应触发第一条规则的简短 。请勿通过Bash或其他工具执行这些命令——仅以文本形式打印。
<model-id><model_name><test prompt>bash
undefined1. Check a model exists before registering
1. 注册前检查模型是否存在
curl http://localhost:13305/api/v1/models/<model-id>
curl http://localhost:13305/api/v1/models/<model-id>
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
-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
-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>"}]}'
-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>"}]}'
-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
默认值汇总
| Field | Default when the user doesn't say |
|---|---|
| |
| the "small/local/safe" candidate, else first mentioned |
| mode | rules if any concrete signal is named, else LLM-as-router |
classifier | |
| |
| first label / concept |
| |
| omit |
| router prompt | intent only - no reply-format instruction (Step 4) |
Worked NL → JSON pairs live in ; the full schema, parser error
matrix, and model-capability table live in ; the offline
validator is (run it - see Step 8).
examples.mdreference.mdscripts/validate.py| 字段 | 用户未指定时的默认值 |
|---|---|
| |
| “小型/本地/安全”的候选模型,否则为第一个提及的模型 |
| 模式 | 若指定了具体信号则使用规则模式,否则使用LLM-as-router |
分类器 | |
| |
| 第一个标签/概念 |
| |
| 省略 |
| 路由器提示 | 仅描述意图——不包含回复格式说明(步骤4) |
自然语言转JSON的示例可参考 ;完整schema、解析器错误矩阵和模型能力表可参考 ;离线验证器为 (请运行它——见步骤8)。
examples.mdreference.mdscripts/validate.py