Table of Contents

Analyze Customer Sentiment at Scale

Sentiment analysis classifies text as positive, negative, or neutral. LM-Kit.NET provides dedicated APIs for sentiment analysis and emotion detection that work out of the box with no training data, no prompt engineering, and no cloud dependencies. This tutorial builds a working system that processes customer feedback, detects emotions, and exports results.


Why Local Sentiment Analysis Matters

Two enterprise problems that on-device sentiment analysis solves:

  1. Customer data privacy. Support tickets, NPS surveys, and product reviews contain personally identifiable information. Sending them to cloud APIs creates data processing agreements, GDPR notifications, and vendor lock-in. Local inference keeps customer data entirely within your infrastructure.
  2. High-volume processing without per-call costs. Cloud sentiment APIs charge per request. At scale (100K+ reviews, continuous social monitoring), costs compound. A local model runs unlimited classifications at zero marginal cost after the initial hardware investment.

Prerequisites

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

Step 1: Create the Project

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

Step 2: Basic Sentiment Analysis

This program classifies text as positive, negative, or neutral with a confidence score.

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:1b",
    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. Create sentiment analyzer
// ──────────────────────────────────────
var sentiment = new SentimentAnalysis(model)
{
    NeutralSupport = true  // enable positive/negative/neutral (set false for binary)
};

// ──────────────────────────────────────
// 3. Classify sample reviews
// ──────────────────────────────────────
string[] reviews =
{
    "This product exceeded my expectations. The build quality is outstanding.",
    "Terrible customer service. Waited 3 hours on hold and got no help.",
    "The item arrived on time. It works as described.",
    "I love this app! It saved me hours of work every week.",
    "Broken on arrival. Returning immediately. Very disappointed.",
    "It's okay. Nothing special but gets the job done."
};

Console.WriteLine("Analyzing customer reviews:\n");

foreach (string review in reviews)
{
    SentimentCategory category = sentiment.GetSentimentCategory(review);
    float confidence = sentiment.Confidence;

    Console.ForegroundColor = category switch
    {
        SentimentCategory.Positive => ConsoleColor.Green,
        SentimentCategory.Negative => ConsoleColor.Red,
        _ => ConsoleColor.Yellow
    };

    Console.Write($"  [{category,-8}]");
    Console.ResetColor();
    Console.ForegroundColor = ConsoleColor.DarkGray;
    Console.Write($" ({confidence:P0}) ");
    Console.ResetColor();

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

// ──────────────────────────────────────
// 4. Interactive mode
// ──────────────────────────────────────
Console.WriteLine("\nEnter text to analyze (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;

    SentimentCategory result = sentiment.GetSentimentCategory(input);
    Console.WriteLine($"  Sentiment: {result} (confidence: {sentiment.Confidence:P0})\n");
}

Step 3: Emotion Detection

Go beyond positive/negative with fine-grained emotion classification: happiness, anger, sadness, fear, or neutral.

using LMKit.TextAnalysis;

var emotion = new EmotionDetection(model)
{
    NeutralSupport = true
};

string[] messages =
{
    "I'm so excited about the new features in this release!",
    "This bug has been driving me crazy for weeks.",
    "We lost a major client today. The team is devastated.",
    "What if the deployment fails during peak hours?",
    "The update installed successfully. No issues to report."
};

foreach (string message in messages)
{
    EmotionCategory category = emotion.GetEmotionCategory(message);
    float confidence = emotion.Confidence;

    Console.WriteLine($"  {category,-10} ({confidence:P0})  {message}");
}

Expected output:

  Happiness  (94%)  I'm so excited about the new features in this release!
  Anger      (89%)  This bug has been driving me crazy for weeks.
  Sadness    (91%)  We lost a major client today. The team is devastated.
  Fear       (85%)  What if the deployment fails during peak hours?
  Neutral    (88%)  The update installed successfully. No issues to report.

Step 4: Batch Processing a CSV File

Process a file of customer feedback and export results:

using LMKit.TextAnalysis;

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

// Read feedback from a file (one review per line)
string[] lines = File.ReadAllLines("feedback.csv");
var results = new List<string>();
results.Add("text,sentiment,confidence");

int positive = 0, negative = 0, neutral = 0;

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

    SentimentCategory category = sentiment.GetSentimentCategory(line);
    float confidence = sentiment.Confidence;

    results.Add($"\"{line.Replace("\"", "\"\"")}\",{category},{confidence:F3}");

    switch (category)
    {
        case SentimentCategory.Positive: positive++; break;
        case SentimentCategory.Negative: negative++; break;
        default: neutral++; break;
    }
}

File.WriteAllLines("results.csv", results);

int total = positive + negative + neutral;
Console.WriteLine($"Processed {total} reviews:");
Console.WriteLine($"  Positive: {positive} ({(double)positive / total:P0})");
Console.WriteLine($"  Negative: {negative} ({(double)negative / total:P0})");
Console.WriteLine($"  Neutral:  {neutral} ({(double)neutral / total:P0})");
Console.WriteLine($"Results saved to results.csv");

Step 5: Combining Sentiment with Custom Classification

Use Categorization alongside sentiment analysis to understand both the topic and the tone:

using LMKit.TextAnalysis;

var sentiment = new SentimentAnalysis(model) { NeutralSupport = true };
var categorizer = new Categorization(model);

string[] topics = { "product quality", "customer service", "pricing", "delivery", "usability" };

string feedback = "The interface is confusing and I keep getting lost. Very frustrating.";

// What is the topic?
int topicIndex = categorizer.GetBestCategory(topics, feedback);
string topic = topics[topicIndex];
float topicConfidence = categorizer.Confidence;

// What is the sentiment?
SentimentCategory tone = sentiment.GetSentimentCategory(feedback);
float sentimentConfidence = sentiment.Confidence;

Console.WriteLine($"Topic:     {topic} ({topicConfidence:P0})");
Console.WriteLine($"Sentiment: {tone} ({sentimentConfidence:P0})");
// Output: Topic: usability (92%)  Sentiment: Negative (95%)

This combination is the building block for customer feedback dashboards: categorize by topic, then aggregate sentiment per topic to identify the areas that need attention.


Model Selection for Sentiment Analysis

Model ID VRAM Speed Accuracy Best For
gemma3:1b ~1.5 GB Fastest Good High-volume batch processing
qwen3:1.7b ~1.5 GB Very fast Good Multilingual feedback
gemma3:4b ~3.5 GB Fast Very good General use (recommended)
qwen3:4b ~3.5 GB Fast Very good Mixed-language datasets

For sentiment and emotion detection, smaller models (1B-4B) perform well because the task is straightforward. Use a 1B model for batch processing large datasets where speed matters more than nuance.


Common Issues

Problem Cause Fix
Always returns Neutral NeutralSupport is false (default) Set NeutralSupport = true for three-way classification
Low confidence on short texts Not enough signal in 2-3 words Provide more context; consider using EmotionDetection for short messages
Wrong sentiment on sarcasm Model takes text literally Use a larger model (4B+) for better sarcasm understanding
Slow batch processing Model too large for the task Use gemma3:1b for bulk operations

Next Steps