All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[1.0.0] - 2026-11-08
First release.
Changed
- The neural runtime and trainer use OneModel 1.2.0. Checkpoints stream through OneModel, including files over 2 GiB; single-resource embedded encoders and embeddings load in place and keep their resource streams open for the lifetime of the model. Split-resource graphs still use the buffered concatenation path.
Added
TransformerEncoder.FromStreamsandSentenceEmbedder.Loadhave stream-ownership overloads (leaveOpen). They opt into in-place loading for embedded graphs; the existing overloads keep their buffered lifetime contract. WithleaveOpen: false, the model stream is closed on disposal or load failure; tokenizer streams remain caller-owned.EntityRecognizer.MinConfidencedrops decoded entities below a confidence (default 0, which changes nothing); it is saved in the model file only when above 0, so existing files load unchanged. The Chinese entity model ships with 0.6.
Changed (breaking)
Nlp.Entities(text),Nlp.Lemmas(text)andNlp.Analyze(text): when the language detector is unsure (confidence below 0.8) and has no model for its answer, the text is read in the most likely of the languages that have a model, so "Paris", "Ok" or a name is read by the model you referenced instead of throwing from one verb and silently losing entities in the other. Confident answers are unchanged: French text without a French model is still an error naming the package.A missing model throws
ModelNotAvailableException(still anInvalidOperationException) withLanguage,TierandPackageIds; messages name only packages that exist.Nlpverbs andPipeline.Process/ProcessManyrefuse text longer thanNlp.MaxLength/Pipeline.MaxLength(1,000,000 characters by default) with anArgumentOutOfRangeException. Texts above that size that used to be accepted now need a raised limit.Tokenizer.FromFile("vocab.txt")no longer reads letters without a case pair (such as U+211D, whichbert-base-uncasedcontains) as evidence of a cased vocabulary: that vocabulary is now uncased, as it is.Recognizer.Recognize(text, context = default)is two overloads,Recognize(text)andRecognize(text, context); adefault(DateTimeOffset)reference date throwsArgumentExceptionhere and inNlp.DatesandNlp.ParseDate.Language.Unknown.ToString()is"Unknown"(itsCodeis still empty).PatternFormatExceptionandRecognizerRulesExceptioncarryLineNumber(andBytePositionInLinefor patterns); pattern messages no longer end in(Parameter 'name'), and rule-line messages no longer repeat "Line N" inside the text they pass to the constructor.Token.TextandSpan.Textare nowstrings cut fromDoc.Text, sotoken.Text == "x",doc.Select(t => t.Text)andToDictionary(s => s.Text)work. The copy-free view isTextSpan(aReadOnlySpan<char>). Migration: code that wanted the span, such astoken.Text.SequenceEqual(...)or aReadOnlySpan<char>local, readstoken.TextSpan;token.Text.ToString()becomestoken.Text.One default word tokenizer:
PhraseMatcher,EntityRulerphrases andJsonlOptions.Tokenizernow default to the tokenizerPipeline.Blankuses (UaxWordTokenizer.Default) instead ofRuleWordTokenizer. AnEntityRuleradded to a pipeline splits its phrases with that pipeline's tokenizer unless it was given one. New:new PhraseMatcher(pipeline)andJsonlReader.Read(path | stream | reader, pipeline). Migration: calls that passednullfor the string table with options,JsonlReader.Read(path, null, options), are now ambiguous; writeJsonlReader.Read(path, strings: null, options: options), or pass the pipeline.EntityRulerhas no indexer. It is a collection:new EntityRuler { { "CASE_NO", Pattern.Shape("dd-dddd") } }(orruler.Add(label, pattern)). Migration:new EntityRuler { ["X"] = p }becomesnew EntityRuler { { "X", p } }.Serialization verbs:
Doc.WriteTo(Stream)isDoc.WriteBinary(Stream),Doc.FromBytes(Stream)isDoc.FromStream(Stream), andDocBin.WriteTo(Stream)isDocBin.WriteBinary(Stream).TrainOptions,TrainResult,TrainProgress,DevScores(wasRecursiveAI.OneNlp.Neural.Training) andExample(wasRecursiveAI.OneNlp.Data) are in theRecursiveAI.OneNlpnamespace. Migration: deleteusing RecursiveAI.OneNlp.Neural.Training;;using RecursiveAI.OneNlp.Data;is still needed for the readers andAlignment.Training a head that already has weights (a packaged pipeline) on data with labels it lacks: an
EntityRecognizernow appends the new labels and keeps its weights (spaCy's "add a label, then train"); before, the labels were silently dropped. ATagger,Morphologizer,EditTreeLemmatizer,Parser,SpanCategorizerorTextClassifierthrowsArgumentExceptionnaming the unknown values (part-of-speech and feature combinations, edit trees seen at leastminClassCounttimes, dependency relations, span labels, or categories) instead of skipping them.ITrainableComponent.ExtendLabelsis the new member (a default interface method).Pipeline.Add("sentencizer")on a pipeline for a language uses that language's segmentation data (EnglishMr.no longer ends a sentence); it used the neutral data before. Settings that namedataorlanguagestill win.SpanCategorizerdefault threshold is 0.8 (was 0.5), andThresholdis settable. At 0.8, chosen on a development split, the English UNER precision is 76.0 (was 70.3; spaCy 75.4) with F1 63.8 (was 64.4). Saved models keep the threshold they were saved with.ModelNotAvailableExceptionfor a language no published package covers (for example Basque) has an emptyPackageIdsand says so, instead of naming a package that cannot help; a package already referenced is never named.SpanandTokenwrite theirstartandendin code points throughSystem.Text.Json, as theDocform does (they were UTF-16 offsets). The two differ only after a character outside the Basic Multilingual Plane;StartCharandIdxare unchanged.default(Token)anddefault(Span)members throwInvalidOperationException(they threwNullReferenceException).Example.CreatethrowsArgumentOutOfRangeExceptionfor an offset outside the text.Doc.FromJsonmessages no longer end in(Parameter 'name').Pipeline.ProcessManytakes a trailingCancellationToken(source compatible; a method group or delegate bound to the old signature must be rebuilt).
Added
Nlp.Entities(text, types, threshold)for open-type entities with a probability threshold (the default stays 0.5);docs/open-ner.mdsays where the recognizer comes from.Nlp.Entities,Nlp.Analyze,Nlp.AnalyzeManyandNlp.Lemmasoverloads that take aLanguage;Nlp.MaxLength;Pipeline.MaxLength.Tokenizer.FromWordPieceVocabulary(path or stream, WordPieceVocabularyOptions)states the casing of avocab.txt;Tokenizer.EncodeToIds(text, addSpecialTokens)returns anint[].ModelNotAvailableException.System.Text.Jsonworks onDoc(it writes and reads theDoc.ToJsonshape), and writesSpanandTokenas objects with character offsets, label and text; no reflection, so it also works under NativeAOT with a source-generated context.Example.Create(pipeline, text, entities, cats)builds a training example from character offsets, tokenized by the pipeline.WriteJsonAsync,WriteBinaryAsync,FromJsonAsyncandFromStreamAsynconDoc,WriteBinaryAsyncandFromStreamAsynconDocBin, andDocBinReaderis anIEnumerable<Doc>withReadAsyncandReadAllAsync.Documentation for the first release: a rewritten README and package readmes,
docs/results-table.md(every quality row with its reference and interval), side-by-side "reads like" samples (docs/reads-like.md), migration pages for Catalyst, Microsoft.Recognizers.Text, Lingua and Microsoft.ML.Tokenizers, guides for training, the fast tier and classification, andsamples/OneNlp.Samples, which builds and runs every sample the docs show.Recognizer comparison files for Turkish and Hindi test sets, and refreshed English ones that include date/time.
Sentence embeddings:
IEmbedder,SentenceEmbedder(sentence-transformers-format ONNX models, CLS/mean/max pooling, normalization, batching, opt-in int8),Nlp.Embed/Nlp.EmbedMany,EmbedderPackages,Tokenizer.IsSpecialToken, and theRecursiveAI.OneNlp.Models.Embeddingspackage (bge-small-en-v1.5, MIT). Parity and MTEB results:docs/embeddings.md.French:
SegmentationData.French(elision, inversion clitics,du/au/aux/desas multiword-token pieces, digit groups) withTryMatchMultiword, the recipes inrecipes/frand theRecursiveAI.OneNlp.Models.Frenchpackage.Dutch:
SegmentationData.Dutch(apostrophe words and short forms, abbreviations, dash amounts), the recipe inrecipes/nl/fastand theRecursiveAI.OneNlp.Models.Dutchpackage (tagging, morphology, lemmas, parser, entities on UD Dutch-Alpino and WikiNER Dutch).Language identification:
LanguageDetectornames the language of a text (75 languages, a 3.6 MB model embedded at build time fromONENLP_LANGID_WEIGHTS) from one word to a document, with confidences,Restrict(languages)and mixed-language runs (DetectSpans).Nlp.DetectLanguageis the happy path, andNlp.Analyze,Nlp.TokensandNlp.Sentencesnow use the detected language's segmentation data. Method, model file and measurements are indocs/language-id.md; the recipe and provenance log are inrecipes/langid.RecursiveAI.OneNlp.Models.English: the English fast-tier model package (tags, morphology, lemmas, dependency parse, PER/ORG/LOC entities) with weights embedded;ModelPackages(the registry model packages use),Nlp.Entities,Nlp.Lemmas,Nlp.AnalyzeMany, andNlp.Analyzefilling every annotation when a model package is referenced. Seedocs/model-packages.md.Recognizers (
Recognizer,RecognizerComponent,Quantity,SequenceMatch,Nlp.Quantities,Nlp.Numbers,Nlp.Sequences): numbers and ordinals in digits and words, percentages, measures (length, mass, volume, area, speed, information, temperature, age), money with ISO 4217 codes, e-mail, URL, IPv4/IPv6, phone-like sequences (unvalidated), hashtags, mentions and GUIDs, from per-language rule data that is embedded in the assembly or loaded from files. English rules are included; seedocs/recognizers.md.Recognizers: a number in words repeated in brackets is one result (
sixty (60) days,five thousand dollars ($5,000); a disagreeing numeral lowers the confidence and is not merged); durations of business days (ten business days,P10BD),calendarunits, andTemporalMatch.CountandTemporalMatch.Unit(DurationUnit) on every duration;in 3 business daysresolves to a date; compound measures (5 feet 3 inchesis 63 inches); Indian digit grouping (12,34,567) and scientific notation (6.02e23,1.5×10^3);1,000thas an ordinal;1,000 poundsis money in a money context;wait a secondis a duration;no one,one of themandfor the first timeare not numbers; the ISO 4217 code list was checked against the published List One.Repository skeleton:
RecursiveAI.OneNlpandRecursiveAI.OneNlp.Neuralprojects, tests, a NativeAOT smoke app, a benchmark project and a fuzz harness.Doc,TokenandSpan: columnar documents with character offsets into the original string, interned attribute columns, entities, sentences and confidences.Pipeline,IPipelineComponent,IWordTokenizerandComponentRegistry: ordered components, batch and streaming processing, and reflection-free registration by name.Tokenizer,TokenEncodingand the format loaders' entry points (Tokenizer.FromFile,Tokenizer.FromStream): the contract the tokenizer families will implement.RuleWordTokenizerand theNlp.Analyzestatic.IEncoder, the first shape of the encoder seam, inRecursiveAI.OneNlp.Neural.Doc.ToJson/Doc.FromJsonin the shape spaCy documents forDoc.to_json(offsets in code points, extra fields for norm, confidences and sentence boundaries), and a compact, versioned, checksummed binary form (Doc.ToBytes/Doc.FromBytes), plusDocBinand a streamingDocBinReaderthat share one string table. Readers reject malformed, truncated and oversized input withInvalidDataException. Formats are specified indocs/json-format.mdanddocs/binary-format.md.
Fixed
bench/OneNlp.Benchruns out of process under BenchmarkDotNet (the assembly name now matches the project file);tools/oracles/bench_recognizers_lang.shbuilds its own tools, uses small inputs where the comparator is slow (Turkish, Hindi) and times out each run.