I am sure many of you remember Introducing .NET Smart Components – AI-powered UI controls by Daniel Roth and Steve Sanderson (both from Microsoft) and the excitement this article generated within the community. While this is still an ongoing experiment, many loved the example (myself included) because of the following:
- Thanks to its simplicity, developers can "ride AI" locally without earning an Azure or OpenAI PhD - smart functions are available using a single NuGet package.
- "Smart search" is made possible by Local Embeddings, and its beauty lies in its ability to run locally, without external AI services.
- Associated usage scenarios simply make sense - many line of business (LOB) applications can benefit from this implementation immediately. Take semantic similarity search as an example: End users can search for the word "furniture" when using the DevExpress Data Grid, Lookup or other data-aware controls, and the implementation is smart enough to return records such as "chair", "table", "sofa", "wardrobe", etc.
Problem/Usage Scenario
The original post referenced above featured ASP.NET Core Blazor and MVC/Razor integrations. In this post, I'll demonstrate the ease with which you can introduce "smart search" using our award-winning Data Grid for WinForms. Integrations for other data-aware DevExpress UI controls will be similar - you will simply need to use appropriate events to achieve the desired result.
Implementation Details
1. Microsoft's SmartComponents.LocalEmbeddings NuGet package (available as an experimental GitHub repo) lies at the core of our "smart search" implementation - we simply added it to our WinForms project along with our DevExpress.Win.Grid package. Nothing more, nothing less.
Let's take a closer look at Local Embeddings from Microsoft's GitHub repo:
Embeddings are used for semantic similarity search. Natural-language strings are converted into numerical vectors called embeddings. The more conceptually related are two strings, the closer their vectors. While you can use an external AI service to compute embeddings, in many cases you can simply compute them locally on your server (no need for a GPU - the CPU will work fine). SmartComponents.LocalEmbeddings is a package to simplify doing this. With SmartComponents.LocalEmbeddings, you can compute embeddings in under a millisecond, and perform semantic search over hundreds of thousands of candidates in single-digit milliseconds.
For additional information in this regard, please review Understand embeddings in Azure OpenAI Service or search for similar articles on the web. The ONNX embeddings model (powered by Microsoft.ML.OnnxRuntime.dll) is automatically added to your Bin folder from the SmartComponents.LocalEmbeddings NuGet package - it is 16MB in size.
2. Our WinForms Grid is bound to a collection of Item records (ID, Name, Description). Test data was AI generated (a time saver). We handle the Grid's ColumnView.CustomRowFilter event to determine row visibility (based on item text similarity - in this example Name and optionally Description) for search strings. We also trigger the filtering routine when handling keyboard input or changing search options (like similarity threshold).
void OnCustomRowFilter(object sender, RowFilterEventArgs e) {
Item? item = ((ColumnView)sender).DataController.GetRowByListSourceIndex(e.ListSourceRow) as Item;
if(item == null)
return;
string filter = teFilter.Text;
if(string.IsNullOrEmpty(filter))
return;
float threshold = (float)tbThreshold.Value / 100;
e.Visible = SmartFilterProvider.IsSimilarTo(filter, item.Name, threshold);
if(!e.Visible && cbIncludeDescription.Checked)
e.Visible = SmartFilterProvider.IsSimilarTo(filter, item.Description, threshold);
e.Handled = true;
}
3. "Smart search" is made possible by the SmartFilterProvider class - which calls the SmartComponents.LocalEmbeddings API based on documentation published in the GitHub repo.
namespace SmartAIFilter.Provider {
using SmartComponents.LocalEmbeddings;
public static class SmartFilterProvider {
readonly static LocalEmbedder Embedder = new LocalEmbedder(caseSensitive: false);
readonly static ConcurrentDictionary<string, EmbeddingF32> cache =
new ConcurrentDictionary<string, EmbeddingF32>(StringComparer.OrdinalIgnoreCase);
public static bool IsSimilarTo(string filter, string text, float threshold = 0.75f) {
EmbeddingF32 eText = cache.GetOrAdd(text, x => Embedder.Embed(x));
EmbeddingF32 eFilter = cache.GetOrAdd(filter, x => Embedder.Embed(x));
return eFilter.Similarity(eText) > threshold;
}
}
}
As a developer, you can tune this example based on your specific use-case scenario/project requirement. For example, you can use online (OpenAI, Azure, etc.) or offline models (Ollama, ONNX) as neeeded. You can download the example referenced in this post using the following link.
Your Feedback Matters
As always, your feedback is very important. Please let us know whether additional AI-related samples/solutions are of interest to you and how you expect AI to change your development strategies in the next 12-months.
Thanks,
Dennis Garavsky
Principal Product Manager
dennis@devexpress.com