Skip to content
←返回开源项目

OPEN SOURCE DEEP DIVE

大语言模型Decision EngineNon-Autoregressive多语言

Laya:单次前向的类型化决策引擎

面向本地部署的多语言非自回归决策引擎,用单次前向回答选择、评分与二元判断,并以路由、批处理和校准接口服务 Agent 工作流。

NandhaKishorM/laya34kPythonApache-2.03 min read

把“做决定”从长文本生成里拆出来

Laya 是一个非自回归的决策引擎。它不续写一段回答,也不让调用方从自然语言里猜答案;它读取一段状态,再针对调用方定义的类型化问题直接输出选项、分数或概率。这个差别看起来只是接口形式,实际影响的是整条工程链:没有生成文本,就没有“解析模型输出失败”;没有采样过程,就可以在一次前向里同时处理多个问题;选择题还能返回完整分布,而不是只返回一个不可解释的标签。

仓库当前版本为 0.4.13,使用 Apache-2.0 许可,要求 Python 3.10 或更高版本。它最适合放在 Agent 工作流的“系统一”位置:分类工单、选择路由、判断是否升级、给有序等级打分、从一批候选中选动作。它不替代负责长链推理和开放式写作的语言模型,而是把高频、结构固定、需要低延迟的判断独立出来。

项目作者给出的关键取舍很明确:用一个专门训练的双向编码器做判断,让 421M 或 322M 参数的检查点在本机完成推理;复杂开放式生成仍交给上游 Agent 或大模型。因此,评估 Laya 时不应问“它是否比聊天模型更聪明”,而应问“这类判断值不值得从聊天模型里拆出来,并换成可校准、可批处理、输出形状稳定的专用层”。

flowchart LR
    A["文本、邮件、工单或 JSON"] --> B["Router 判断脚本与语言"]
    B --> C["English / Multilingual / Typed-decisions"]
    C --> D["双向编码器"]
    Q["choice / score / noul 问题"] --> D
    D --> E["类型化决策头"]
    E --> F["标签与完整概率分布"]
    E --> G["有序分数"]
    E --> H["P(true)"]
    F --> I["业务策略与阈值"]
    G --> I
    H --> I

三个检查点,一个请求级路由器

Laya 提供三个预训练检查点。它们共享同一套类型化接口,但承担不同负载。英语检查点和 typed-decisions 检查点使用 ModernBERT-large,各约 421M 参数;多语言检查点使用 mmBERT-base,约 322M 参数。Router 默认最多同时保留两个检查点,以控制内存;预加载可以消除首次切换的冷启动成本,但也会提高常驻内存。

检查点基础编码器参数上下文适用位置
englishModernBERT-large约 421M512 token英语分类、路由、排序与判断
multilingualmmBERT-base约 322M默认 1024,可设 8192非英语文本及跨语言工单
typed-decisionsModernBERT-large约 421M1024 token四种已微调的决策工作流

路由器先判断输入使用的文字系统和语言,再决定加载哪个检查点。显式指定模型拥有最高优先级;之后还可以按任务类型、可选的工作流识别、语言提示、语言检测和默认模型依次选择。这个顺序很重要:长英文文档会被路由到英语模型,多语混合内容则更适合固定到多语言检查点。生产服务应把模型选择写进日志,否则同一接口在不同语言上表现变化时,很难判断是输入分布变了,还是路由目标变了。

typed-decisions 不会被静默选为默认模型。它只针对四组特定决策结构做过微调,使用精确的问题标识集合匹配来识别工作流。这是一个保守设计:如果一个通用请求恰好也包含“风险”或“紧急程度”字段,系统不会因此误以为它属于训练过的场景。只有显式指定任务,或者开启自动工作流检测,路由器才会把它纳入候选。

三类问题不是三种问法,而是三种统计对象

Laya 的题目类型决定输出形状。把“是或否”硬写成两个普通选项,或把有序等级当成无序分类,都会丢失后续业务需要的统计含义。项目刻意把三者拆开:

类型输入核心输出正确用法
choice一组带说明的候选标签一个标签与完整概率分布工单分类、意图路由、动作选择
score一组按升序排列的等级期望有序值、图例与完整分布严重程度、满意度、风险分级
noul一条是非判断与可选标签命题为真的概率是否升级、是否弃用、是否触发规则

