判断引擎落地 .NET 的四条路线、一个客户端与一份生产样本

很多业务场景要的其实不是一段话。

客服系统收到一条消息,要的是"转账单组还是技术组";审核系统拿到一张图,要的是"过还是不过";工单系统要的是一个严重度评分。答案就是一个选项、一个概率、一个分数,程序拿到就走自己的逻辑,没有人去读它。

但过去的通行做法是:把整段文本发给前沿大模型,让它"按 JSON 格式输出",然后祈祷它这次不要多写一句解释、不要漏一个括号。格式错了就重试,字段类型不对再修一遍------用过的人都知道这条路有多烦。

最近一个月,这个局面被一个叫 Jev 的闭源模型和它的开源对手 Laya 彻底搅动了。更有意思的是,这股浪潮几乎立刻冲进了 .NET 生态:先是 Laya 的两个独立移植 NLaya 和 layar 前后脚出现,紧接着又出现了两条思路截然相反的 .NET 原生实现路线------用 26B 扩散大模型"单步读取"的 TensorSharp ,和用小编码器加决策头的 Sezika ;与此同时,社区还补上了客户端这一环------SystemOneSharp ,一个把所有兼容服务统一起来的类型化 HTTP 客户端。而自托管 Agent 运行时 OpenClaw.NET 的 PR #246 与紧随其后的 PR #255,则演示了把这套东西真正接进生产需要过多少道关卡------以及它最终如何收敛成一个纯 .NET 的技术栈。

这篇文章把这条链路从头拆到尾:判断引擎到底是什么、它解决了什么问题,以及在 .NET 里你有哪四条引擎路线、一个客户端可以走,最后看一份把它开上生产路的真实样本。


一、先看懂背景:Jev 与 Laya 的"九天三国杀"

2026 年 9 月 15 日到 9 月 24 日,九天里发生了三件事:TypeSafe AI 发布闭源决策模型 Jev;Convai Innovations 开源了对标的 Laya;1Panel 把 Laya 封装成 Laya Server 上架应用商店。

三件事单独看都是新闻,串起来看是一条完整的产业信号:"判断"正在从"生成"里被剥离出来,成为一层独立的基础设施。

Jev:System 1 模型

Jev 出自 TypeSafe AI,创始人 Diogo Almeida 是前 OpenAI 研究员、RLHF 的共同发明者之一。他反复问的一个问题是:模型在对话上超越人类已经好几年了,那么自动化到底在哪里?

他自己的回答是:我们一直用最贵、最慢、最不确定的工具,去干最廉价、最需要确定性的活。

Jev 的名字取自 Daniel Kahneman 的双系统理论------System 1 是快速、直觉的判断,System 2 是缓慢、审慎的推理。Chat 模型和推理模型都在追 System 2(一步一步想),Jev 做的是 System 1:像一个熟练的客服扫一眼工单就知道该转给谁,不解释、不展开、不写理由,只给结论和把握程度。

调用形态被压缩成两个概念:

  • state:一段上下文。可以是一封邮件、一条工单、一条告警记录、一段 JSON;
  • questions:一组你事先定义好类型的问题。

模型一次前向传播把整批问题全部算完,返回带概率的答案。没有 token 逐个采样,没有 JSON 需要修复。它的全部表达能力由三种原语构成:

原语 作用 返回 典型用法
choice 从离散选项里挑一个 选中的选项 + 概率分布 + 置信度 这个工单归 billing / technical / sales?
score 在固定等级上打分 一个数值 + 分布 + 置信度 客户愤怒程度 0--2 打几分?
noul 是非判断 校准后的 P(true) 这条消息是否要求退款?

这里有两个关键设计值得展开。

关键一:答案空间在调用之前就锁死了。 传统做法是在输出之后做校验;Jev 的做法是程序先声明"我要问什么、允许答什么",模型只负责在约束内填概率。它不可能返回错误的类型或畸形结构------这是构造性保证,不是训练出来的好习惯。但它依然可以判错:它保证的是"答案合法",不是"答案正确"。

