Contrôler les dépenses de Noël avec Semantic Kernel
## Présentation
À l’approche des fêtes de fin d’année, la gestion des dépenses peut devenir un défi, surtout avec le tourbillon de magasinages et d’achats de cadeaux. Dans cet article de blog, nous explorerons comment tirer parti de l’intelligence artificielle pour vous aider à suivre vos dépenses de Noël à l’aide des technologies .NET. En analysant les reçus grâce à la puissance du noyau sémantique et de l’IA, nous pouvons extraire efficacement des détails clés tels que les noms des magasins, les dates, les listes d’articles et les montants totaux. Cette solution vous permet de surveiller et de gérer sans effort vos dépenses de Noël, vous garantissant ainsi de rester maître de votre budget sans avoir à examiner manuellement les reçus.
Calendrier d’Adviento de Inteligencia Artificial 2024 en Español

Ce projet a été inspiré par ma participation au Calendario de Adviento de Inteligencia Artificial 2024 en Español, un événement en ligne dédié à l’IA. Vous pouvez en savoir plus sur l’événement sur ce lien Dev.to.
Le projet
Pour ce projet, nous utiliserons Azure OpenAI, un service qui nous permet d’utiliser de puissants modèles d’IA tels que GPT-4 pour traiter et analyser des images. Le processus comporte plusieurs étapes, de la configuration du service API backend à l’intégration avec un frontal Blazor pour le téléchargement d’images. Nous utiliserons également .NET Aspire, un composant qui permet de tout connecter de manière transparente.
Prérequis
Avant de plonger dans le code, assurez-vous d’avoir les prérequis suivants :
-.NET 9
- Accès Azure OpenAI (clé API)
- Visual Studio ou Visual Studio Code
- Connaissance de base de Blazor, des clients HTTP et du développement d’API
La solution Visual Studio
Nous finirons par avoir quelque chose comme ça, j’aime garder les choses séparées et avec des noms sympas, alors voici à quoi ça ressemble :

