关于 lemonade-router-builder
此技能将自然语言描述的请求路由需求转换为 `collection.router` 策略 JSON,并通过离线验证器检查,面向编写 Lemonade 路由策略的用户。它只生成和验证文件,不会注册策略或执行请求。
主要功能
- 01生成路由策略 JSON
- 02选择 LLM 或规则路由
- 03支持分类器和匹配条件
- 04离线验证 JSON
- 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 文档;生成或验证策略无需硬件。
- PATH 中有 Python(任意 3.x 版本)——用于运行随附的离线验证器(
scripts/validate.py)。无需安装额外的软件包。
路由器会为每个请求选择一个候选模型。编写策略有两种模式,首先要决定使用哪一种:
| 模式 | 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.开头。如果用户给出了名称,则根据该名称生成 slug(user.<Name>,只能使用[A-Za-z0-9._-])。如果用户没有提供名称,应根据上下文生成名称,而不是使用固定字面值——例如user.<slug-of-default-candidate>-Router——这样不同策略默认不会发生冲突。**/pull对每个model_name都是幂等的:在同一名称下注册第二个策略会静默覆盖第一个。**如果本次对话已经生成过一个未命名路由器,不要对下一个路由器重复使用相同的派生名称——请先询问,或选择一个明显不同的名称。
第 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:分类器
仅声明规则实际引用的分类器。共有三种类型:
{ "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)。在此处使用聊天大模型也是合法的(通过聊天将大模型用作分类器),但建议为此使用type: "llm"——这种写法明确且会提供提示词。semantic_similarity:reference_phrases的格式为{concept: [phrases...]},至少包含一个概念,且每个概念至少有一个短语。概念名称就是标签——此类型不接受labels键。模型必须是嵌入模型。新建概念时,每个概念提供 3–5 个不同的短语。llm:必须同时提供prompt和非空的labels。只描述意图——绝不要告诉模型如何格式化回复。 引擎会在你的提示词后追加自己的{"model": "<chosen_label>", "rationale": "..."}约定(与routing.router使用的约定相同)。如果你写了类似“只回复一个标签:SAFE 或 RISKY”的内容,较弱的模型可能会只输出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(与)、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 中必须且只能提供一个;注意:桌面界面暂不支持编辑此项——仅在用户要求按元数据路由时使用 |
步骤 7 - 组件
components = 以下内容的并集:所有 candidates + 每个分类器的 model + router.model(模式 A)。去重并保持顺序稳定。解析器会拒绝任何此处未声明但被引用的模型。
步骤 8 - 验证并输出 curl 命令
这两个操作是一个必需步骤,不要中途停止。
8a. 在向用户展示任何内容之前,运行离线验证器:
python scripts/validate.py router.json # Windows
python3 scripts/validate.py router.json # macOS/Linux
没有错误时,验证器会以 "ready": true 退出,退出代码为 0。如果报告错误,请修正 JSON 并重新运行。不要提供未通过此检查的策略。
**8b. 验证通过后,立即以纯文本形式打印以下三条 curl 命令,供用户复制并运行。**这不是可选项。根据策略填写 <model-id> 和 <model_name>,并填写一个应触发第一条规则的简短 <test prompt>。不要使用 Bash 或任何工具执行这些命令——只以文本形式打印。
验证器返回 ready: true 只表示 JSON 符合架构要求,并不能说明这些模型是否存在于目标主机上。对于每个候选模型/分类器模型,都要先运行 #1,再运行 #2;否则即使策略刚通过验证,/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 - 打印说明,指导用户自行验证路由
**此步骤是必需的,不是可选的后续操作。**策略即使通过验证并成功注册,仍可能将所有请求发送到错误的模型,或者静默地一直得到 0.0 分数,而服务器两种情况下都会返回 HTTP 200,且不报错——这两种故障都无法从 JSON 或步骤 8a 的验证器中看出。根据此技能的设计,你绝不会自行调用在线服务;相反,应将以下内容打印为文本,以便用户运行并查看结果。
打印两条测试 curl 命令(针对两条命令都改写步骤 8b 的命令 #3):一条措辞要能明确触发某条特定规则(或 Mode A 意图),另一条措辞要确保不会触发任何规则,从而应落到 default_model。然后立即打印以下结果解读说明:
- 告诉用户检查每条响应中的
x_lemonade_route,而不只是 HTTP 状态。 - 检查
default_used,而不是理由。"default_used": true且"matched_rule": ""是回退的标志。仅rationale为空并不能说明发生了回退——成功路由到非首选候选模型时也常会返回空的rationale。 - **对于由分类器支持的规则,请检查
trace[]中每个条件的score。**如果针对明显应匹配该标签而设计的请求,其分数一直为0.0,说明声明的labels条目与模型的实际输出类别不匹配(步骤 5),而不是输入未匹配。 - 如果任一项检查失败,Mode A 路由错误时应使用能力更强的
router.model;分类器不匹配时应修正labels条目。告诉用户反馈结果,以便你修订并重新验证。
默认值摘要
| 字段 | 用户未说明时的默认值 |
|---|---|
model_name | user.<default-candidate-slug>-Router(不得重复使用本次对话中此前用过的名称) |
default_model | “small/local/safe”候选模型;否则使用最先提到的模型 |
| 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(Mode A) | 能力最强的候选模型,而非最便宜的模型(步骤 4) |
NL → JSON 示例对位于 examples.md;完整架构、解析器错误矩阵和模型能力表位于 reference.md;离线验证器为 scripts/validate.py(运行它——参见步骤 8)。