关键二:校准过的置信度才是判断能进生产的前提。 Jev 和 Laya 都用 RLCD(Reinforcement Learning for Calibrated Decisions)训练,奖励函数是严格 proper scoring rule------只有当模型诚实报告自己的概率时,期望奖励才最大。于是代码可以写成:

python 复制代码
if conf >= 0.85:
    route_automatically(dept)      # 高置信度:自动执行
else:
    escalate_to_human(dept)        # 低置信度:转人工,或升级到大模型

"自动化处理大多数 + 阈值兜底少数",才是决策模型真正的产品形态。

代价是:Jev 闭源,只能调 API,数据要出网。这个缺口存在了三天。

Laya:三天后的开源对手

Laya 由 Convai Innovations 于 9 月 18 日发布,Apache 2.0 许可,权重、代码、训练数据全部公开,接口设计几乎一比一对应 Jev。它发布了三个 checkpoint:

Checkpoint 基座 参数量 上下文 说明
laya ModernBERT-large 421M 512 tokens 英文基础版
laya-multilingual mmBERT-base 322M 1024 tokens 100+ 语言,中文场景用这个
laya-typed-decisions ModernBERT-large 421M 512 tokens 在客服、发票、安全事件、Agent trace 四个工作流上微调

但必须泼一盆冷水,Laya 的 model card 自己写得很直白:零样本 typed-decisions 准确率只有 0.362,连"永远猜最多那一类"的多数类基线(0.461)都不如;逐题型重拟合温度参数后,平均 ECE 才能从 0.466 降到 0.081。项目方的原话是:"Laya is a fast base to specialise, not a zero-shot decision engine."------它是一个可快速特化的底座,不是开箱即用的零样本决策引擎。

还有一条来自第三方测算的规律必须记住:成本优势的上限 = 1 / 弃权率。 无论单次判断多便宜,只要有 30% 的判断要回退给大模型,整体最多省 3.3 倍。自托管只省掉了单价,不省掉弃权。


二、NLaya 与 layar:Laya 的两个 .NET 移植

对 .NET 开发者来说,最直接的上车方式是移植版。有意思的是,Laya 发布没多久,.NET 社区就冒出了两个互相独立的移植 :NLaya (TorchSharp + ONNX Runtime)和 layar(ONNX Runtime + TorchSharp)。两个都号称与 Python 原版逐位对齐,三个 checkpoint 全部支持,但设计取向不太一样------NLaya 走"生态全家桶"路线,layar 走"精简内核 + 双后端对打"路线。先看 NLaya。

技术栈:站在微软 ML 栈的肩膀上

职责 组件
分词 Microsoft.ML.Tokenizers(BpeTokenizer,直接读 checkpoint 的 tokenizer.json)
张量运算 System.Numerics.Tensors
原生推理 TorchSharp,直接加载 model.safetensors
ONNX 推理 Microsoft.ML.OnnxRuntime,跑官方导出的 encoder.onnx + head.onnx

后端只负责把一批 token id 变成 logits(ILayaBackend.Run),分词、prompt 布局、校准、解码全部在核心库里,所以两个后端给出的答案完全一致。

三分钟上手

csharp 复制代码
using NLaya;
using NLaya.TorchSharp;

await using var agent = await Laya.LoadAsync(
    "convaiinnovations/laya-multilingual", o => o.UseTorchSharp());

var questions = new Questions()
    .Choice("department", "Which team should handle `body`?",
        ("billing", "invoices, payments, refunds"),
        ("technical", "bugs and outages"),
        ("sales", "pricing"))
    .Score("urgency", "How urgent is this?",
        "not urgent", "somewhat urgent", "very urgent")
    .Noul("refund_requested", "Does the sender ask for money back?");

var result = agent.Predict(
    new { body = "I was charged twice for invoice 4411. Please refund me today." },
    questions,
    new PredictOptions { MaxLen = 8192 });

Console.WriteLine(result.Choice("department").Choice);

