Table of Contents

Detect Sarcasm and Irony in Text

Sarcasm inverts the literal meaning of words. "Great job breaking the build" means the opposite of what it says. Automated systems that take text at face value misclassify sarcastic complaints as praise and sarcastic praise as neutral. LM-Kit.NET's SarcasmDetection class identifies sarcastic text with a confidence score, which is critical for accurate sentiment analysis, content moderation, and customer feedback processing. This tutorial builds a sarcasm detector with batch processing and integration with sentiment analysis.


Why Sarcasm Detection Matters

Two practical problems that sarcasm detection solves:

  1. Sentiment accuracy. "Oh wonderful, another update that breaks everything" registers as positive sentiment without sarcasm detection. Layering sarcasm detection on top of sentiment analysis flips the interpretation, giving you accurate signal from customer feedback, reviews, and social media.
  2. Content moderation coverage. Sarcastic hostility bypasses keyword-based filters. "What a brilliant idea, did you come up with that all by yourself?" contains no toxic words but is clearly mocking. Sarcasm detection catches veiled negativity that other classifiers miss.

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 SarcasmQuickstart
cd SarcasmQuickstart
dotnet add package LM-Kit.NET

Step 2: Basic Sarcasm Detection

using System.Text;
using LMKit.Model;
using LMKit.TextAnalysis;

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. Detect sarcasm in sample texts
// ──────────────────────────────────────
var detector = new SarcasmDetection(model);

string[] samples =
{
    "This is a really helpful tutorial, I learned a lot from it.",
    "Oh sure, because restarting the server always fixes everything.",
    "The new interface is clean and easy to navigate.",
    "Wow, only 45 minutes to load a page. What amazing performance.",
    "I appreciate the team's quick response to the outage.",
    "Yeah right, because who needs documentation anyway.",
    "The API design follows REST conventions well.",
    "Oh fantastic, another meeting that could have been an email."
};

Console.WriteLine("Sarcasm detection:\n");

foreach (string text in samples)
{
    bool isSarcastic = detector.IsSarcastic(text);
    float confidence = detector.Confidence;

    Console.ForegroundColor = isSarcastic ? ConsoleColor.Yellow : ConsoleColor.Green;
    Console.Write($"  {(isSarcastic ? "SARCASTIC" : "SINCERE  ")}");
    Console.ResetColor();
    Console.ForegroundColor = ConsoleColor.DarkGray;
    Console.Write($" ({confidence:P0}) ");
    Console.ResetColor();
    Console.WriteLine(text.Length > 60 ? text.Substring(0, 60) + "..." : text);
}

Step 3: Combine with Sentiment Analysis

Sarcasm flips sentiment. Use both classifiers together for accurate interpretation:

using System.Text;
using LMKit.Model;
using LMKit.TextAnalysis;

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. Detect sarcasm in sample texts
// ──────────────────────────────────────
var detector = new SarcasmDetection(model);

string[] samples =
{
    "This is a really helpful tutorial, I learned a lot from it.",
    "Oh sure, because restarting the server always fixes everything.",
    "The new interface is clean and easy to navigate.",
    "Wow, only 45 minutes to load a page. What amazing performance.",
    "I appreciate the team's quick response to the outage.",
    "Yeah right, because who needs documentation anyway.",
    "The API design follows REST conventions well.",
    "Oh fantastic, another meeting that could have been an email."
};

Console.WriteLine("Sarcasm detection:\n");

foreach (string text in samples)
{
    bool isSarcastic = detector.IsSarcastic(text);
    float confidence = detector.Confidence;

    Console.ForegroundColor = isSarcastic ? ConsoleColor.Yellow : ConsoleColor.Green;
    Console.Write($"  {(isSarcastic ? "SARCASTIC" : "SINCERE  ")}");
    Console.ResetColor();
    Console.ForegroundColor = ConsoleColor.DarkGray;
    Console.Write($" ({confidence:P0}) ");
    Console.ResetColor();
    Console.WriteLine(text.Length > 60 ? text.Substring(0, 60) + "..." : text);
}

var sarcasm = new SarcasmDetection(model);
var sentiment = new SentimentAnalysis(model) { NeutralSupport = true };

string[] reviews =
{
    "Best purchase I've ever made. Works perfectly.",
    "Oh great, it broke after two days. Best purchase ever.",
    "The battery life is terrible, barely lasts an hour.",
    "Sure, who needs a working charger included in the box."
};

Console.WriteLine("Combined sarcasm + sentiment analysis:\n");

