Skip to content

feat: SentimentAnalyzer v3.0 - Multi-engine architecture with samples#6

Merged
arafattehsin merged 6 commits intomainfrom
feature/sentiment-enhancements
Jan 26, 2026
Merged

feat: SentimentAnalyzer v3.0 - Multi-engine architecture with samples#6
arafattehsin merged 6 commits intomainfrom
feature/sentiment-enhancements

Conversation

@arafattehsin
Copy link
Copy Markdown
Owner

🎭 SentimentAnalyzer v3.0

Overview

Major release introducing a modular multi-engine architecture for on-device sentiment analysis.

📦 New Package Structure (4 packages)

Package Size Engine Languages Accuracy
SentimentAnalyzer (legacy) ~15MB ML.NET English ~85%
SentimentAnalyzer.Core <1MB VADER English ~75%
SentimentAnalyzer.Onnx ~18MB TinyBERT English 94.2%
SentimentAnalyzer.Onnx.Multilingual ~5MB + 270MB download DistilBERT 104 languages ~88%

✨ New Features

  • Fluent Builder API: SentimentAnalyzer.CreateBuilder().UseTinyBert().Build()
  • 3-class classification: Positive, Neutral, Negative
  • Batch processing: analyzer.AnalyzeBatch(texts)
  • Fully offline: All processing on-device
  • Privacy-first: No data leaves the app

🎯 Sample Applications

  • Console: Interactive demo with Spectre.Console (all 3 engines)
  • Blazor WASM: Browser-based demo (VADER - WASM compatible)
  • MAUI: Cross-platform mobile app (Android/iOS/macOS/Windows)
  • Benchmarks: BenchmarkDotNet performance tests

🧪 Tests

All 58 tests pass across:

  • SentimentAnalyzer.Tests (26 tests - legacy)
  • SentimentAnalyzer.Core.Tests (32 tests - new)

🔄 Breaking Changes

None for existing users! Legacy Sentiments.Predict() API still works.

📝 Migration

// New API (recommended)
var analyzer = SentimentAnalyzer.CreateBuilder().Build();
var result = analyzer.Analyze("Great product!");
// result.Label, result.Score, result.Confidence

// Legacy API (still works)
var result = Sentiments.Predict("Great product!");
// result.Prediction, result.Score

Ready to merge and publish 4 NuGet packages!

- Add permissions: id-token: write for OIDC token
- Use NuGet/login@v1 for passwordless authentication
- Remove dependency on NUGET_AUTH_TOKEN secret
- Add environment: production to match policy
- Use short-lived API keys (1 hour validity)

Benefits:
- More secure - no long-lived secrets
- No token rotation needed
- Industry standard OIDC authentication
…apps

BREAKING CHANGES:
- New modular architecture with multiple NuGet packages
- Builder pattern API for engine selection

NEW PACKAGES:
- SentimentAnalyzer.Core (<1MB) - VADER lexicon-based, ~75% accuracy
- SentimentAnalyzer.Onnx (~18MB) - TinyBERT transformer, 94.2% accuracy
- SentimentAnalyzer.Onnx.Multilingual (~5MB + 270MB download) - DistilBERT, 104 languages

SAMPLE APPS:
- Console app with Spectre.Console UI (all 3 engines)
- Blazor WASM demo (VADER only - WASM compatible)
- MAUI app (Android/iOS/macOS/Windows with engine switching)
- BenchmarkDotNet performance tests

FEATURES:
- Fluent builder API: SentimentAnalyzer.CreateBuilder().UseTinyBert().Build()
- 3-class classification (Positive/Neutral/Negative)
- Batch processing support
- Fully offline after first use
- Privacy-first: all processing on-device

All 58 tests pass.
- SentimentAnalyzer.Core
- SentimentAnalyzer.Onnx
- SentimentAnalyzer.Onnx.Multilingual
Ensures all 4 packages are published:
- SentimentAnalyzer (legacy, backward compatibility)
- SentimentAnalyzer.Core
- SentimentAnalyzer.Onnx
- SentimentAnalyzer.Onnx.Multilingual
Copilot AI review requested due to automatic review settings January 26, 2026 22:23
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces SentimentAnalyzer v3.0 with a modular, multi-engine architecture (Core VADER, TinyBERT ONNX, DistilBERT multilingual ONNX), along with new samples (Console, MAUI, Blazor WASM), benchmarks, and CI updates for publishing four NuGet packages.