NLaya 不替你下载模型,用 Hugging Face CLI 拉到标准缓存目录即可;缺模型时报错信息会直接告诉你该跑哪条 hf download 命令。

类型化决策:把答案写成一个 C# record

这是 NLaya 最" .NET 味儿"的能力------用 C# 类型描述答案,拿回来的就是这个类型。每个属性对应一个问题:枚举是 choice,bool 是 noul,带 [Range] 的整数是 score;[Description] 就是 instructions。

csharp 复制代码
public enum Team
{
    [Description("Payments, invoices and refunds")] Billing,
    [Description("Bugs and outages")] Support,
    Other,
}

public sealed record Triage(
    [property: Description("Which team should handle `body`?")] Team Team,
    [property: Description("How urgent is this?"), Range(1, 3)] int Urgency,
    bool NeedsHuman);

[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
[JsonSerializable(typeof(Triage))]
internal partial class AppJson : JsonSerializerContext;

Triage t = agent.Decide(email, AppJson.Default.Triage);   // AOT 安全

配合源生成的 JsonSerializerContext,整条链路在 Native AOT 下可发布、可裁剪------四个核心包都标记了 IsAotCompatible。

生产级的细节一个不少

NLaya 不是玩具移植,它把 Python 库的工程化能力几乎搬全了:

  • Router:按请求自动选 checkpoint,非拉丁文字自动路由到 multilingual,支持懒加载、预加载和并发共享加载;
  • 批量与流式 :PredictBatch 共享前向传播,PredictStreamAsync 对接数据库游标、CSV、消息队列,内存有界;
  • 微批处理 :MicroBatchingPredictor 把并发请求攒几毫秒合成一个 batch,这是 GPU 吞吐的正确打开方式;
  • DI 与可观测性 :AddLaya / AddLayaRouter 注册单例,tracing 和 metrics 走标准 ActivitySource / Meter,OpenTelemetry 零配置接入;
  • Microsoft.Extensions.AI 集成 :UseLayaGuardrail 在 LLM 看到用户输入之前先做护栏筛查,LayaRouterChatClient 用一道 choice 题给请求选模型,还能把判断能力作为工具挂进 Agent 循环;
  • ML.NET 管道 :mlContext.Transforms.Laya(...) 直接在 IDataView 上追加答案列,CSV 进、分好类的表出。

最后一个场景特别值得展开:用 Laya 给大模型当门卫和路由 。每个用户请求先过一道几十毫秒的判断------该不该拦、该给哪个模型答------只有真正需要生成和推理的请求才触达大模型。这正是 Laya 内置 router / guard / moderation 三个 preset 的设计意图,也是 1Panel AI 网关用 Laya Server 替代 Embedding 相似度路由的同款思路:路由规则用自然语言写成 criteria,改一行字就能调整路由行为,不需要标注样本、重建向量、重新部署。

layar:另一个移植,把两个后端放在一起打擂台

layar(nullean 出品)是另一个独立移植,气质和 NLaya 很不一样:它不做生态集成,而是把内核做到极简,然后认真回答一个工程问题------ONNX Runtime 和 TorchSharp 到底哪个快?

它的设计里有几个点很见功力:

  • 从零写的 BPE 分词器 :Layar.Tokenization 直接读 Hugging Face 的 tokenizer.json(byte-level GPT-2 式 + Metaspace/SentencePiece 式都支持),不依赖任何外部分词包,运行时零 Python;
  • 双后端同一接口 :Layar.Onnx 和 Layar.TorchSharp 都实现 IDecisionBackend,可以在同一批请求上互相对打基准;TorchSharp 后端加载的是 torch.jit.trace 导出的 TorchScript 模块,而不是手工移植的网络结构;
  • 类型化 Schema 绑定 :IQuestionSchema<T> 把固定问题集和答案绑回 record 的方式合在一起,PredictAsync 直接返回 TriageResult,不用字符串 key 去查字典;
  • 工程细节拉满 :模型生命周期 Router(加载、预载、LRU 逐出)、超 20 个选项时的 embedding 初筛(Shortlist,精确对齐 Python 的 shortlist.py,连 NaN 和零向量的边界行为都对齐)、按需下载 checkpoint 的 CheckpointCache(库版本和模型版本用两套独立 tag 发布,互不惊动)。

最硬的是它的验证方式:两个后端都与 Python 原版的输出逐位对齐(置信度一致到 1e-6),而且是提交进仓库的回归测试,不是一次性的手工核对。 BenchmarkDotNet 实测(Apple M2、CPU、多语言 checkpoint、一次 3 题批量请求):

后端 均值 相对 分配内存
TorchSharp 70.9 ms 0.48x 85.3 KB
ONNX Runtime 148.7 ms 1.00x 82.6 KB

TorchSharp 在这里快约 2.1 倍------作者还坦白交代了 ONNX 侧的 SessionOptions 调优没能补上差距。两个后端还各自带一个 Native AOT 冒烟测试,本地已验证 AOT 二进制能加载真实 checkpoint 并给出正确预测。

两个移植怎么选

你的需求 选谁
要 DI、OpenTelemetry、ML.NET、Extensions.AI 护栏/路由等生态集成 NLaya,全家桶开箱即用
想要极简内核、自己控制依赖、或在两个后端间做性能对比 layar,接口更小,基准数据现成
重度 AOT 场景 两者都已验证 AOT 兼容
不想引入外部分词依赖 layar(分词器从零自研)

一个赛道出现两个高质量移植不是坏事------它们的 parity 测试互为交叉验证,等于给"这个判断引擎在 .NET 里到底靠不靠谱"上了双保险。


三、另外两条 .NET 路线:同一个契约,两种引擎

NLaya 和 layar 是"移植",而 TensorSharp 和 Sezika 代表的是"原生实现"------它们共享同一套 Choice / Score / Boolean 契约,但引擎几乎是反着走的。

路线一:TensorSharp------26B 扩散大模型"单步读取"

先看上游发生了什么。9 月 22 日,vLLM 合并了 PR #57250,把 Google 的文本扩散模型 DiffusionGemma 改造成了一台结构化读取引擎:扩散模型生成文本时在一块叫 canvas 的区域里反复去噪,vLLM 把这个机制反过来用------canvas 缩到 16 或 32 宽,预填一份答案模板(如 "billing": "?"),只在答案位置留 mask,然后只跑一步去噪,直接读空位上各候选词的 logprobs。全程没有逐 token 生成,自然没有格式错误和重试。

TensorSharp(纯 .NET 的本地 LLM 推理引擎)第二天就把这套机制搬进了 .NET,实现了 Jev 兼容的 POST /v1/systemone,底层跑 26B-A4B 的 DiffusionGemma,并且做了两件原型没有的事:

  1. 稀疏输出投影:不计算完整词表的 logits,只算答案位置 × 候选标签的那几行,同硬件下比"生成 JSON 再解析"快 4--5 倍;
  2. 图片输入:请求里内联最多 8 张 base64 图片直接参与决策。由于市面上的 DiffusionGemma GGUF 都是纯文本的,TensorSharp 干脆直接读上游官方 checkpoint 的第 11 个 safetensors 分片(2.84 GB,装着视觉塔全部 356 个张量),不转格式。

项目自带的红绿灯例子设计得很巧:state 文本只说"车辆正在接近路口",一个字没提灯,但请求里嵌了一张绿灯照片------模型回答绿灯、不用停车,答案只能来自像素。更妙的是文档自带反事实实验:同一个请求去掉图,模型会很有把握地答"红灯,必须停车"。

安全细节也到位:服务器没加载视觉塔时收到带图请求返回 503 明确拒绝;远程图片 URL 和本地文件路径一律拒收,免得推理服务器被当成免费代理。

路线二:Sezika------不用大模型,编码器加决策头就够了

IoTSharp 团队的 Sezika 走的是完全相反的方向:既然只要一个判断,为什么要用 26B 的生成模型?

它的底座是 mmBERT-base 级别的多语言编码器(正是 Laya multilingual 的同款基座),上面接类型化决策头。输入进来编码器过一遍,决策头直接给每个候选打分------没有 canvas,没有去噪,连"一步"都不需要。

这个项目最吸引人的是它的纯粹程度:整个推理栈都是 C# 写的------embedding、attention、RoPE、归一化、MLP、决策头,CPU 上有标量 FP32 / SIMD FP32 / W8A32 量化三条路径,不碰 Python、PyTorch、ONNX Runtime。GPU 路径的做法在 .NET 圈很少见:构建期用 ILGPU 把 kernel 编译成 PTX,运行时 Native AOT 程序通过静态绑定直接调 CUDA Driver,绕开了与 AOT 不兼容的运行时代码生成,cuBLAS、cuDNN 一概不用。

性能数据给得很克制:Core Ultra 9 185H + RTX 4070 Laptop、CUDA FP32 后端,1 个问题热路径约 40 ms,32 个约 1.23 秒。而且作者把限定条件写得很全:只预热一次、采样五次,明说"五次采样不足以证明稳定的尾延迟";W8A32 量化目前比 SIMD 还慢,照实写;所有输出标记为 uncalibrated,没校准就是没校准。这种"丑话说在前面"的文档风格,在人人吹 benchmark 的年代挺少见。

字段像到什么程度?像,但语义不一样

两边的请求骨架(model / state / questions / criteria)几乎同构,但有几个语义差异比字段差异更值得注意:

  • confidence 和 concentration 不是一个东西。 TensorSharp 的 confidence 是条件分布的最大概率;Sezika 的 concentration 是归一化熵算出的集中度。同一个请求两边跑出来的数没有可比性,别混用。
  • 对"不确定"的处理思路相反。 TensorSharp 遇到不确定的题会自己多读几次(samples: "auto",条件熵超阈值自动触发),给平均值和多次读取间的标准误;Sezika 是确定性的,一次前向一个答案,但把"信不信得过"做成协议的一部分------低于浓度门槛标记 abstained,由调用方决定怎么办。一个靠"多想几遍",一个靠"明说不敢答"。
  • 可复现性的含义不同。 TensorSharp 有 seed,但只保证同运行时同后端可复现;Sezika 压根没有随机源,复现性靠固定模型版本加逐张量 hash 校验链。

四条路线怎么选

场景 建议
想要 Laya 完整生态:Router、校准、ML.NET、Extensions.AI NLaya,移植保真度最高,DI/AOT 全套支持
想要极简内核、自研分词器、双后端性能对比 layar,ONNX 与 TorchSharp 任选,逐位对齐 Python
高频低延迟的本地判断:意图识别、路由、RAG 重排 Sezika,毫秒级,AOT 编译嵌进进程即可
需要大模型的世界知识和语义理解 TensorSharp Jev
答案在图里:质检、票据、红绿灯这类视觉判断 TensorSharp Jev,目前只有它支持图片
业务流程里需要显式拒答和校准状态 Sezika ,协议自带 abstained / calibration
不确定时希望模型自己多算几遍 TensorSharp Jev 的 samples: "auto"
引擎在别处(云端 Jev / Laya Server),只要一个顺手的调用方 SystemOneSharp,一个客户端通吃所有兼容服务

其实四者并不冲突。接口契约几乎一样,完全可以分层用:Sezika、NLaya 或 layar 放前面做高频路由和初筛,拿不准的、需要看图或者需要常识的,再丢给 TensorSharp Jev 或大模型。小引擎守门口,大引擎做终审。


四、客户端这一环:SystemOneSharp,一个客户端打所有兼容服务

前面四个项目都是"引擎"------模型在你这边跑。但还有一种同样常见的形态:引擎在别处(TypeSafe 的云端 Jev、1Panel 的 Laya Server、自己部署的 TensorSharp /v1/systemone),你的程序只是调用方。这层需求由 SystemOneSharp 补上:一个 .NET 10 的 System One API 类型化客户端,零运行时依赖 ,同一个 POST /v1/systemone 请求,云端 Jev 和本地 Laya 服务器通吃。

它的存在本身就是"接口契约统一"这条主线最好的注脚:因为大家都讲同一套协议,一个客户端就够了。

csharp 复制代码
using SystemOneSharp;

using var http = new HttpClient();
ISystemOneClient client = new SystemOneClient(http, new SystemOneOptions
{
    ApiKey = Environment.GetEnvironmentVariable("TYPESAFE_API_KEY")
});

var request = new SystemOneRequestBuilder()
    .WithState("Please refund my duplicate charge")
    .AddChoice("team", "Which team should handle this?", choice => choice
        .Option("billing", "Payments and refunds")
        .Option("support", "Product issues"))
    .AddScore("urgency", "How urgent is this?", score => score
        .Level("routine").Level("soon").Level("critical"))
    .AddNoul("refund", "Does the customer ask for a refund?")
    .Build();

var response = await client.DecideAsync(request);
var team = response.GetChoice("team");
var refundProbability = response.GetNoul("refund").Noul;

切到本地 Laya Server 只需把 BaseUri 指向 http://127.0.0.1:8000、按需省略 ApiKey------改一行配置就换了供应商。

客户端库的分量不在 Demo,在边角处理,SystemOneSharp 这几点做得挺扎实:

  • 重试策略内置 :自动重试 HTTP 429 / 529,遵守 Retry-After,失败时抛出区分传输层、API 层、协议层的类型化异常;
  • 可观测性 :每次 DecideAsync 走名为 SystemOneSharp 的 ActivitySource,采用 OpenTelemetry GenAI 属性命名(模型、各类型问题数、重试次数、token 用量、结果),且明确承诺不记录 state、instructions、criteria、答案和 API Key;
  • AOT 友好 :WithState 可传 JsonNode / JsonElement / 任意 C# 对象,配合 JsonSerializerContext 的 JsonTypeInfo<T> 可完全避开反射;
  • 宽容的响应处理 :契约之外的字段(比如 Laya 特有的 routing)收进 AdditionalProperties 原样保留,不做强行校验------这正是"客户端不该比协议更聪明"的正确姿势。

另外还有三个可选包对接微软 AI 栈:SystemOneSharp.Extensions.AI(把 ChatMessage 对话规范地投影成 System One state)、SystemOneSharp.Extensions.AI.Evaluation(一次请求产出多个 MEAI 评估指标)、SystemOneSharp.AgentFramework(给 Microsoft Agent Framework 提供 LoopEvaluator 和函数调用中间件)。

要说明一点:它是社区维护的非官方客户端,与 TypeSafe 和 Laya 官方都没有隶属关系。


五、生产样本:OpenClaw.NET PR #246 与 #255------把决策模型关进工程的笼子

前面讲的都是引擎和客户端。真正的问题来了:把概率模型的输出接进一个正在跑业务的 Agent 系统,需要多少工程护栏? 自托管 Agent 运行时 OpenClaw.NET 的 PR #246(贡献者 Telli,《feat: add hosted Jev and local Laya decision routing》)给出了一份教科书级的答案------它把托管 Jev 和本地 Laya 同时接进了回合路由层,而其工程形态远比"接一个 API"复杂。

它叠加在什么之上

OpenClaw.NET 原本就有一套 ONNX 小模型做的动态回合路由:每个用户回合被分类到 T0--T3 四个成本层级,路由结果投影到模型档案、工具过滤和推理档位上。这套基线的文档诚实得近乎自贬------Macro-F1 只有约 0.65,并配了一张严苛的上线门禁表(标注集 Macro-F1 ≥ 0.90、路由附加延迟 p95 ≤ +80ms、任何 Sev1 事故立即回滚)。换言之,在 PR #246 之前,路由的骨架与门禁文化已经在了,缺的只是一个更好的"判断器官"------ONNX 小模型是第一代,Jev/Laya 是第二代候选。

Jev 是装饰器,不是提供商

PR 为 Jev 设计了版本化 rubric openclaw-tiers-v1,一次请求并行问三道题:tier(T0--T3/abstain 的 Choice)、high_risk(Noul)、requires_tools(Noul)------正是 Jev"一次前向并行多题"能力的最小生产形态。送往云端的 state 只含当前请求和至多四条最近消息,脱敏管道先行。

可靠性设计的密度令人印象深刻,值得逐条列出:

  • 1500ms 有界超时覆盖全程,关键路径不重试;并发槽占满立即回退而非排队;连续失败 3 次熔断 30 秒;
  • abstain、不确定、未知层级、模型版本不符、凭据缺失、HTTP 错误、超时------全部收敛到同一个动作:保留基线;
  • 风险与工具信号只能上调层级地板,不能下调 ;降级方向要求更高置信度(DowngradeMinConfidence 0.95 对 MinConfidence 0.80,外加前两名概率间距 ≥ 0.15);
  • active 模式只允许改动模型档案与推理档位,不授予任何工具权限,文档原话:"Jev 是装饰器,不是聊天提供商",并禁止把它的模型 ID 写进模型档案。

Laya 侧:一条强制的校准工具链

本地路径回答的是"数据不能出厂"的同一个问题。Laya 服务被做成独立 Python 进程(PyTorch 刻意挡在 .NET 网关和 NativeAOT 二进制之外),只听 127.0.0.1,同一时刻只处理一条推理;模型资产钉死 Hugging Face 修订号、生成 SHA-256 清单、强制离线模式。最重的投入在校准 :运营者必须先用自有标注数据按检查点/题型/选项数分桶拟合温度参数,把校准产物的 SHA-256 写进配置,active 模式强制要求响应中的校准 ID 与配置匹配------不达标就永远停在影子模式。这直接回应了 Laya 出厂 ECE 高达 0.466 的已知短板。

影子模式不是建议,是默认姿势

PR 的发货默认值是 disabled;启用后的第一站是 shadow 模式------基线决策原样返回,但系统在有界期限内等待一次真实评估,把基线层级、提案层级、概率、置信度、延迟与估算成本追加进元数据日志(刻意不记录对话文本和凭据)。配套 Python 评估脚本计算准确率、混淆矩阵、ECE 与风险-覆盖曲线;无标注时报告明确不做任何准确率断言。回滚是一行配置加重启。

这套设计的立场可以概括成一句话:决策模型做提案,工程约束做裁决。 每次决策无论采纳与否都可事后重放------"模型当时为什么这么想"是事实,不是猜测。

同样需要如实记录边界:PR 只覆盖回合路由------Agent 系统里频率最高、爆炸半径最小的决策点;技能选择、记忆重排、心跳分诊被列为未来工作。先在最安全的焊点上建好"决策模型 + 护栏 + 日志 + 校准"的全套规程,再向高风险焊点推广------这个顺序本身,就是给所有想把判断引擎接入业务的团队最有价值的示范。

后续:PR #255------把最后的 Python 也换成 .NET

PR #246 留下的唯一遗憾是:本地路径的核心部件还是 Python------tools/laya_service 是个独立的 Python 进程,评估脚本也是 python3 scripts/evaluate-jev-routing.py。当时的理由是"把 PyTorch 刻意挡在 .NET 网关和 NativeAOT 二进制之外",但 NLaya 的成熟让这道隔离墙失去了存在的必要。

紧随其后的 PR #255 (贡献者 geffzhang,《Laya to dotnet》)干的就是这件事:把 tools/laya_service 整体迁移为 .NET 10 + NLaya 的工具链。PR 里的迁移计划覆盖了八个任务:CLI 脚手架、安全的 loopback 协议与服务行为、模型下载与清单校验、运行时集成、评估/校准/报告的逐项对齐、CI 与解决方案接线、Python 清理、文档更新。落地后的对比非常直观:

bash 复制代码
# 之前(PR #246):Python 评估脚本
python3 scripts/evaluate-jev-routing.py ./jev-decisions.snapshot.jsonl --output /tmp/jev-report.json

# 之后(PR #255):同一条命令,纯 .NET
dotnet run --project tools/laya_service -c Release -- report \
  ./jev-decisions.snapshot.jsonl --output /tmp/jev-report.json

测试入口也同步从 python3 -m unittest 换成了 dotnet test。细节上的较真程度延续了 PR #246 的风格:校准拟合时拒绝非数值的原始概率、强制答案概率为 0,1 有限值、概率和允许 ±0.002 的舍入漂移并在缩放前重新归一化;CLI 把 checkpoint_not_installed、requested_device_unavailable 等启动失败映射为明确的退出码和 stderr 输出,不让运维者猜。

这一步的意义超出"少装一个 Python":NLaya 从社区移植变成了被生产级 Agent 运行时选中的正式底座 ,而整条决策路由链路------引擎、服务、校准、评估、报告------第一次完整收敛到了纯 .NET 技术栈内,Native AOT 发布、单一工具链构建、统一的可观测性全都顺理成章。回头看,这正是 PR #246 把决策客户端抽象成 OpenClaw.Routing.Decisions 时就铺好的路:契约对了,引擎换心只是配置问题。


六、上线前的冷静清单

判断引擎这类东西,接入业务的门槛从来不在 API 好不好调,而在你敢不敢信它。无论你选哪条路线,这五件事绕不开:

  1. 别零样本上生产。 Laya 零样本准确率 0.362,接近随机,实用能力全部来自微调。准备好自己的标注数据,先跑一批真实样本看基线。
  2. 置信度要先校准。 逐题型拟合温度参数,把 ECE 从 0.466 压下来,否则 conf >= 0.85 这种阈值没有意义。
  3. 路由目标别贪多。 高基数是明确短板(Banking77 上只有 0.425),两到五个目标最稳。
  4. 延迟按自己的环境重新测。 官方 GPU 基准是几十毫秒,CPU 部署实测可能落在几百毫秒到秒级------这个开销每次请求都要付,必须计入整体 P99。
  5. 兜底必须配。 决策失败、超时、置信度不足,都要有明确的降级路径,不能让判断层成为新的单点故障。

对照上一章 OpenClaw.NET 的做法会发现,这五条清单在它的 PR 里全部硬化成了配置和代码:校准对应"校准 ID 强制匹配",兜底对应"一切异常收敛到保留基线"。清单是态度,护栏是实现,两者都要。

最后记住那条最重要的数学规律:成本优势的上限 = 1 / 弃权率。 真正决定 ROI 的,从来不是"一次判断多少钱",而是"你自己的数据能把弃权率压到多少"。

模型给出的概率只是概率,没校准过就不是正确率。这几个项目至少在这点上都很清醒------先把丑话写进协议里,再谈怎么用。判断合法不等于判断正确,动不动手,永远由调用方决定。


参考资料

  1. Jev 发布三天就被开源了:33 毫秒做一次判断的 Laya,值不值得进生产?https://www.cnblogs.com/xiaobaiysf/p/23127340
  2. 不让模型写作文,直接从它脑子里读答案:Jev 决策在 .NET 的两条路线:https://www.cnblogs.com/shanyou/p/23117082
  3. NLaya(Laya 的 .NET 移植):https://github.com/ljfio/NLaya
  4. layar(Laya 的另一个 .NET 移植):https://github.com/nullean/layar
  5. TensorSharp:https://github.com/zhongkaifu/TensorSharp/
  6. Sezika:https://github.com/IoTSharp/Sezika
  7. SystemOneSharp(System One API 的 .NET 客户端):https://github.com/pinkroosterai/SystemOneSharp
  8. OpenClaw.NET PR #246(决策路由接入 Jev/Laya):https://github.com/clawdotnet/openclaw.net/pull/246
  9. OpenClaw.NET PR #255(laya_service 迁移到 .NET 10 + NLaya):https://github.com/clawdotnet/openclaw.net/pull/255
  10. vLLM PR #57250:https://github.com/vllm-project/vllm/pull/57250

文中性能数据均来自各项目公开文档,测试条件以原文为准。