foreach (string review in reviews)
{
    SentimentAnalysis.SentimentCategory sent = sentiment.GetSentimentCategory(review);
    bool isSarcastic = sarcasm.IsSarcastic(review);

    // Adjust sentiment if sarcasm detected
    string rawSentiment = sent.ToString();
    string adjustedSentiment = rawSentiment;

    if (isSarcastic && sent == SentimentAnalysis.SentimentCategory.Positive)
        adjustedSentiment = "Negative (sarcastic positive)";
    else if (isSarcastic && sent == SentimentAnalysis.SentimentCategory.Neutral)
        adjustedSentiment = "Negative (sarcastic neutral)";

    Console.ForegroundColor = ConsoleColor.DarkGray;
    Console.Write("  Raw: ");
    Console.ResetColor();
    Console.Write($"{rawSentiment,-10}");

    if (isSarcastic)
    {
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.Write(" [sarcasm] ");
        Console.ResetColor();
    }
    else
    {
        Console.Write("           ");
    }

    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.Write($"Adjusted: {adjustedSentiment,-35}");
    Console.ResetColor();

    string preview = review.Length > 45 ? review.Substring(0, 45) + "..." : review;
    Console.WriteLine(preview);
}

Step 4: Interactive Detector

using System.Text;
using LMKit.Model;
using LMKit.TextAnalysis;

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. Detect sarcasm in sample texts
// ──────────────────────────────────────
var detector = new SarcasmDetection(model);

Console.WriteLine("Enter text to check for sarcasm (or 'quit' to exit):\n");

while (true)
{
    Console.ForegroundColor = ConsoleColor.Green;
    Console.Write("Text: ");
    Console.ResetColor();

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

    bool isSarcastic = detector.IsSarcastic(input);
    float confidence = detector.Confidence;

    Console.ForegroundColor = isSarcastic ? ConsoleColor.Yellow : ConsoleColor.Green;
    Console.Write($"  {(isSarcastic ? "Sarcastic" : "Sincere")}");
    Console.ResetColor();
    Console.WriteLine($" (confidence: {confidence:P0})\n");
}

Step 5: Batch Processing

Scan customer feedback for sarcasm and export results:

using System.Text;
using LMKit.Model;
using LMKit.TextAnalysis;

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. Detect sarcasm in sample texts
// ──────────────────────────────────────
var detector = new SarcasmDetection(model);

string[] feedback = File.ReadAllLines("customer_feedback.txt")
    .Where(l => !string.IsNullOrWhiteSpace(l))
    .ToArray();

var output = new List<string>();
output.Add("text,is_sarcastic,confidence");

int sarcasticCount = 0;

foreach (string line in feedback)
{
    bool isSarcastic = detector.IsSarcastic(line);
    float confidence = detector.Confidence;

    if (isSarcastic) sarcasticCount++;

    output.Add($"\"{line.Replace("\"", "\"\"")}\",{isSarcastic},{confidence:F2}");
}

File.WriteAllLines("sarcasm_results.csv", output);

Console.WriteLine($"Processed {feedback.Length} entries");
Console.WriteLine($"  Sarcastic: {sarcasticCount} ({(double)sarcasticCount / feedback.Length:P0})");
Console.WriteLine($"  Sincere:   {feedback.Length - sarcasticCount} ({(double)(feedback.Length - sarcasticCount) / feedback.Length:P0})");

Step 6: Confidence-Based Filtering

Not all detections are equally certain. Use confidence thresholds to route edge cases for human review:

using System.Text;
using LMKit.Model;
using LMKit.TextAnalysis;

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. Detect sarcasm in sample texts
// ──────────────────────────────────────
var detector = new SarcasmDetection(model);

var results = new List<(string Text, bool IsSarcastic, float Confidence)>();

foreach (string text in feedback)
{
    bool isSarcastic = detector.IsSarcastic(text);
    results.Add((text, isSarcastic, detector.Confidence));
}

var confident = results.Where(r => r.Confidence >= 0.85f).ToList();
var uncertain = results.Where(r => r.Confidence < 0.85f).ToList();

Console.WriteLine($"Confident detections: {confident.Count}");
Console.WriteLine($"Needs human review:   {uncertain.Count}\n");

if (uncertain.Count > 0)
{
    Console.WriteLine("Uncertain cases:");
    foreach (var (text, isSarcastic, conf) in uncertain)
    {
        string label = isSarcastic ? "sarcastic?" : "sincere?";
        string preview = text.Length > 50 ? text.Substring(0, 50) + "..." : text;
        Console.WriteLine($"  [{label}] ({conf:P0}) {preview}");
    }
}

Common Issues

Problem Cause Fix
Dry humor not detected Subtle sarcasm without obvious markers Use a larger model (gemma3:12b) for better nuance
Too many false positives Enthusiastic positive text flagged as sarcastic Filter by confidence (only flag above 0.80)
Low confidence on short text Not enough context Provide surrounding context or combine with other signals (sentiment, emotion)
Context-dependent sarcasm missed Text is ambiguous without conversation history Include the full message thread, not just the single message

Next Steps

Share