Kontrollieren Sie die Weihnachtsausgaben mit Semantic Kernel
## Einführung
Wenn die Weihnachtszeit näher rückt, kann die Verwaltung der Ausgaben zu einer Herausforderung werden, insbesondere angesichts der Flut an Einkäufen und Geschenkkäufen. In diesem Blogbeitrag untersuchen wir, wie Sie künstliche Intelligenz nutzen können, um mithilfe von .NET-Technologien den Überblick über Ihre Weihnachtsausgaben zu behalten. Durch die Analyse von Belegen mithilfe des semantischen Kernels und der KI können wir wichtige Details wie Geschäftsnamen, Daten, Artikellisten und Gesamtbeträge effizient extrahieren. Mit dieser Lösung können Sie Ihre Weihnachtsausgaben mühelos überwachen und verwalten und so sicherstellen, dass Sie den Überblick über Ihr Budget behalten, ohne die Belege manuell überprüfen zu müssen.
Calendario de Adviento de Inteligencia Artificial 2024 in Español

Dieses Projekt wurde durch meine Teilnahme am Calendario de Adviento de Inteligencia Artificial 2024 en Español inspiriert, einer Online-Veranstaltung zum Thema KI. Weitere Informationen zur Veranstaltung finden Sie unter diesem Dev.to-Link.
Das Projekt
Für dieses Projekt verwenden wir Azure OpenAI, einen Dienst, der es uns ermöglicht, leistungsstarke KI-Modelle wie GPT-4 zur Verarbeitung und Analyse von Bildern zu nutzen. Der Prozess umfasst mehrere Schritte, von der Einrichtung des Backend-API-Dienstes bis zur Integration in ein Blazor-Frontend für Bild-Uploads. Wir werden auch .NET Aspire verwenden, eine Komponente, die dabei hilft, alles nahtlos zu verbinden.
Voraussetzungen
Bevor wir uns mit dem Code befassen, stellen Sie sicher, dass Sie die folgenden Voraussetzungen erfüllen:
- .NET 9
- Azure OpenAI-Zugriff (API-Schlüssel)
- Visual Studio oder Visual Studio Code
- Grundkenntnisse über Blazor, HTTP-Clients und API-Entwicklung
Die Visual Studio-Lösung
Am Ende werden wir so etwas haben. Ich halte die Dinge gerne getrennt und mit coolen Namen, also sieht es so aus:

Aber lasst uns Schritt für Schritt vorgehen und Dinge erschaffen!
Schritt 0: Die Modelle
Der Kern der Receipt Scanner-Anwendung basiert auf mehreren Schlüsselmodellen, die die Interaktion zwischen Front-End, API und KI-Diensten erleichtern. Nachfolgend sind die wichtigsten Modelle aufgeführt, die in diesem Projekt verwendet werden:
AnalyzeReceiptRequest
Dieses Modell stellt die Anforderungsstruktur zur Analyse einer Quittung dar. Es enthält die EigenschaftImageBytes, die das Byte-Array des zu verarbeitenden Belegbilds enthält.public class AnalyzeReceiptRequest { public byte[] ImageBytes { get; set; } }ReceiptAnalyzeResult
Dieses Modell erfasst das Ergebnis nach der Bearbeitung eines Belegs. Es enthält die aus der Quittung extrahierten strukturierten Daten, z. B. den Namen des Geschäfts, das Datum, die Artikel und den Gesamtbetrag.public class ReceiptAnalyzeResult { public DateTime CreatedAt { get; set; } public ReceiptData Result { get; set; } }ReceiptData
Dies ist das Modell, das die strukturierten Belegdaten enthält. Es enthält Eigenschaften für den Geschäftsnamen, das Datum, eine Artikelliste (mit Name und Preis jedes Artikels) und den Gesamtbetrag auf der Quittung.public class ReceiptData { public string Store { get; set; } public DateTime? Date { get; set; } public List<ReceiptItem> Items { get; set; } public decimal? Total { get; set; } }ReceiptItem
Jeder Artikel auf der Quittung wird durch dieses Modell dargestellt. Es enthält den Artikelnamen und seinen Preis.public class ReceiptItem { public string Name { get; set; } public decimal? Price { get; set; } } ```Diese Modelle dienen als Grundlage für die Datenübertragung zwischen Client und Server und sorgen so für einen reibungslosen Informationsfluss. Die API empfängt das Belegbild und verarbeitet im Gegenzug ein strukturiertes JSON-Objekt und gibt es zurück, das vom Front-End problemlos verwendet werden kann.
Schritt 1: Einrichten des Backend-API-Dienstes
Der erste Schritt beim Erstellen dieser Anwendung ist die Einrichtung eines API-Dienstes zur Analyse von Belegbildern. Wir verwenden die Azure OpenAI-API, um Informationen aus den Belegbildern zu extrahieren. Hier ist eine Aufschlüsselung, wie alles zusammenpasst:
KI-Service – Ein tiefer Einblick
Der KI-Service ist das Herzstück unseres Beleganalysesystems. Es ist für die Kommunikation mit der API von Azure OpenAI verantwortlich, um die Bilddaten zu verarbeiten und aussagekräftige Erkenntnisse zurückzugeben. Die Klasse AiApiClient ist der Client, der alle Interaktionen mit der Azure OpenAI-API abwickelt.
AI-Client-Implementierung
AiApiClient ist die Schlüsselkomponente, die für das Senden des Belegbilds (im Byte-Array-Format) an die Azure OpenAI-API verantwortlich ist. Es übernimmt die Kommunikation, Fehlerprotokollierung und Datenanalyse:
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;
}
}
}
In diesem Teil des Codes definieren wir die Methode AnalyzeAsync, die verantwortlich ist für:
- Senden des Bildbyte-Arrays an die Azure OpenAI-API.
- Umgang mit etwaigen Fehlern oder erfolglosen Antworten der API.
- Parsen der zurückgegebenen JSON-Daten in ein strukturiertes Ergebnis (
ReceiptAnalyzeResult).
Zu den Vorteilen der Aufteilung dieser Funktionalität in einen dedizierten Dienst (AiApiClient) gehören:
- Fehlerbehandlung: Zentralisierte Behandlung von Fehlern wie Netzwerkproblemen oder ungültigen Antworten.
- Protokollierung: Korrekte Protokollierung von Anfragen und Antworten zur Überwachung des Systemverhaltens.

API-Dienst – Bearbeitung von Anfragen und Antworten
Der API-Dienst fungiert als Vermittler zwischen der Frontend-Blazor-Anwendung und dem KI-Dienst. Dieser Dienst ist dafür verantwortlich, die Bilddaten entgegenzunehmen, sie an den KI-Dienst weiterzuleiten und die Analyseergebnisse an den Kunden zurückzusenden.
API-Endpunkt
In diesem Schritt definieren wir einen einfachen API-Endpunkt, um Belegbilder zu akzeptieren, sie zur Verarbeitung an den AI-Dienst weiterzuleiten und die Ergebnisse an den Client zurückzugeben:
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();
Dieser Endpunkt:
- Akzeptiert das Belegbild als Teil des Anforderungstexts.
- Ruft die Endopint-Methode
AiServiceauf, um das Bild zur Verarbeitung an Azure OpenAI zu senden. - Gibt das Analyseergebnis an den Client zurück.

Schritt 2: Einrichten des Blazor-Frontends
Nachdem wir nun das Backend eingerichtet haben, wenden wir uns dem Blazor-Frontend zu. Hier können Benutzer ihre Belegbilder zur Analyse hochladen und die Ergebnisse sehen.
Blazor-Seitenimplementierung
Die Blazor-Seite bietet eine einfache Schnittstelle, über die Benutzer mehrere Belegbilder hochladen und dann die Analyseergebnisse in einer Tabelle anzeigen können. Hier ist der Code für die Seite:
@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;
}
}
}
```Auf dieser Seite können Benutzer Belege hochladen und die Analyseergebnisse in einer Tabelle mit Filialnamen, Daten, Gesamtbeträgen und der Artikelliste anzeigen.
<p align="center">
<img src="https://imgur.com/BLswKhm.png">
</p>
## Schritt 3: .NET Aspire
<p align="center">
<img src="https://imgur.com/ja56RWN.png">
</p>
### Was ist .NET Aspire?
.NET Aspire ist eine Reihe leistungsstarker Tools, Vorlagen und Pakete zum Erstellen beobachtbarer, produktionsbereiter Apps. .NET Aspire wird über eine Sammlung von NuGet-Paketen bereitgestellt, die spezifische Cloud-native Probleme behandeln. Cloud-native Apps bestehen oft aus kleinen, miteinander verbundenen Teilen oder Mikrodiensten und nicht aus einer einzigen, monolithischen Codebasis. Cloud-native Apps verbrauchen im Allgemeinen eine große Anzahl von Diensten wie Datenbanken, Messaging und Caching. Informationen zum Support finden Sie in der .NET Aspire-Supportrichtlinie.
Eine verteilte Anwendung ist eine Anwendung, die Rechenressourcen über mehrere Knoten hinweg nutzt, beispielsweise Container, die auf verschiedenen Hosts ausgeführt werden. Solche Knoten müssen über Netzwerkgrenzen hinweg kommunizieren, um den Benutzern Antworten zu liefern. Eine cloudnative App ist eine spezielle Art verteilter App, die die Skalierbarkeit, Belastbarkeit und Verwaltbarkeit von Cloud-Infrastrukturen voll ausnutzt.
Die Verwendung von **.NET Aspire** für dieses Projekt bietet mehrere Vorteile, die die Gesamtsystemqualität verbessern, wie zum Beispiel:
### 1. **Zentralisierte Protokollierung**
.NET Aspire integriert die Protokollierung automatisch in die gesamte Anwendung, sodass Sie die Protokollierung nicht für jeden Dienst manuell konfigurieren müssen. Dadurch wird sichergestellt, dass die Protokolle konsistent sind und an einem zentralen Ort gespeichert werden, was das Debuggen und Überwachen erheblich vereinfacht.
Beispielsweise verwendet die Klasse `AiApiClient` die Protokollierung, um die an den AI-Dienst gesendeten Bildbytes, die API-Antworten und alle Fehler aufzuzeichnen, die während des Analyseprozesses auftreten.
```csharp
_logger.LogInformation("Sending analyze request with image bytes of length: {Length}",
imageBytes.Length);

2. Automatische Metrikerfassung
.NET Aspire verfolgt und meldet außerdem automatisch wichtige Anwendungsmetriken wie Antwortzeiten, Anforderungsanzahl und Fehlerraten. Dies hilft Ihnen, die Leistung der Anwendung zu verstehen und etwaige Engpässe oder Probleme schnell zu erkennen.

3. Verbesserte Leistung
.NET Aspire optimiert HTTP-Aufrufe, was dazu beiträgt, die Antwortzeiten niedrig zu halten und unnötigen Ressourcenverbrauch zu reduzieren. Es bietet Funktionen wie Verbindungspooling, Anforderungswiederholungen und intelligentes Routing.
4. Nahtlose Integration
.NET Aspire vereinfacht die Integration verschiedener Dienste (wie die KI- und API-Dienste in diesem Projekt) und rationalisiert den Bereitstellungsprozess. Sie müssen sich keine Gedanken über Low-Level-Konfigurationen machen, da Aspire die infrastrukturbezogenen Aufgaben für Sie übernimmt.

FazitKI ist nicht mehr nur ein Schlagwort oder etwas, das wir in Science-Fiction-Filmen sehen. Es löst heute aktiv reale Probleme, wie das, mit dem wir uns in diesem Projekt befasst haben – das Extrahieren strukturierter Daten aus Belegen. Mit Hilfe von Azure OpenAI, .NET Aspire und Blazor können wir eine ansonsten zeitaufwändige und fehleranfällige manuelle Aufgabe automatisieren. KI chattet nicht nur oder reagiert auf Eingabeaufforderungen wie ChatGPT; Es interpretiert Bilder, extrahiert wertvolle Informationen und liefert uns in Sekundenschnelle umsetzbare Erkenntnisse.
Durch die Verwendung von Azure OpenAI für die Empfangsanalyse und .NET Aspire für die nahtlose Integration mit Protokollierung und Metriken haben wir eine Lösung geschaffen, die sowohl leistungsstark als auch skalierbar ist. Das Potenzial von KI zur Rationalisierung von Geschäftsprozessen, zur Automatisierung mühsamer Aufgaben und zur Verbesserung der Genauigkeit ist enorm, und dies ist nur ein Beispiel dafür, wie sie angewendet werden kann.
Dieser Beitrag ist Teil des Calendario de Adviento de Inteligencia Artificial 2024 en Español, einer Veranstaltung, die reale KI-Anwendungen vorstellt und die spanischsprachige Tech-Community über die neuesten Trends informiert. Wenn Sie tiefer in die KI und ihre Möglichkeiten eintauchen möchten, ist diese Veranstaltung ein guter Ausgangspunkt.
KI verändert unsere Arbeitsweise und dieses Projekt ist nur ein kleiner Vorgeschmack auf das, was möglich ist. Die wahre Stärke der KI liegt in ihrer Fähigkeit, reale Probleme zu lösen – sei es bei der Verarbeitung von Belegen, der Analyse von Bildern oder der Vorhersage von Trends. Wir kratzen nur an der Oberfläche.
Quellcode
Der vollständige Quellcode für dieses Projekt ist auf [GitHub](https://github.com/emimontesdeoca/ReceiptScannerPoc verfügbar. Laden Sie es gerne herunter, erkunden Sie, wie die KI- und API-Dienste zusammenarbeiten, und passen Sie es an Ihre eigenen Anwendungsfälle an. Wenn Sie auf Probleme stoßen oder Verbesserungsvorschläge haben, zögern Sie nicht, ein Problem zu erstellen oder eine Pull-Anfrage einzureichen. Beiträge sind immer willkommen und Ihr Feedback wird dazu beitragen, dieses Projekt noch besser zu machen!
Viel Spaß beim Codieren!