choice 的选项顺序具有语义,返回标签时保留调用方写入的原始值。每个候选最好配上可区分的说明,因为模型真正读取的是渲染后的问题文本;只写两个没有描述的短标签,相当于把判断依据交给模型猜测。返回中的 probabilities 可以用于阈值、拒答和多标签后处理,不能只保存 argmax。

score 返回的 score 是概率分布上的期望值,不是最可能等级的索引。例如五级量表可能给出 2.90,而最大概率落在第 3 级。期望值适合最小化平方误差,argmax 适合最小化与模型标签不一致的概率;两者回答的问题不同。接口同时返回 legend,调用方应依据它解释索引,不要四舍五入后再猜自然语言标签。

noul 返回的是 P(true),不是已经由模型替你裁好的布尔值。阈值属于业务策略:漏报和误报的成本不同,同一个 0.6 不可能适合所有场景。它还允许给真假两侧改名,但改写标签会真实改变模型行为,尤其英语检查点对标签措辞很敏感。任何重命名都必须用自己的留出集验证,不能把“自动”和“升级”仅仅当成展示文案。

一次读取状态,回答多个类型化问题

一个请求可以同时包含分类、有序评分和是非题,并共享同一次状态编码。下面这个结构展示的不是 API 装饰,而是 Laya 想解决的核心工作流:同一张工单只读一遍,然后同时得到部门、紧急程度和严重等级。

import laya

agent = laya.load("convaiinnovations/laya")
questions = {
    "department": {
        "type": "choice",
        "instructions": "哪个团队应该处理?",
        "criteria": {
            "billing": "账单、退款和发票",
            "technical": "缺陷、故障和技术支持",
            "sales": "价格、合同和采购"
        }
    },
    "urgent": {
        "type": "noul",
        "instructions": "是否需要立即升级?"
    },
    "severity": {
        "type": "score",
        "instructions": "问题严重到什么程度?",
        "criteria": ["轻微", "一般", "中等", "严重", "紧急"]
    }
}

result = agent.system_one(
    {"text": "我被重复扣款两次,一周无人回复,请立即退款。"},
    questions
)

department = result["answers"]["department"]["choice"]
urgent_probability = result["answers"]["urgent"]["noul"]
severity = result["answers"]["severity"]["score"]

这段代码也说明了边界:Laya 只负责概率判断,是否退款、是否升级、哪些工具可以调用,仍由外部业务状态机决定。把模型概率直接等同于执行权限,是把校准问题误当成授权问题。

批处理、长文档与上下文预算

选择题的题干、说明和候选都占用固定预算。中间的 head_max_len 越长,留给状态文本的空间越少;候选越多,每个候选分到的 token 也越少。这是为什么 Laya 不适合把几百个标签塞进同一个选择题:到 77 个标签时,每个标签只剩很少的 token,模型无法可靠区分。仓库自己的建议是选择题保持在约 20 个候选以内,更多标签应先做召回或短名单,再做最终决策。

多语言检查点默认上下文为 1024 token,可以显式提升到 8192。短输入使用 8192 并不会变慢,因为实际计算仍按真实长度进行;对真正很长的文档,则应同时关注状态占用和决策头占用。输入被截断不是“模型自己会处理”的小事,调用方应读取截断信息,把被丢弃的位置与长度写进日志。

当文档超过一次可读窗口时,predict_long 会用重叠窗口扫描,再汇总各窗口答案。是非题取最强的窗口;选择题和评分题取最有把握的窗口。这能定位分散在长文中的局部证据,但输出的概率仍是“决定答案的那个窗口”的概率,不是整篇文档经过联合推理后的校准概率。长文场景必须把这层含义写进产品逻辑,尤其不能拿窗口置信度直接做高风险自动执行。

批处理是 Laya 在吞吐上最有价值的部分。多个问题共享前向计算,多个请求也可按长度排序并组成批次;短请求和长请求混在一批里会造成大量 padding,按长度分组能明显减少无效计算。生产环境还应记录单次前向的问题数、批大小、真实 token 长度和超时,不要只测空载下的单条延迟。

结构化投影与工程接入