Changes:

  • Add SentimentAnalyzer.Core, SentimentAnalyzer.Onnx, and SentimentAnalyzer.Onnx.Multilingual libraries with a pluggable SentimentAnalyzer/ISentimentEngine abstraction, configurable options, and 3‑class sentiment results.
  • Introduce rich sample applications (Console, MAUI, Blazor WASM) and BenchmarkDotNet benchmarks demonstrating all engines, plus expanded test suites for both the legacy ML.NET engine and the new Core/ONNX engines.
  • Update solution, root README, samples README, and GitHub Actions workflow to support the new package family and automated multi-package publishing.

Reviewed changes

Copilot reviewed 68 out of 76 changed files in this pull request and generated 30 comments.

Show a summary per file
File Description
samples/SentimentAnalyzer.Samples.Maui/StringNotEmptyConverter.cs Adds a simple string-to-bool value converter for MAUI bindings.
samples/SentimentAnalyzer.Samples.Maui/SentimentAnalyzerFactory.cs Centralizes creation and async loading of VADER, TinyBERT, and DistilBERT analyzers for the MAUI app.
samples/SentimentAnalyzer.Samples.Maui/SentimentAnalyzer.Samples.Maui.csproj Defines the MAUI sample project, target frameworks, platform assets, and references to Core/ONNX projects.
samples/SentimentAnalyzer.Samples.Maui/Resources/Styles/Styles.xaml Provides basic MAUI styles for buttons, editors, and frames in the MAUI UI.
samples/SentimentAnalyzer.Samples.Maui/Resources/Styles/Colors.xaml Defines light/dark theme and semantic sentiment colors for the MAUI app.
samples/SentimentAnalyzer.Samples.Maui/Resources/Splash/splash.svg Adds an emoji-based splash screen asset for MAUI platforms.
samples/SentimentAnalyzer.Samples.Maui/Resources/Raw/AboutAssets.txt Placeholder text file for MAUI raw assets.
samples/SentimentAnalyzer.Samples.Maui/Resources/AppIcon/appiconfg.svg Foreground emoji icon for the MAUI app icon set.
samples/SentimentAnalyzer.Samples.Maui/Resources/AppIcon/appicon.svg Background circle for MAUI app icon.
samples/SentimentAnalyzer.Samples.Maui/Platforms/iOS/Program.cs Standard MAUI iOS entry point wiring up AppDelegate.
samples/SentimentAnalyzer.Samples.Maui/Platforms/iOS/AppDelegate.cs iOS MAUI application delegate bootstrapping MauiProgram.
samples/SentimentAnalyzer.Samples.Maui/Platforms/Windows/App.xaml.cs WinUI MAUI app host that creates the Windows entry point and links to MauiProgram.
samples/SentimentAnalyzer.Samples.Maui/Platforms/Windows/App.xaml XAML definition of the WinUI MAUI application root.
samples/SentimentAnalyzer.Samples.Maui/Platforms/MacCatalyst/Program.cs Mac Catalyst MAUI entry point using AppDelegate.
samples/SentimentAnalyzer.Samples.Maui/Platforms/MacCatalyst/AppDelegate.cs Mac Catalyst MauiUIApplicationDelegate for the MAUI app.
samples/SentimentAnalyzer.Samples.Maui/Platforms/Android/Resources/xml/network_security_config.xml Android network security config disallowing cleartext and specifying trust settings for Hugging Face.
samples/SentimentAnalyzer.Samples.Maui/Platforms/Android/MainApplication.cs Android MauiApplication with network security config and cleartext disabled.
samples/SentimentAnalyzer.Samples.Maui/Platforms/Android/MainActivity.cs Android main activity for the MAUI app with splash theme and configuration flags.
samples/SentimentAnalyzer.Samples.Maui/Platforms/Android/AndroidManifestOverlay.xml Requests INTERNET and ACCESS_NETWORK_STATE permissions for Android model downloads.
samples/SentimentAnalyzer.Samples.Maui/MauiProgram.cs Configures MAUI services, fonts, CommunityToolkit, and registers SentimentAnalyzerFactory/view types.
samples/SentimentAnalyzer.Samples.Maui/MainViewModel.cs Implements the MAUI view model handling engine selection, async DistilBERT loading, analysis, and result presentation.
samples/SentimentAnalyzer.Samples.Maui/MainPage.xaml.cs Binds the MAUI main page to MainViewModel.
samples/SentimentAnalyzer.Samples.Maui/MainPage.xaml Defines the MAUI UI: engine picker, input editor, results view, and example buttons.
samples/SentimentAnalyzer.Samples.Maui/App.xaml.cs MAUI application class that applies theme-based resource colors and hosts the navigation root.
samples/SentimentAnalyzer.Samples.Maui/App.xaml Root MAUI application XAML merging Colors and Styles dictionaries.
samples/SentimentAnalyzer.Samples.Console/SentimentAnalyzer.Samples.Console.csproj Console sample project configuration referencing Core and ONNX libraries plus Spectre.Console.
samples/SentimentAnalyzer.Samples.Console/Program.cs Rich Spectre.Console-driven CLI demo offering multi-engine analysis, interactive mode, and performance tests.
samples/SentimentAnalyzer.Samples.Blazor/wwwroot/index.html Blazor WASM host page with Bootstrap and an initial loading indicator.
samples/SentimentAnalyzer.Samples.Blazor/wwwroot/css/app.css Basic Blazor styles including validation and error UI.
samples/SentimentAnalyzer.Samples.Blazor/_Imports.razor Centralizes using directives and aliases for the Blazor WASM app.
samples/SentimentAnalyzer.Samples.Blazor/Shared/MainLayout.razor Simple layout component to host page content.
samples/SentimentAnalyzer.Samples.Blazor/SentimentAnalyzer.Samples.Blazor.csproj Blazor WASM sample project targeting net10.0 and referencing SentimentAnalyzer.Core.
samples/SentimentAnalyzer.Samples.Blazor/Program.cs Blazor WASM startup registering a singleton VADER-based SA.SentimentAnalyzer.
samples/SentimentAnalyzer.Samples.Blazor/Pages/Index.razor Interactive browser UI for VADER-based sentiment analysis with examples and result visualization.
samples/SentimentAnalyzer.Samples.Blazor/App.razor Configures routing, layouts, and not-found handling for the Blazor app.
samples/SentimentAnalyzer.Samples.Benchmarks/SentimentBenchmarks.cs BenchmarkDotNet benchmarks comparing VADER, TinyBERT, and DistilBERT single and batch performance plus cold start.
samples/SentimentAnalyzer.Samples.Benchmarks/SentimentAnalyzer.Samples.Benchmarks.csproj Benchmark project configuration referencing Core and ONNX projects and BenchmarkDotNet.
samples/SentimentAnalyzer.Samples.Benchmarks/Program.cs Entrypoint to run SentimentBenchmarks via BenchmarkDotNet.
samples/README.md Documentation of all sample apps, their purposes, and how to run them.
SentimentAnalyzer/SentimentAnalyzerOptions.cs Introduces configurable options (neutral thresholds, binary mode) for the legacy ML.NET engine.
SentimentAnalyzer/SentimentAnalyzerEngine.cs New ML.NET-based SentimentAnalyzerEngine with options and modern SentimentResult/builder API.
SentimentAnalyzer/SentimentAnalyzer.csproj Bumps legacy package to v3.0.0 and updates description/release notes to reflect modular family.
SentimentAnalyzer/Models/SentimentResult.cs Enhanced legacy result type with label, score, confidence, and compatibility properties.
SentimentAnalyzer/Models/SentimentPrediction.cs Extends ML.NET prediction model to include raw score and probability.
SentimentAnalyzer/Models/SentimentLabel.cs Legacy enum defining negative/neutral/positive labels for the ML.NET engine.
SentimentAnalyzer.sln Updates solution to VS 17, adds Core/ONNX projects, tests, and sample projects, and expands build configs.
SentimentAnalyzer.Tests/SentimentAnalyzerEngineTests.cs Adds comprehensive tests for the legacy SentimentAnalyzerEngine behavior and options.
SentimentAnalyzer.Tests/SentimentAnalyzer.Tests.csproj Simplifies test project references and packages (removes JSON dataset dependency).
SentimentAnalyzer.Tests/Benchmarks.cs Replaces external IMDB JSON benchmark with inline review data and simpler performance tests.
SentimentAnalyzer.Onnx/WordPieceTokenizer.cs Implements a TinyBERT WordPiece tokenizer loading vocab from embedded resources or temp files.
SentimentAnalyzer.Onnx/TinyBertSentimentEngine.cs ONNX Runtime-based TinyBERT sentiment engine implementing the common engine interface.
SentimentAnalyzer.Onnx/SentimentAnalyzerBuilderExtensions.cs Adds a UseTinyBert extension to plug TinyBERT into the shared SentimentAnalyzerBuilder.
SentimentAnalyzer.Onnx/SentimentAnalyzer.Onnx.csproj New ONNX package project for TinyBERT with embedded model and vocab resources.
SentimentAnalyzer.Onnx.Multilingual/WordPieceTokenizer.cs Simple WordPiece tokenizer and ITokenizer interface for multilingual DistilBERT.
SentimentAnalyzer.Onnx.Multilingual/SentimentAnalyzerBuilderExtensions.cs Adds UseDistilBert builder extensions for the multilingual ONNX engine.
SentimentAnalyzer.Onnx.Multilingual/SentimentAnalyzer.Onnx.Multilingual.csproj New multilingual ONNX package project (DistilBERT) with nuget metadata.
SentimentAnalyzer.Onnx.Multilingual/ModelDownloader.cs Handles downloading, caching, and validating the DistilBERT ONNX model and vocab.
SentimentAnalyzer.Onnx.Multilingual/DistilBertSentimentEngine.cs ONNX Runtime-based DistilBERT sentiment engine with async creation and multilingual support.
SentimentAnalyzer.Core/SentimentAnalyzer.cs Defines the new Core SentimentAnalyzer facade and builder using pluggable ISentimentEngines.
SentimentAnalyzer.Core/SentimentAnalyzer.Core.csproj Core package project for the VADER-based engine with multi-targeting and nuget metadata.
SentimentAnalyzer.Core/Models/SentimentResult.cs Core result type shared across engines: label, score, confidence, and engine name.
SentimentAnalyzer.Core/Models/SentimentLabel.cs Core sentiment label enum (negative/neutral/positive) used by all engines.
SentimentAnalyzer.Core/Models/SentimentAnalyzerOptions.cs Core configuration options (neutral band, enabling/disabling neutral).
SentimentAnalyzer.Core/Engines/VaderSentimentEngine.cs VADER-based implementation of ISentimentEngine using VaderSharp2.
SentimentAnalyzer.Core/Engines/ISentimentEngine.cs Common engine interface for Core, TinyBERT, and DistilBERT implementations.
SentimentAnalyzer.Core.Tests/VaderSentimentEngineTests.cs Unit tests validating VADER engine label/score behavior and options.
SentimentAnalyzer.Core.Tests/TinyBertEngineTests.cs Tests exercising TinyBERT ONNX engine behavior with real model integration.
SentimentAnalyzer.Core.Tests/SentimentAnalyzerBuilderTests.cs Tests for the builder API, custom engines, and various option presets.
SentimentAnalyzer.Core.Tests/SentimentAnalyzer.Core.Tests.csproj Test project configuration for Core and Onnx with xUnit and coverage tooling.
README.md Overhauls root docs for v3.0: package matrix, quick starts, advanced usage, and performance summary.
.gitignore Replaces ad-hoc ignores with a more comprehensive .NET/VS-oriented ignore list.
.github/workflows/nuget.yml Extends NuGet publishing workflow to build and push all four packages using Trusted Publishing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +68
public sealed class DistilBertSentimentEngine : ISentimentEngine, IDisposable
{
private readonly InferenceSession _session;
private readonly SentimentAnalyzerOptions _options;
private readonly WordPieceTokenizer _tokenizer;
private bool _disposed;

/// <inheritdoc/>
public string Name => "DistilBert";

/// <summary>
/// Creates a new DistilBERT sentiment engine with automatic model download.
/// The ~270MB model will be downloaded on first use and cached locally.
/// </summary>
/// <param name="options">Configuration options.</param>
/// <remarks>
/// This constructor will block until the model is downloaded (first use only).
/// For async download with progress, use CreateAsync instead.
/// </remarks>
public DistilBertSentimentEngine(SentimentAnalyzerOptions? options = null)
{
_options = options ?? SentimentAnalyzerOptions.Default;
var modelPath = ModelDownloader.EnsureModel();
_session = new InferenceSession(modelPath);
_tokenizer = new WordPieceTokenizer(ModelDownloader.GetVocabPath());
}

/// <summary>
/// Creates a new DistilBERT sentiment engine from a specific model path.
/// </summary>
/// <param name="modelPath">Path to the ONNX model file.</param>
/// <param name="vocabPath">Path to the vocabulary file.</param>
/// <param name="options">Configuration options.</param>
public DistilBertSentimentEngine(string modelPath, string vocabPath, SentimentAnalyzerOptions? options = null)
{
if (modelPath == null)
throw new ArgumentNullException(nameof(modelPath));
if (vocabPath == null)
throw new ArgumentNullException(nameof(vocabPath));

if (!File.Exists(modelPath))
throw new FileNotFoundException($"ONNX model not found at: {modelPath}", modelPath);
if (!File.Exists(vocabPath))
throw new FileNotFoundException($"Vocabulary file not found at: {vocabPath}", vocabPath);

_options = options ?? SentimentAnalyzerOptions.Default;
_session = new InferenceSession(modelPath);
_tokenizer = new WordPieceTokenizer(vocabPath);
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DistilBertSentimentEngine implements ISentimentEngine and uses SentimentAnalyzerOptions, SentimentResult, and SentimentLabel, but the SentimentAnalyzer namespace is not imported here, so these shared Core types will not resolve and the project will fail to compile. Add a using SentimentAnalyzer; (or fully qualify these type names) so this engine correctly implements the common interface and returns the expected result type.

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +139
ResultEmoji = result.Label switch
{
SentimentLabel.Positive => "😊",
SentimentLabel.Negative => "😞",
_ => "😐"
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SentimentLabel is used in these switch expressions, but this file only defines an alias SA = global::SentimentAnalyzer and does not import the SentimentAnalyzer namespace directly, so SentimentLabel will be an unresolved type and the MAUI sample won’t compile. Please either qualify these references as SA.SentimentLabel or add using SentimentAnalyzer; so the enum resolves correctly in both switch expressions.

Copilot uses AI. Check for mistakes.
Comment thread README.md Outdated
Analyzes the sentiment of the provided text.
// Use DistilBERT for multilingual support (downloads ~541MB model on first use)
var analyzer = SentimentAnalyzer.CreateBuilder()
.UseDistilBertMultilingual()
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Option C code sample shows .UseDistilBertMultilingual(), but the actual extension method provided in SentimentAnalyzer.Onnx.Multilingual.SentimentAnalyzerBuilderExtensions is named UseDistilBert. To avoid confusing users, please update the sample to call the existing UseDistilBert extension (or add a matching UseDistilBertMultilingual helper if you prefer that name).

Suggested change
.UseDistilBertMultilingual()
.UseDistilBert()

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +282 to +283
var analyzer = await SentimentAnalyzer.CreateBuilder()
.UseDistilBertMultilingualAsync(progress: progress);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This advanced usage sample references UseDistilBertMultilingualAsync, but the codebase only defines synchronous UseDistilBert builder extensions for the multilingual engine. Consider updating the snippet to use the existing API (for example, wrapping DistilBertSentimentEngine.CreateAsync yourself) or adding a corresponding async builder extension so the documentation matches the available API.

Suggested change
var analyzer = await SentimentAnalyzer.CreateBuilder()
.UseDistilBertMultilingualAsync(progress: progress);
var analyzer = SentimentAnalyzer.CreateBuilder()
.UseDistilBert(progress: progress)
.Build();

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +98 to 103
### Option D: Legacy API (Backward Compatible)

### Example Usage
```bash
dotnet add package SentimentAnalyzer
```

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note describes the multilingual DistilBERT model as ~541MB, but other documentation (for example, SentimentAnalyzer.Onnx.Multilingual.csproj and comments in ModelDownloader) describe the download as ~270MB. Please align these size values so users have a consistent expectation of the model download footprint.

Copilot uses AI. Check for mistakes.
private string _resultEmoji = string.Empty;

[ObservableProperty]
private string _confidenceText = string.Empty;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field '_confidenceText' can be 'readonly'.

Copilot uses AI. Check for mistakes.
private double _score;

[ObservableProperty]
private string _scoreText = string.Empty;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field '_scoreText' can be 'readonly'.

Copilot uses AI. Check for mistakes.
private string _scoreText = string.Empty;

[ObservableProperty]
private string _timingText = string.Empty;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field '_timingText' can be 'readonly'.

Copilot uses AI. Check for mistakes.
private string _timingText = string.Empty;

[ObservableProperty]
private Color _resultBackgroundColor = Colors.Transparent;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field '_resultBackgroundColor' can be 'readonly'.

Copilot uses AI. Check for mistakes.
private TimeSpan analysisTime;
private bool isAnalyzing;

private string[] examples = new[]
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field 'examples' can be 'readonly'.

Copilot uses AI. Check for mistakes.
- Features first (hook readers)
- Quick Start immediately (get started fast)
- Package comparison with performance, platform support, and use cases (help choose)
- Detailed API reference (for those who need it)
- Advanced usage (power users)
- Migration guide (existing users)
- Resources at the end
- Add missing 'using SentimentAnalyzer;' in ONNX engine files (fixes compilation errors)
- Fix incorrect method name in README: UseDistilBert() not UseDistilBertMultilingual()
- Fix model size inconsistency: 270MB not 541MB for DistilBERT
- Remove stray 'D' character in README
- Fix false thread-safety claim in legacy ML.NET engine documentation

Resolves compilation errors and documentation accuracy issues.
@arafattehsin arafattehsin merged commit b193cb2 into main Jan 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants