mcpField
L
lemonade-router-builder

lemonade-router-builder

github.com/amd开发470848 次安装2 天前更新
代码仓库

关于 lemonade-router-builder

此技能将自然语言描述的请求路由需求转换为 `collection.router` 策略 JSON,并通过离线验证器检查,面向编写 Lemonade 路由策略的用户。它只生成和验证文件,不会注册策略或执行请求。

主要功能

  1. 01生成路由策略 JSON
  2. 02选择 LLM 或规则路由
  3. 03支持分类器和匹配条件
  4. 04离线验证 JSON
  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 文档;生成或验证策略无需硬件。
  • PATH 中有 Python(任意 3.x 版本)——用于运行随附的离线验证器(scripts/validate.py)。无需安装额外的软件包。

路由器会为每个请求选择一个候选模型。编写策略有两种模式,首先要决定使用哪一种:

模式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. 开头。如果用户给出了名称,则根据该名称生成 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_nameuser.<default-candidate-slug>-Router(不得重复使用本次对话中此前用过的名称)
default_model“small/local/safe”候选模型;否则使用最先提到的模型
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(Mode A)能力最强的候选模型,而非最便宜的模型(步骤 4)

NL → JSON 示例对位于 examples.md;完整架构、解析器错误矩阵和模型能力表位于 reference.md;离线验证器为 scripts/validate.py(运行它——参见步骤 8)。

评论

支持 Markdown