Laya 的原生输出已经是结构化的。没有生成文本,就没有 JSON 语法错误或“模型忘了一个引号”的解析失败;但这不是“不会判断错”的承诺。标签含义、问题措辞、候选说明和训练分布仍会让模型选错。工程上应把“解析安全”和“语义正确”分开测试:前者可以在单元测试里稳定验证,后者必须依赖留出数据和业务验收。

  • HTTP 服务提供单条和批量端点,可把 lang、max_len、head_max_len 与最低置信度策略传给核心推理。
  • Python、TypeScript、Go、Java、Rust 和 .NET 客户端覆盖不同部署栈;TypeScript 还可连接自托管的 HTTP 服务。
  • MCP、LangChain、LangGraph、LlamaIndex 和 CrewAI 适配器把决策结果接回 Agent 编排。
  • ONNX Runtime 路径面向生产部署;本地 PyTorch 路径更适合需要直接改模型和训练的实验环境。
  • 结构化扩展可把结果投影到 JSON Schema 或 Pydantic 模型,但仍应在应用侧再一次校验枚举、范围和必填字段。

它并不要求替换现有模型网关。更合理的接入方式是把 Laya 放在网关后面作为一个专用能力:左侧接收稳定 schema,右侧返回分布,外层再决定是否调用大模型、调用工具或向人求助。这样一来,路由和阈值可以独立配置,Laya 的升级也不会牵动整条生成链。

训练方法与校准:重点在概率质量

仓库把训练方法称为 RLCD。它在监督学习之外加入按严格适当评分规则计算的奖励,再与软交叉熵联合训练。严格适当评分规则的意义在于:模型若报告真实概率分布,就能取得最优期望分数;这比只奖励 argmax 更贴近 Laya 需要输出的完整分布。这里的“强化学习”不是让模型自由试错生成文本,而是围绕概率报告质量优化决策头与编码表示。

部署时最关键的一步不是再调温度来“让高置信度看起来更多”,而是按问题类型和候选数量,在自己的留出集上拟合温度,再在另一份验证集检查。仓库给出的英语检查点校准结果从 ECE 0.466 降到 0.081,多语言检查点从 0.314 降到 0.106。这个改进说明校准接口有实际价值,也说明默认权重不能当作已经校准的生产概率。

仓库同时明确写到:随包发布的检查点普遍过度自信,多语言检查点甚至没有内置拟合温度。把 3 选项数据上得到的阈值迁到 20 选项问题,会破坏覆盖率与准确率的对应关系。更稳妥的做法是把 answer_confidence 和归一化熵型 confidence 分开使用:前者是温度缩放拟合的对象,后者描述整个分布有多集中,两者不在同一尺度,不能共享一个阈值。

证据应该怎样读

仓库提供了可复现实验、原始结果文件和图表。下面只摘取能够对应到具体设置的数字,不把不同数据集、不同检查点和第三方公布值混成一个“总冠军”结论。

Laya consolidated benchmark:应用工作流、英语与非英语、T4 延迟、批处理与类型化决策基准
仓库汇总图。应用工作流、语言分组、单条与批处理延迟、typed-decisions 微调结果来自不同实验设置,不能把其中任何一列单独外推成通用能力。

在多语言 T4 测量中,单问题 p50 为 32.8 ms;10 个问题批处理总耗时 72.3 ms,折合每问题 7.2 ms。路由器把 MASSIVE 中达到“随机基线三倍以上”的语言从 23/51 提升到 48/51。这个结果说明按脚本与语言分流有明确收益,但它也提醒我们,剩下的困难语言并没有因为加了路由器就全部解决。

证据数值可以怎么读
多语言单问题延迟32.8 ms p50T4 上的专用决策层延迟很低
10 问题批处理72.3 ms,约 7.2 ms/问题共享前向和批处理带来吞吐优势
Router 可用语言23/51 提升到 48/51路由对多语言覆盖有实质帮助
typed-decisions 微调结果0.766 准确率对应基准训练集微调后的检查点
英语校准ECE 0.466 降至 0.081按留出数据重拟合温度后改善明显
约 4000 token 前的长文请求20 条中 16 至 18 条正确有可用信号,但必须按自己的文档验证

与 TypeSafe Jev 的对照需要克制解读

仓库把 0.727 的 Jev 数值标为第三方已发表结果,而不是在相同硬件、相同数据顺序和相同训练预算下完成的受控对比。Laya 的 0.766 又来自在对应训练集上微调过的检查点。两者可以提示 Laya 在某些任务上具备竞争力,不能被写成“全面优于 Jev”。如果要做采购决策,应固定数据、提示、候选顺序、硬件和超时,重新跑一次双方同条件的比较。

