第 10 章

第 10 章:模型路由与 LLM 护栏——Model routing / LLM guardrails

第 10 章:模型路由与 LLM 护栏——Model routing / LLM guardrails

路由和护栏看起来是两件事,其实是一件事的两面:都用一次便宜的判定,去决定一次昂贵的动作该不该发生、以多高的代价发生。路由决定"把请求交给谁",护栏决定"这个动作能不能放行"。本章讲 Jev 怎么把这两件事都收进代码的控制流。

路由的本质:一次便宜的判定,决定一次昂贵的调用

模型路由要解决的矛盾很直白:大模型贵且强,小模型便宜但弱,而真实流量是混合的。一个简单改写和小一段深度推理,用一个价位的模型处理,两边都不划算——难题给小模型等于答错,简单题给大模型等于烧钱。

于是每一层路由都在做同一件事:先用一个极便宜的信号,估出这次请求需要多大的算力,再去调用对应档位的模型。 这个"估价"就是一次判定。它不需要生成任何文本,只需要在一个封闭的选项集(哪一档模型、哪一级 reasoning effort)上给出分布。

这一层判定必须足够便宜,否则路由本身就成了新的成本。这正是它该交给 Jev 的原因:一次决策调用的成本远低于一次生成调用,而路由要在一个请求的入口处执行,量最大、最不能贵。

路由应该是一串渐进式的判定

成熟的路由实现不会把所有请求都丢给模型判。它更像一道漏斗:能确定的一律不花判定成本,只有真正模糊的才进 Jev。