Mais allons-y étape par étape pour créer des choses !
Étape 0 : Les modèles
Le cœur de l’application Receipt Scanner repose sur plusieurs modèles clés qui facilitent l’interaction entre les services front-end, API et IA. Voici les principaux modèles utilisés dans ce projet :
AnalyzeReceiptRequest
Ce modèle représente la structure de demande d’analyse d’un reçu. Il contient la propriétéImageBytes, qui contient le tableau d’octets de l’image du reçu qui sera traitée.public class AnalyzeReceiptRequest { public byte[] ImageBytes { get; set; } }RéceptionAnalyzeResult
Ce modèle capture le résultat après traitement d’un reçu. Il contient les données structurées extraites du reçu, telles que le nom du magasin, la date, les articles et le montant total.public class ReceiptAnalyzeResult { public DateTime CreatedAt { get; set; } public ReceiptData Result { get; set; } }Données de réception
Il s’agit du modèle qui contient les données structurées des reçus. Il comprend les propriétés du nom du magasin, la date, une liste d’articles (avec le nom et le prix de chaque article) et le montant total sur le reçu.public class ReceiptData { public string Store { get; set; } public DateTime? Date { get; set; } public List<ReceiptItem> Items { get; set; } public decimal? Total { get; set; } }Article de réception
Chaque élément du reçu est représenté par ce modèle. Il contient le nom de l’article et son prix.public class ReceiptItem { public string Name { get; set; } public decimal? Price { get; set; } } ```Ces modèles servent de base à la transmission des données entre le client et le serveur, garantissant ainsi un flux fluide d'informations. L'API reçoit l'image du reçu et, en retour, elle traite et renvoie un objet JSON structuré qui peut être facilement consommé par le front-end.
Étape 1 : Configuration du service API backend
La première étape de la création de cette application consiste à configurer un service API pour analyser les images des reçus. Nous utiliserons l’API Azure OpenAI pour extraire les informations des images de reçu. Voici un aperçu de la façon dont tout s’articule :
Service IA – Une plongée en profondeur
Le service IA est au cœur de notre système d’analyse des reçus. Il est chargé de communiquer avec l’API d’Azure OpenAI pour traiter les données d’image et renvoyer des informations significatives. La classe AiApiClient est le client qui gérera toutes les interactions avec l’API Azure OpenAI.
Implémentation du client IA
Le AiApiClient est le composant clé responsable de l’envoi de l’image du reçu (au format tableau d’octets) à l’API Azure OpenAI. Il gère la communication, la journalisation des erreurs et l’analyse des données :
public class AiApiClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<AiApiClient> _logger;
public AiApiClient(HttpClient httpClient, ILogger<AiApiClient> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<ReceiptAnalyzeResult?> AnalyzeAsync(byte[] imageBytes, CancellationToken cancellationToken = default)
{
if (imageBytes == null || imageBytes.Length == 0)
{
_logger.LogWarning("ImageBytes is null or empty.");
return null;
}
_logger.LogInformation("Sending analyze request with image bytes of length: {Length}", imageBytes.Length);
var request = new AnalyzeReceiptRequest
{
ImageBytes = imageBytes
};
try
{
var response = await _httpClient.PostAsJsonAsync("/analyze-receipt", request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Failed to analyze receipt. StatusCode: {StatusCode}", response.StatusCode);
return null;
}
var analyzeResult = await response.Content.ReadFromJsonAsync<ReceiptAnalyzeResult>(cancellationToken: cancellationToken);
if (analyzeResult == null)
{
_logger.LogWarning("No content received from AI API service.");
return null;
}
_logger.LogInformation("Analysis result received: {AnalyzeResult}", analyzeResult);
return analyzeResult;
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while analyzing the receipt.");
return null;
}
}
}
Dans cette partie du code, nous définissons la méthode AnalyzeAsync, qui se charge de :
- Envoi du tableau d’octets de l’image à l’API Azure OpenAI.
- Gérer les erreurs ou les réponses infructueuses de l’API.
- Analyse des données JSON renvoyées en un résultat structuré (
ReceiptAnalyzeResult).
Les avantages de séparer cette fonctionnalité en un service dédié (AiApiClient) incluent :
- Gestion des erreurs : Gestion centralisée des erreurs telles que des problèmes de réseau ou des réponses non valides.
- Journalisation : Journalisation appropriée des demandes et des réponses pour surveiller le comportement du système.

Service API - Gestion des requêtes et des réponses
Le Service API agit comme intermédiaire entre l’application frontale Blazor et le service AI. Ce service est chargé d’accepter les données d’image, de les transmettre au service AI et de renvoyer les résultats de l’analyse au client.
Point de terminaison de l’API
Dans cette étape, nous définissons un point de terminaison d’API simple pour accepter les images de reçus, les transmettre au service AI pour traitement et renvoyer les résultats au client :
using ReceiptScanner.Shared.Clients;
using ReceiptScanner.Shared.Models;
var builder = WebApplication.CreateBuilder(args);
// Add service defaults & Aspire client integrations.
builder.AddServiceDefaults();
// Add services to the container.
builder.Services.AddProblemDetails();
// Register AiApiClient with HttpClient
builder.Services.AddHttpClient<AiApiClient>(client =>
{
client.BaseAddress = new Uri("https+http://aiservice");
});
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseExceptionHandler();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
// POST endpoint to analyze receipt
app.MapPost("/api/analyze-receipt", async (AnalyzeReceiptRequest request, AiApiClient aiApiClient, ILogger<Program> logger) =>
{
if (request.ImageBytes == null || request.ImageBytes.Length == 0)
{
logger.LogWarning("ImageBytes is null or empty.");
return Results.BadRequest("ImageBytes is required.");
}
logger.LogInformation("Received analyze receipt request with image bytes of length: {Length}", request.ImageBytes.Length);
try
{
var result = await aiApiClient.AnalyzeAsync(request.ImageBytes);
if (result == null)
{
logger.LogWarning("Failed to analyze the receipt.");
return Results.Problem("Unable to process the receipt at this time. Please try again later.");
}
logger.LogInformation("Analysis completed successfully. Result: {Result}", result);
return Results.Ok(result);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while processing the receipt.");
return Results.Problem("An error occurred while processing the receipt. Please try again later.");
}
});
app.MapDefaultEndpoints();
app.Run();
Ce point de terminaison :
- Accepte l’image du reçu dans le corps de la demande.
- Appelle la méthode d’endopinte
AiServicepour envoyer l’image à Azure OpenAI pour traitement. - Renvoie le résultat de l’analyse au client.

Étape 2 : Configuration de l’interface Blazor
Maintenant que nous avons configuré le backend, tournons notre attention vers le frontend Blazor. C’est ici que les utilisateurs peuvent télécharger leurs images de reçus pour analyse et voir les résultats.
Implémentation des pages Blazor
La page Blazor fournit une interface simple où les utilisateurs peuvent télécharger plusieurs images de reçus, puis voir les résultats d’analyse affichés dans un tableau. Voici le code de la page :
@page "/analyzer"
@using ReceiptScanner.Shared.Clients
@using ReceiptScanner.Shared.Models
@using System.Globalization
@inject ApiServiceClient ApiClient
@inject ILogger<Program> Logger
@attribute [StreamRendering]
@rendermode InteractiveServer
<PageTitle>Receipt Analyzer</PageTitle>
<h1 class="text-center my-4">Receipt Analyzer</h1>
<div class="container">
<p class="lead text-center mb-4">Upload receipt images below to extract their data.</p>
<!-- File Upload Section -->
<div class="card mb-4">
<div class="card-body">
<InputFile OnChange="HandleFileSelected" multiple class="form-control mb-3" />
<button class="btn btn-primary w-100" @onclick="ProcessReceipts" disabled="@(!hasFiles)" type="button">
<span class="@(!processing ? "d-none" : "") spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
@if (processing)
{
<span>Processing...</span>
}
else
{
<span>Process Receipts</span>
}
</button>
</div>
</div>
<!-- Uploaded Images Preview -->
@if (fileBytesList.Any())
{
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Uploaded Receipt Images</h5>
</div>
<div class="card-body">
<div class="row">
@foreach (var fileBytes in fileBytesList)
{
<div class="col-12 col-md-4 mb-3">
<img src="@($"data:image/jpeg;base64,{Convert.ToBase64String(fileBytes)}")" class="img-fluid rounded" alt="Uploaded receipt" />
</div>
}
</div>
</div>
</div>
}
<!-- Processing Indicator -->
@if (processing)
{
<div class="alert alert-info text-center" role="alert">
<strong>Processing receipts...</strong> Please wait while we analyze the uploaded files.
</div>
}
<!-- Analysis Results Section -->
@if (analyzedReceipts != null && analyzedReceipts.Any())
{
<div class="card">
<div class="card-header">
<h5 class="mb-0">Analysis Results</h5>
</div>
<div class="card-body">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Store</th>
<th>Date</th>
<th>Total</th>
<th>Items</th>
</tr>
</thead>
<tbody>
@foreach (var receipt in analyzedReceipts)
{
<tr>
<td>@(receipt.Result?.Store ?? "Unknown")</td>
<td>@(receipt.Result?.Date?.ToString() ?? "Unknown")</td>
<td>@(receipt.Result?.Total?.ToString("C", ci) ?? "Unknown")</td>
<td>
<ul class="list-unstyled">
@if (receipt.Result?.Items is not null)
{
@foreach (var item in receipt.Result?.Items!)
{
<li><strong>@item.Name</strong> - @item.Price?.ToString("C", ci)</li>
}
}
</ul>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else if (processed && (analyzedReceipts == null || !analyzedReceipts.Any()))
{
<div class="alert alert-warning text-center" role="alert">
<strong>No results found.</strong> Please try again with different images or ensure they are clear and legible.
</div>
}
</div>
@code {
private bool hasFiles;
private bool processing;
private bool processed;
private List<byte[]> fileBytesList = new();
private List<ReceiptAnalyzeResult> analyzedReceipts = new();
CultureInfo ci = new CultureInfo("es-es");
private async Task HandleFileSelected(InputFileChangeEventArgs e)
{
try
{
fileBytesList.Clear();
foreach (var file in e.GetMultipleFiles())
{
var memoryStream = new MemoryStream();
await file.OpenReadStream().CopyToAsync(memoryStream);
fileBytesList.Add(memoryStream.ToArray());
}
hasFiles = fileBytesList.Any();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error while handling file upload.");
}
}
private async Task ProcessReceipts()
{
if (!hasFiles)
return;
processing = true;
analyzedReceipts.Clear();
try
{
foreach (var fileBytes in fileBytesList)
{
var result = await ApiClient.AnalyzeReceiptAsync(fileBytes);
if (result != null)
{
analyzedReceipts.Add(result);
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error while processing receipts.");
}
finally
{
processing = false;
processed = true;
}
}
}
```Cette page permet aux utilisateurs de télécharger des reçus et affiche les résultats de l'analyse dans un tableau avec les noms des magasins, les dates, les montants totaux et la liste des articles.
<p align="center">
<img src="https://imgur.com/BLswKhm.png">
</p>
## Étape 3 : .NET Aspire
<p align="center">
<img src="https://imgur.com/ja56RWN.png">
</p>
### Qu'est-ce que .NET Aspire ?
.NET Aspire est un ensemble d'outils, de modèles et de packages puissants permettant de créer des applications observables et prêtes pour la production. .NET Aspire est fourni via une collection de packages NuGet qui répondent à des problèmes spécifiques liés au cloud. Les applications cloud natives sont souvent constituées de petits éléments ou de microservices interconnectés plutôt que d'une base de code unique et monolithique. Les applications cloud natives consomment généralement un grand nombre de services, tels que les bases de données, la messagerie et la mise en cache. Pour plus d’informations sur la prise en charge, consultez la politique de prise en charge de .NET Aspire.
Une application distribuée est une application qui utilise des ressources de calcul sur plusieurs nœuds, tels que des conteneurs exécutés sur différents hôtes. Ces nœuds doivent communiquer au-delà des limites du réseau pour fournir des réponses aux utilisateurs. Une application cloud native est un type spécifique d'application distribuée qui tire pleinement parti de l'évolutivité, de la résilience et de la gérabilité des infrastructures cloud.
L'utilisation de **.NET Aspire** pour ce projet offre plusieurs avantages qui améliorent la qualité globale du système, tels que :
### 1. **Journalisation centralisée**
.NET Aspire intègre automatiquement la journalisation dans l'ensemble de l'application, ce qui signifie que vous n'avez pas besoin de configurer manuellement la journalisation pour chaque service. Cela garantit que les journaux sont cohérents et stockés dans un emplacement centralisé, ce qui facilite grandement le débogage et la surveillance.
Par exemple, la classe `AiApiClient` utilise la journalisation pour enregistrer les octets d'image envoyés au service AI, les réponses de l'API et toutes les erreurs qui se produisent pendant le processus d'analyse.
```csharp
_logger.LogInformation("Sending analyze request with image bytes of length: {Length}",
imageBytes.Length);

2. Collecte automatique de métriques
.NET Aspire suit et rapporte également automatiquement les mesures d’application importantes telles que les temps de réponse, le nombre de demandes et les taux d’erreur. Cela vous aide à comprendre les performances de l’application et à détecter rapidement les goulots d’étranglement ou les problèmes.

3. Performances améliorées
.NET Aspire optimise les appels HTTP, ce qui permet de maintenir des temps de réponse faibles et de réduire la consommation inutile de ressources. Il fournit des fonctionnalités telles que le regroupement de connexions, les tentatives de requêtes et le routage intelligent.
4. Intégration transparente
.NET Aspire simplifie l’intégration de divers services (comme les services IA et API de ce projet) et rationalise le processus de déploiement. Vous n’avez pas à vous soucier des configurations de bas niveau, car Aspire s’occupe pour vous des tâches liées à l’infrastructure.

###ConclusionL’IA n’est plus seulement un mot à la mode ou quelque chose que l’on voit dans les films de science-fiction. Il résout activement des problèmes du monde réel aujourd’hui, comme celui que nous avons abordé dans ce projet : extraire des données structurées à partir de reçus. Avec l’aide de Azure OpenAI, .NET Aspire et Blazor, nous pouvons automatiser ce qui serait autrement une tâche manuelle fastidieuse et sujette aux erreurs. L’IA ne se contente pas de discuter ou de répondre à des invites comme ChatGPT ; il interprète les images, extrait des informations précieuses et nous donne des informations exploitables en quelques secondes.
En utilisant Azure OpenAI pour l’analyse des reçus et .NET Aspire pour une intégration transparente avec la journalisation et les métriques, nous avons créé une solution à la fois puissante et évolutive. Le potentiel de l’IA pour rationaliser les processus métiers, automatiser les tâches fastidieuses et améliorer la précision est énorme, et ce n’est qu’un exemple de la façon dont elle peut être appliquée.
Cet article fait partie du Calendario de Adviento de Inteligencia Artificial 2024 en Español, un événement qui présente des applications d’IA du monde réel et sensibilise la communauté technologique hispanophone aux dernières tendances. Si vous souhaitez approfondir votre connaissance de l’IA et de ses possibilités, cet événement est un excellent point de départ.
L’IA transforme notre façon de travailler, et ce projet n’est qu’un aperçu de ce qui est possible. Le véritable pouvoir de l’IA réside dans sa capacité à résoudre des problèmes réels, qu’il s’agisse de traiter des reçus, d’analyser des images ou de prédire des tendances. Nous ne faisons qu’effleurer la surface.
###Code source
Le code source complet de ce projet est disponible sur GitHub. N’hésitez pas à le télécharger, à découvrir comment les services IA et API fonctionnent ensemble et à l’adapter à vos propres cas d’utilisation. Si vous rencontrez des problèmes ou si vous avez des idées d’amélioration, n’hésitez pas à créer un problème ou à soumettre une pull request. Les contributions sont toujours les bienvenues et vos commentaires contribueront à rendre ce projet encore meilleur !
Bon codage !