Laya 与 TypeSafe Jev 在公开数据集、应用工作流、语言、速度、校准和预加载成本上的对照
与 Jev 的多面板对照。图中的 Jev 数字是第三方公布值,不能当作同一实验台上的受控胜负;对实际业务最重要的仍是自己的数据和成本约束。
多语言检查点在 8192 上下文设置下,不同前置文档长度对 20 条支持请求正确数的影响
长文实验把支持请求放在无关文档末尾,测量窗口长度对正确数的影响。约 4000 token 之后结果波动明显,说明“支持 8192 token”不等于“在 8192 token 上都同样可靠”。

必须提前知道的失败边界

这个项目值得记录的地方之一,是作者没有把失败用例藏在营销文案后面。下面这些限制直接决定它能否进入生产,而不是可选的脚注。

  • 零样本的类型化决策接近随机。基础检查点在对应测试上约为 0.36,随机基线约 0.318,多数类约 0.461;0.766 属于针对该基准训练集微调后的检查点,不能拿来代表开箱即用能力。
  • 候选越多,区分越困难。选择题应控制在约 20 个候选以内;77 个标签时,固定决策头预算会把每个标签压到几乎无法区分。
  • 否定语义不可靠。诸如“不要取消账户”仍可能被高置信度地判成取消,语义相近的标签尤其容易混淆。
  • 改写是非题标签会引入标签偏差。模型有时跟随标签,而不是读取状态;任何标签重命名都必须重新验收。
  • 长文概率不是全文概率。决定答案的窗口概率不能直接解释为整篇文档的校准置信度。
  • 审核模型在留出数据上表现弱。toxic-chat 上约 0.530 准确率、0.400 macro-F1,演示样例不能代表真实流量。
  • 动作头目前不可依赖。仓库报告的动作概率几乎总接近 1,给定 AUROC 约为 0.30,不能用它替代权限系统。
  • 多语言评分存在位置偏差。有序量表上可能偏向某些等级,必须按语言和量表分别检查。

还有一个容易被误解的点是“没有生成,所以没有幻觉”。更准确的说法是:Laya 不会生成一段需要解析的文本,因而没有生成式语法解析失败;它仍可能依据错误标签、错误指令或训练偏差做出错误判断。可靠性来自数据、校准、监控和回退策略,不来自输出恰好是 JSON。

适合怎样落地

最稳妥的起点不是把 Laya 放到用户直接面对的最终决策上,而是让它成为一条可观测的辅助通道:用历史工单做离线回放,用真实语言分布评估路由,用每个候选数量分别拟合温度,再选择低风险动作逐步灰度。上线前至少要准备以下检查。

  1. 固定一套独立留出集,按语言、文本长度、候选数量和类别分层,不使用训练或调参数据评估。
  2. 保存完整概率分布,不只保存 argmax;记录路由模型、实际 token 长度、截断情况、阈值和是否拒答。
  3. 把 answer_confidence、熵型 confidence 与业务置信度分开命名,避免团队误用同一阈值。
  4. 为否定、双重否定、语义相近标签、超长候选和低资源语言设置回归用例。
  5. 保留到大模型或人工审核的回退路径,并针对漏报与误报分别计算成本。
  6. 如果使用长文档扫描,记录答案来自哪个窗口,并对跨窗口冲突做二次规则,而不是直接取单窗概率。

本地运行只需要 Python 3.10 以上。HTTP、MCP、ONNX 和各语言客户端是可选安装项;首次下载模型需要能访问 Hugging Face,离线部署则应提前缓存检查点并固定 revision。没有 GPU 也能运行,但延迟和并发上限必须在目标硬件上实测。多模型同时驻留会显著增加内存,开箱默认最多保留两个检查点,正是为了避免一台机器悄悄加载全部权重。

Laya 的长期价值不在于第一个月就超过谁,而在于给出了一条足够清晰的工程路线:把常见判断变成专用模型,把完整概率留给外层策略,把训练与校准写进可复现实验。对于已经有大量结构化业务决策、又不想每一步都调用生成式大模型的团队,这种“一次前向、类型化输出、按语言路由”的结构比聊天式接口更接近可以运营的系统组件。前提是接受它的边界,并用自建数据把每个决策点重新验一遍。

项目地址:NandhaKishorM/laya。

相关开源项目

作为亚马逊联盟会员,我们可能从符合条件的购买中获得佣金。