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:
- 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.
- 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
- Translate and Localize Content: translate corrected text into other languages.
- Build a Content Moderation Filter: combine correction with moderation in a content pipeline.
- Samples: Text Corrector: text correction demo.
- Samples: Text Rewriter: text rewriting demo.