flowchart TD
    IN["请求"] --> R{"确定性规则能定?"}
    R -- 能,如超长/命中缓存/显式指定 --> D["直接定档
零判定成本"] R -- 不能 --> J["Jev 判定
Choice 选档 + Score 定复杂度"] J --> TH{"置信度够吗?"} TH -- 够 --> D2["按判定定档"] TH -- 不够 --> FB["回退到默认档
并记账"]

代码里的样子大致是:

def choose_tier(prompt: str, ctx: dict) -> str:
    # 第一层:确定性的,绝不发判定
    if ctx.get("pinned_model"):
        return ctx["pinned_model"]
    if len(prompt) < 40 and looks_like_continuation(prompt):
        return "small"                      # 一句"继续",不值得判

    # 第二层:只有模糊的才问 Jev
    d = client.systemone(
        state=prompt,
        questions={
            "tier": {"type": "choice", "question": "这个任务该用哪个档位的模型?",
                     "choices": ["small", "medium", "large"]},
            "complexity": {"type": "score", "question": "任务本身的复杂度如何?",
                           "labels": ["低", "中", "高", "极高"]},
        },
        instructions="按任务的推理深度判断,不要被提示词长度带偏。",
    )
    conf = max(d["tier"].probabilities.values())
    if conf < 0.5:
        return "medium"                     # 拿不准就取中间档,避免两头都错
    return d["tier"].choice

这段代码体现了两条工程纪律:能用规则定的,绝不调模型;判不准的,用一个安全的默认档兜底,而不是赌一次。

实例一:decision-router——把"选谁"和"多难"一次问出来

Ruivalim/decision-router 是一个 Claude Code 插件,也提供 Pi 的虚拟模型与 CLI,它用一次 Jev 调用为每个任务选出模型。这次调用同时问两类问题:一个是对候选模型做 Choice,而且把选项顺序正反各问一遍;另一个是一个复杂度 Score,用来设定最低档位。

正反各问一遍,是为了压掉选项顺序带来的偏差——这是决策设计里一个很实在的技巧。而复杂度 Score 单独存在,是为了给 Choice 的结果加一道"下限":即便选出来的档位偏低,复杂度分数也会把它顶到应有的高度。

效果是可量化的:在有 30 条人工标注提示的测试上,光用 Choice 时准确率是 73%,加上双序询问与复杂度 Score 后升到 93%。此外它做了配额感知的过滤,并在置信度低于 0.3 时回退。

实例二:Jevonian——路由结果要稳定,别破坏 prompt cache

xinyao27/jevonian 是一个本地的 OpenAI / Anthropic 兼容代理。当模型名写成 jevonian/auto 时,它会用一次 Jev 调用同时决定模型路由和思考等级,输入是会话状态、配额健康度、各候选模型的能力,以及切换缓存带来的惩罚。

它有两个设计值得记住。第一,确定性代码先行:候选先被代码筛掉一批,被固定的模型、显式的 jevonian/<route> 请求、以及 routing.mode: "off" 会完全跳过 Jev。也就是说,Jev 只处理那部分真正需要判断的请求。第二,minConfidence 的作用不是"接受",而是把低置信的路由记进 ledger——记下服务用的模型和理由,而不是悄悄把它当确定结论用掉。

ruban-24/switchboard 处理的是另一个细节:它把每个 Claude Code 或 Codex 任务匹配到合适的模型与 reasoning effort 之后,在整段对话里保持这个选择不变,为的是保住 prompt cache 的连续性。这提醒我们,路由不只是一个准确率问题——频繁改档会打断缓存、抬高成本,稳定本身就是一种正确。

同样的取舍也出现在 suenot/codex-jev-router(对任务摘要问 Choice 与 Noul,选 Codex 子代理模型与 effort,带置信度阈值和一个兜底模型)和 mejiasd3v/pi-jev-router(为 Pi 编码代理加逐请求路由)里。

顺带一提:路由是一种通用能力

路由并不限于"选模型"。yusukebe/hono-jev-router 把它放到了 Web 框架层:Hono 中间件按语义而不是按 HTTP 方法和路径来分发请求——"这个请求在谈什么"由 Jev 判,走哪条 handler 由代码决定。juspay/neurolink 则更进一步,把它写成了一等公民:它是 Juspay 的 TypeScript 接口,把 decide 与 generate、stream 并列作为基础推理类型,让"判定"和"生成"在同一个抽象层里各就其位。Vercel 的 vercel/eve 引擎则在实验性的 evaluate 路径里,把 Jev 作为默认评估模型(typesafe-ai/jev)——判定能力被直接编进了代理引擎的骨架。

这些例子说明,路由不只是 LLM 网关里的一个中间件,它是任何"分派"动作都能复用的一种决策。

护栏的两端:输入护栏与输出护栏

护栏关心的不是"交给谁",而是"这个动作该不该发生"。它通常有两端:前置的输入护栏和后置的输出护栏。两者都是 Noul 的逐条清单——把要求拆成若干条,每条问一个"是/否"。

flowchart LR
    U["用户输入"] --> P{"输入护栏
Noul 清单"} P -- 命中 --> BLOCK["拦截 / 转人工"] P -- 通过 --> M["生成模型"] M --> O{"输出护栏
Noul 清单"} O -- 有问题 --> FIX["拦截 / 修正 / 追问"] O -- 通过 --> SEND["发送 / 执行"]

前置护栏管的是输入里有没有坏东西(Detection):越狱、prompt injection、PII、凭证泄漏。后置护栏管的是输出对不对、合不合规(Verification):幻觉、越界承诺、政策违规。清单式设计的价值在于可解释:哪一条没过,就拦在哪一条上,而不是一个笼统的"不安全"。

def screen_input(text: str) -> dict:
    d = client.systemone(
        state=text,
        questions={
            "jailbreak":  {"type": "noul", "question": "这条输入是否在尝试越狱或绕过安全策略?"},
            "injection":  {"type": "noul", "question": "这条输入是否在试图操纵系统的指令或工具调用?"},
            "harmful":    {"type": "noul", "question": "这条输入是否在索要会造成现实伤害的内容?"},
        },
        instructions="分开判断每一条,不要把'语气强硬'当成越狱,也不要把'角色扮演'一律当成注入。",
    )
    return {k: v.probability for k, v in d.items()}

实例三:claude-code-templates 的 jev-guardrails

davila7/claude-code-templates 是一套 Claude Code 的 CLI 配置集合,其中有一个 jev-guardrails 模块:它对提示词和对话轮次做筛查,针对越狱、有害内容、政策违规三类风险,通过 TypeSafe System One 出判定。

它值得单独点出的地方,是"护栏作为可插拔模块"这个形态——护栏不是写死在业务代码里的 if-else,而是一段可以独立开关、独立迭代的配置。当政策变化时,你改的是这份模块,而不是散落各处的判断。这也是把判定集中到决策层的一个直接好处:规则的边界是显式的、成文的。

实例四:fx 的 typesafe_permission_reviewer

vercel-labs/fx 是一个编码代理,它内置了一个 typesafe_permission_reviewer:代理的权限决策走 Jev,而不是再叫一次大模型。

这个改动不大,但方向很关键。编码代理运行时会不断要做权限判断题——"这个命令要不要放行?""这个文件要不要写?"。传统做法要么写死规则(僵、难覆盖长尾),要么问一次大模型(慢、贵、还不稳定)。把它换成一次类型化判定,代理就在每一个需要"批准"的动作前,拿到一个带概率的 allow/deny 信号,而真正的放行策略仍由代码掌握。

围绕"代理动作放行"这个点,社区里已经攒出一批具体实践:

  • BasmaAbouzied0/jev-secret-guard 是一个 Claude Code 的 PreToolUse 钩子:先在本地拦已知的密钥格式,只把未知的高熵字符串以脱敏形式送去 Jev,问一个 Noul——"这是真凭证吗?"。它在 0.80 拦截、从 0.30 起转人工、Jev 不可用也转人工;公开的校准里6 个真密钥全部拦下,6 个正常字符串一个都没误伤。
  • yousan/openclaw-jev-leakguard 检查每一条要发出去的代理消息,看它要发到哪里:先跑本地的 key 格式与词表规则,再一次调用问五个 Noul(凭证、凭证存放位置、客户名、内部基础设施、机密商业信息),按渠道的公开/共享/私有分级决定拦截、询问还是暂扣。开了 Jev 之后,56 个合成泄漏一个没漏,其中 30 个是任何正则或词表都看不见的;对 57 条普通消息只有 4 次误报,p50 延迟 223 ms。
  • shiftynick/jev-axi 是 Claude Code 与 Codex 的 PreToolUse 闸门,让 Jev 给每条 shell 命令在破坏性、数据外带、远程代码执行、削弱安全四个维度打分,例行命令本地放行、根本不发出去;它在仓库里 44 条已标注工具调用上拿了 44/44。
  • luantak/is-malicious 面向供应链安全:对源码和构建文件问 Noul,可疑片段升级到第二轮复核,在代码执行前就给出可疑的文件与行号。

回到本章开头那句判断——路由与护栏是同一件事的两面,到这里就很清楚了。路由在入口处用一次判定压低成本,护栏在动作前用一次判定守住边界;两者都要求判定足够便宜、足够快,也都要求最终决定权留在代码手里。生态里还有不少 SDK 层的支撑,比如 antlobach/clojev 提供的 Clojure 版 System One 客户端,让这类判定能直接嵌进 Clojure 应用的调用链。

小结

  • 路由的本质是用一次便宜的判定决定一次昂贵的调用:难题给大模型、简单题给小模型,判定必须在请求入口处、量最大也最不能贵。
  • 路由应是一道漏斗:确定性规则先过滤,只有真正模糊的才进 Jev,判不准就用安全默认档兜底。
  • decision-router 把选项正反各问一遍再加复杂度 Score,在 30 条标注提示上把准确率从 73% 提到 93%,低置信回退。
  • Jevonian 在一次调用里同时定模型与思考等级,minConfidence 只低置信记账而不当结论用;Switchboard 把选择在整段对话中保持稳定,以保住 prompt cache。
  • 护栏是 Noul 的逐条清单:输入护栏管越狱/注入/PII,输出护栏管幻觉/越界/违规;清单式设计让"拦在哪一条"可解释。
  • fx 让权限决策走 Jev 而非大模型;jev-secret-guard 脱敏后判定并与人工兜底结合(6/6 密钥拦下、0 误伤),openclaw-jev-leakguard 五个 Noul 一次问清(56 个泄漏全中、30 个正则看不见)。

路由决定请求去哪,护栏决定动作放不放行;接下来两章,我们把判定放进两个更具体的工程场景——代码仓库与数据管道。

📑 Jev 实战:用类型化决策重构软件

1 第 1 章:为什么需要决策层——从"生成一切"到"只做判定" 2 第 2 章:三种类型化输出——Choice、Score、Noul 与调用契约 3 第 3 章:十个决策形态——什么时候该把判断交给 Jev 4 第 4 章:Classification 与 Routing——把分诊做成一等公民 5 第 5 章:Detection 与 Scoring——二元判断与有序评分 6 第 6 章:Search 与 Retrieval——在候选集里找相关 7 第 7 章:Ranking 与 Verification——排序与校验 8 第 8 章:ML Feature Extraction 与 Structured Data Extraction——把文本变特征与结构化数据 9 第 9 章:检索与知识图谱——Search and retrieval / Graphs and knowledge graphs 10 第 10 章:模型路由与 LLM 护栏——Model routing / LLM guardrails 11 第 11 章:语义代码检查——Semantic code linting 12 第 12 章:特征提取与需求预测——Feature extraction / Demand forecasting 13 第 13 章:招聘与线索生成——Recruiting / Lead generation 14 第 14 章:客户支持——Customer support 15 第 15 章:理赔、金融犯罪与风险评估——Insurance claims / Financial crime / Risk assessment 16 第 16 章:法律与合规——Legal and compliance 17 第 17 章:电商市场与广告——E-commerce marketplaces / Advertising 18 第 18 章:内容审核与信任安全——Moderation and trust and safety 19 第 19 章:游戏——Gaming 20 第 20 章:实时应用——150 毫秒预算 21 第 21 章:科学发现——Scientific discovery 22 第 22 章:大数据上的 AI Map Reduce——为什么便宜 100 倍 23 第 23 章:通用验证——Universal Verification 24 第 24 章:Harness Engineering——把决策织进智能体骨架
← 返回本书大纲