feat: SentimentAnalyzer v3.0 - Multi-engine architecture with samples#6
feat: SentimentAnalyzer v3.0 - Multi-engine architecture with samples#6arafattehsin merged 6 commits intomainfrom
Conversation
- 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
There was a problem hiding this comment.
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, andSentimentAnalyzer.Onnx.Multilinguallibraries with a pluggableSentimentAnalyzer/ISentimentEngineabstraction, 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.
| 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); | ||
| } |
There was a problem hiding this comment.
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.
| ResultEmoji = result.Label switch | ||
| { | ||
| SentimentLabel.Positive => "😊", | ||
| SentimentLabel.Negative => "😞", | ||
| _ => "😐" |
There was a problem hiding this comment.
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.
| Analyzes the sentiment of the provided text. | ||
| // Use DistilBERT for multilingual support (downloads ~541MB model on first use) | ||
| var analyzer = SentimentAnalyzer.CreateBuilder() | ||
| .UseDistilBertMultilingual() |
There was a problem hiding this comment.
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).
| .UseDistilBertMultilingual() | |
| .UseDistilBert() |
| var analyzer = await SentimentAnalyzer.CreateBuilder() | ||
| .UseDistilBertMultilingualAsync(progress: progress); |
There was a problem hiding this comment.
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.
| var analyzer = await SentimentAnalyzer.CreateBuilder() | |
| .UseDistilBertMultilingualAsync(progress: progress); | |
| var analyzer = SentimentAnalyzer.CreateBuilder() | |
| .UseDistilBert(progress: progress) | |
| .Build(); |
| ### Option D: Legacy API (Backward Compatible) | ||
|
|
||
| ### Example Usage | ||
| ```bash | ||
| dotnet add package SentimentAnalyzer | ||
| ``` | ||
|
|
There was a problem hiding this comment.
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.
| private string _resultEmoji = string.Empty; | ||
|
|
||
| [ObservableProperty] | ||
| private string _confidenceText = string.Empty; |
There was a problem hiding this comment.
Field '_confidenceText' can be 'readonly'.
| private double _score; | ||
|
|
||
| [ObservableProperty] | ||
| private string _scoreText = string.Empty; |
There was a problem hiding this comment.
Field '_scoreText' can be 'readonly'.
| private string _scoreText = string.Empty; | ||
|
|
||
| [ObservableProperty] | ||
| private string _timingText = string.Empty; |
There was a problem hiding this comment.
Field '_timingText' can be 'readonly'.
| private string _timingText = string.Empty; | ||
|
|
||
| [ObservableProperty] | ||
| private Color _resultBackgroundColor = Colors.Transparent; |
There was a problem hiding this comment.
Field '_resultBackgroundColor' can be 'readonly'.
| private TimeSpan analysisTime; | ||
| private bool isAnalyzing; | ||
|
|
||
| private string[] examples = new[] |
There was a problem hiding this comment.
Field 'examples' can be 'readonly'.
- 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.
🎭 SentimentAnalyzer v3.0
Overview
Major release introducing a modular multi-engine architecture for on-device sentiment analysis.
📦 New Package Structure (4 packages)
✨ New Features
SentimentAnalyzer.CreateBuilder().UseTinyBert().Build()analyzer.AnalyzeBatch(texts)🎯 Sample Applications
🧪 Tests
All 58 tests pass across:
🔄 Breaking Changes
None for existing users! Legacy
Sentiments.Predict()API still works.📝 Migration
Ready to merge and publish 4 NuGet packages!