Table of Contents

Correct Grammar and Spelling

User-submitted text (support tickets, form inputs, chat messages, OCR output) often contains typos, grammar errors, and punctuation problems. LM-Kit.NET's TextCorrection class fixes these issues while preserving the original meaning and structure. It runs locally, so customer text and internal documents never leave your infrastructure. This tutorial builds a grammar correction tool with streaming output and batch processing.


Why Local Text Correction Matters

Two enterprise problems that on-device correction solves:

  1. Customer-facing text quality without data exposure. Support ticket replies, chatbot responses, auto-generated emails. These contain customer names, account details, and business context. Sending them to a cloud grammar API means a third party reads your customer communications. Local correction keeps that data private.
  2. Clean OCR and speech-to-text output. OCR from scanned documents and speech transcriptions produce text with consistent error patterns. A local corrector fixes these at the point of capture, before the data enters your pipeline.

Prerequisites

Requirement Minimum
.NET SDK 8.0+
VRAM 4+ GB
Disk ~3 GB free for model download

Step 1: Create the Project

dotnet new console -n CorrectionQuickstart
cd CorrectionQuickstart
dotnet add package LM-Kit.NET

Step 2: Basic Text Correction

The TextCorrection class takes text in and returns corrected text. One method, one result:

using System.Text;
using LMKit.Model;
using LMKit.TextEnhancement;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

// ──────────────────────────────────────
// 1. Load model
// ──────────────────────────────────────
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("gemma3:4b",
    downloadingProgress: (_, len, read) =>
    {
        if (len.HasValue) Console.Write($"\r  Downloading: {(double)read / len.Value * 100:F1}%   ");
        return true;
    },
    loadingProgress: p => { Console.Write($"\r  Loading: {p * 100:F0}%   "); return true; });
Console.WriteLine("\n");

// ──────────────────────────────────────
// 2. Correct sample texts
// ──────────────────────────────────────
var corrector = new TextCorrection(model);

string[] samples =
{
    "Their going to the store tommorow but they dont know what there buying.",
    "The companys revenue have increased by 15% this quater, which are very good.",
    "Me and him was talking about the new polisy and we doesnt agree with it.",
    "I recieved you're email and will definately get back to you by wenesday.",
    "The datas shows that less people are using there phones for this purpuse."
};

Console.WriteLine("Correcting text:\n");

foreach (string text in samples)
{
    Console.ForegroundColor = ConsoleColor.DarkGray;
    Console.WriteLine($"  Original:  {text}");
    Console.ResetColor();

    string corrected = corrector.Correct(text);

    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"  Corrected: {corrected}");
    Console.ResetColor();
    Console.WriteLine();
}

Step 3: Streaming Correction Output

For longer texts, stream the corrected output token by token using the AfterTextCompletion event:

using System.Text;
using LMKit.Model;
using LMKit.TextEnhancement;
using LMKit.TextGeneration;
using LMKit.TextGeneration.Chat;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("gemma3:4b",
    downloadingProgress: (_, len, read) =>
    {
        if (len.HasValue) Console.Write($"\r  Downloading: {(double)read / len.Value * 100:F1}%   ");
        return true;
    },
    loadingProgress: p => { Console.Write($"\r  Loading: {p * 100:F0}%   "); return true; });
Console.WriteLine("\n");

var corrector = new TextCorrection(model);

// Subscribe to streaming events
corrector.AfterTextCompletion += (sender, e) =>
{
    if (e.SegmentType == TextSegmentType.UserVisible)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.Write(e.Text);
        Console.ResetColor();
    }
};

// Interactive correction loop
Console.WriteLine("Enter text to correct (or 'quit' to exit):\n");

while (true)
{
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.Write("Input: ");
    Console.ResetColor();

    string? input = Console.ReadLine();
    if (string.IsNullOrWhiteSpace(input) || input.Equals("quit", StringComparison.OrdinalIgnoreCase))
        break;

    Console.Write("Fixed: ");
    corrector.Correct(input, new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token);
    Console.WriteLine("\n");
}

Step 4: Batch File Correction

Process a file of text entries and export corrected versions:

using System.Text;
using LMKit.Model;
using LMKit.TextEnhancement;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

// ──────────────────────────────────────
// 1. Load model
// ──────────────────────────────────────
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("gemma3:4b",
    downloadingProgress: (_, len, read) =>
    {
        if (len.HasValue) Console.Write($"\r  Downloading: {(double)read / len.Value * 100:F1}%   ");
        return true;
    },
    loadingProgress: p => { Console.Write($"\r  Loading: {p * 100:F0}%   "); return true; });
Console.WriteLine("\n");

// ──────────────────────────────────────
// 2. Correct sample texts
// ──────────────────────────────────────
var corrector = new TextCorrection(model);

string[] lines = File.ReadAllLines("raw_feedback.txt");
var correctedLines = new List<string>();
int fixedCount = 0;

Console.WriteLine($"Correcting {lines.Length} lines...\n");

foreach (string line in lines)
{
    if (string.IsNullOrWhiteSpace(line))
    {
        correctedLines.Add(line);
        continue;
    }

    string corrected = corrector.Correct(line);
    correctedLines.Add(corrected);

    if (corrected != line)
    {
        fixedCount++;
    }
}

File.WriteAllLines("corrected_feedback.txt", correctedLines);
Console.WriteLine($"Done. Fixed {fixedCount}/{lines.Length} lines.");
Console.WriteLine("Output: corrected_feedback.txt");

Step 5: Correction with Diff Highlighting

Show users exactly what changed by comparing original and corrected text word by word:

using System.Text;
using LMKit.Model;
using LMKit.TextEnhancement;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

// ──────────────────────────────────────
// 1. Load model
// ──────────────────────────────────────
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("gemma3:4b",
    downloadingProgress: (_, len, read) =>
    {
        if (len.HasValue) Console.Write($"\r  Downloading: {(double)read / len.Value * 100:F1}%   ");
        return true;
    },
    loadingProgress: p => { Console.Write($"\r  Loading: {p * 100:F0}%   "); return true; });
Console.WriteLine("\n");

// ──────────────────────────────────────
// 2. Correct sample texts
// ──────────────────────────────────────
var corrector = new TextCorrection(model);

void ShowDiff(string original, string corrected)
{
    string[] origWords = original.Split(' ');
    string[] fixedWords = corrected.Split(' ');

    int maxLen = Math.Max(origWords.Length, fixedWords.Length);

    Console.Write("  ");
    for (int i = 0; i < maxLen; i++)
    {
        string origWord = i < origWords.Length ? origWords[i] : "";
        string fixedWord = i < fixedWords.Length ? fixedWords[i] : "";

        if (origWord != fixedWord)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Write(fixedWord);
            Console.ResetColor();
        }
        else
        {
            Console.Write(fixedWord);
        }

        if (i < maxLen - 1) Console.Write(" ");
    }

    Console.WriteLine();
}

// Usage
string original = "The companys revenue have increased by 15% this quater.";
string corrected = corrector.Correct(original);

Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($"  Before: {original}");
Console.ResetColor();
Console.Write("  After:  ");
ShowDiff(original, corrected);

Step 6: Combine Correction with Rewriting

Fix grammar first, then adjust the tone. The TextRewriter reshapes corrected text into different communication styles:

using System.Text;
using LMKit.Model;
using LMKit.TextEnhancement;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("gemma3:4b",
    loadingProgress: p => { Console.Write($"\rLoading: {p * 100:F0}%   "); return true; });
Console.WriteLine("\n");

var corrector = new TextCorrection(model);
var rewriter = new TextRewriter(model);

string rawInput = "me and the team doesnt think the new polisy is good for nobody.";

// Step 1: Fix grammar
string corrected = corrector.Correct(rawInput);
Console.WriteLine($"Corrected:    {corrected}");

// Step 2: Rewrite professionally
string professional = rewriter.Rewrite(corrected, TextRewriter.CommunicationStyle.Professional);
Console.WriteLine($"Professional: {professional}");

// Step 2 (alt): Rewrite concisely
string concise = rewriter.Rewrite(corrected, TextRewriter.CommunicationStyle.Concise);
Console.WriteLine($"Concise:      {concise}");

This two-step pipeline is useful for cleaning up informal input (chat messages, voice transcriptions) before it becomes customer-facing text.


Common Issues

Problem Cause Fix
Correction changes the meaning Model over-corrects informal phrasing Use a smaller model (gemma3:1b) which makes fewer assumptions
Output includes reasoning text Streaming handler not filtering segment types Only display TextSegmentType.UserVisible segments
Slow on long paragraphs Full text processed as one unit Split into sentences before correcting; process each separately
No changes on correct text Text already correct Expected behavior. Compare input/output to detect if changes were made
Timeout on complex text Model takes too long Set a CancellationTokenSource with a timeout (e.g., 2 minutes)

Next Steps

Share