Getting started

Layar doesn't ship model weights. tools/layar-export/export.py is a maintainer-run Python script that loads a checkpoint through the original laya package and exports it to the files Layar's backends actually load:

Shell
pip install laya torch onnx onnxscript
python tools/layar-export/export.py convaiinnovations/laya --subfolder multilingual \
    -o .artifacts/models/multilingual
  1. laya's own deps, plus the ONNX export tooling

This produces, under the output directory: model.onnx (+ model.onnx.data for larger checkpoints), model.pt (a torch.jit.traced TorchScript module), tokenizer/ (the Hugging Face tokenizer files), and config.json (max length, head budget, calibration temperatures).

If you'd rather not run Python at all, Laya.Core.CheckpointCache downloads an already-exported checkpoint on demand from wherever one is hosted — see the project README's "Downloading a checkpoint on demand" section.

C#
using Laya.Core;
using Laya.Onnx;
using Laya.Tokenization;

var tokenizer = HuggingFaceBpeTokenizer.FromDirectory("path/to/checkpoint/tokenizer");
using var backend = new OnnxDecisionBackend("path/to/checkpoint/model.onnx");
var engine = new DecisionEngine(tokenizer, backend, maxLen: 1024, headMaxLen: 256);

var triage = await engine.PredictAsync(
    "Hi, we were billed twice for March. Please refund the duplicate today.",
    TriageSchema.Instance);

Console.WriteLine($"{triage.Intent.Choice} (confidence {triage.Intent.Confidence:F2})");
  1. or: using Laya.TorchSharp;

maxLen/headMaxLen should match the checkpoint's config.json (1024/256 for the multilingual checkpoint at the time of writing; 512/192 for the English one).

TriageSchema is an IQuestionSchema<TriageResult> — a fixed question set plus how to bind the answers back into a record, so PredictAsync returns TriageResult directly instead of a string-keyed dictionary. It exists because Presets.TriageQuestions()'s shape is fixed at compile time; a runtime-composed question set (e.g. Presets.EmailQuestions(myCategories) with your own categories) has no fixed record to bind to and stays on the dictionary path:

C#
var answers = await engine.PredictAsync(state, Presets.EmailQuestions(myCategories));
var category = answers.Choice("category");
  1. typed accessor, any question id

RouterEngine wraps checkpoint selection and lazy loading (mirroring the Python package's Router): it inspects the state's script/language, picks the right checkpoint, and loads it on first use.

C#
using Laya.Core.Routing;

using var router = new RouterEngine(
    loader: key => BuildEngineFor(key),
    maxLoaded: 2);

var triage = await router.PredictAsync(stateJson, TriageSchema.Instance);
  1. your own: pick a checkpoint dir per ModelKey, build a DecisionEngine

For a choice question with more labels than fit in the token budget (more than ~20, depending on the checkpoint), Shortlist/PredictShortlistAsync embeds the state and each option, keeps the top matches, and predicts over just those:

C#
var (answers, shortlists) = await engine.PredictShortlistAsync(stateText, questions, myEmbeddingProvider);

myEmbeddingProvider is anything implementing IEmbeddingProvider — Layar doesn't ship one, the same way the Python package leaves embed_fn entirely to the caller.

Shell
dotnet run -c Release --project tests/Laya.Benchmarks -- --filter '*'

Runs both Layar.Onnx and Layar.TorchSharp against the same real request through BenchmarkDotNet (proper warmup, memory diagnostics) — the numbers in the README's "Measured" section came from exactly this command.

Shell
dotnet build layar.slnx
dotnet run --project tests/Laya.Tests -c Release

See the project AGENTS.md for the known rough edges (native library loading on macOS, why ONNX export needs the dynamo=True exporter, and what's blocked on this or that toolchain issue) before filing something as a bug.