使用语义内核控制圣诞节支出
## 简介
随着假期的临近,管理开支可能成为一项挑战,尤其是在购物和礼品购买热潮的情况下。在这篇博文中,我们将探讨如何利用人工智能使用 .NET 技术来帮助跟踪您的圣诞节支出。通过利用语义内核和人工智能的力量分析收据,我们可以有效地提取关键详细信息,例如商店名称、日期、商品列表和总额。该解决方案可让您轻松监控和管理您的圣诞节支出,确保您控制在预算范围内,而无需手动查看收据。
2024 年人工智能Adviento Adviento 西班牙语日历
<p对齐=“中心”>

这个项目的灵感来自于我参加 Calendario de Adviento de Inteligencia Artificial 2024 en Español,这是一个专门针对人工智能的在线活动。您可以在 此 Dev.to 链接 上找到有关该活动的更多信息。
项目
对于这个项目,我们将使用 Azure OpenAI,该服务允许我们利用强大的 AI 模型(例如 GPT-4)来处理和分析图像。该过程涉及多个步骤,从设置后端 API 服务到与 Blazor 前端集成以进行图像上传。我们还将使用**.NET Aspire**,这是一个有助于无缝连接一切的组件。
先决条件
在我们深入研究代码之前,请确保您满足以下先决条件:
- .NET 9
- Azure OpenAI 访问(API 密钥)
- Visual Studio 或 Visual Studio Code
- Blazor、HTTP 客户端和 API 开发的基本知识
Visual Studio 解决方案
我们最终会得到这样的东西,我喜欢把东西分开并用很酷的名字,所以它看起来是这样的:
<p对齐=“中心”>

但让我们一步一步来创造东西吧!
步骤 0:模型
收据扫描仪应用程序的核心依赖于几个关键模型,这些模型促进前端、API 和 AI 服务之间的交互。本项目使用的主要模型如下:
分析收据请求
该模型表示用于分析收据的请求结构。它包含ImageBytes属性,该属性保存将要处理的收据图像的字节数组。public class AnalyzeReceiptRequest { public byte[] ImageBytes { get; set; } }收据分析结果
该模型在处理收据后捕获结果。它保存从收据中提取的结构化数据,例如商店名称、日期、商品和总金额。public class ReceiptAnalyzeResult { public DateTime CreatedAt { get; set; } public ReceiptData Result { get; set; } }收据数据
这是保存结构化收据数据的模型。它包括商店名称、日期、商品列表(包含每个商品的名称和价格)以及收据上的总金额的属性。public class ReceiptData { public string Store { get; set; } public DateTime? Date { get; set; } public List<ReceiptItem> Items { get; set; } public decimal? Total { get; set; } }收据项目
收据上的每一项都由该模型表示。它包含商品名称及其价格。public class ReceiptItem { public string Name { get; set; } public decimal? Price { get; set; } } ```这些模型是客户端和服务器之间传递数据的基础,确保信息的顺畅流动。 API 接收收据图像,作为回报,它处理并返回一个可以由前端轻松使用的结构化 JSON 对象。
第 1 步:设置后端 API 服务
构建此应用程序的第一步是设置 API 服务来分析收据图像。我们将使用 Azure OpenAI API 从收据图像中提取信息。以下是所有内容如何组合在一起的详细说明:
AI 服务 - 深入探讨
人工智能服务是我们收据分析系统的核心。它负责与 Azure OpenAI 的 API 进行通信,以处理图像数据并返回有意义的见解。 AiApiClient 类是将处理与 Azure OpenAI API 的所有交互的客户端。
AI 客户端实施
AiApiClient 是负责将收据图像(字节数组格式)发送到 Azure OpenAI API 的关键组件。它处理通信、错误记录和数据解析:
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;
}
}
}
在这部分代码中,我们定义了 AnalyzeAsync 方法,该方法负责:
- 将图像字节数组发送到 Azure OpenAI API。
- 处理来自 API 的任何错误或不成功的响应。
- 将返回的JSON数据解析为结构化结果(
ReceiptAnalyzeResult)。
将此功能分离到专用服务 (AiApiClient) 的好处包括:
- 错误处理: 集中处理网络问题或无效响应等错误。
- 日志记录: 正确记录请求和响应以监视系统行为。
<p对齐=“中心”>

API 服务 - 处理请求和响应
API 服务 充当前端 Blazor 应用程序和 AI 服务之间的中介。该服务负责接受图像数据,将其传递给AI服务,并将分析结果返回给客户端。
API 端点
在此步骤中,我们定义一个简单的 API 端点来接受收据图像,将其转发给 AI 服务进行处理,并将结果返回给客户端:
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();
这个端点:
- 接受收据图像作为请求正文的一部分。
- 调用
AiServiceendopint方法将图像发送到Azure OpenAI进行处理。 - 将分析结果返回给客户端。
<p对齐=“中心”>

步骤 2:设置 Blazor 前端
现在我们已经设置了后端,让我们将注意力转向 Blazor 前端。用户可以在这里上传收据图像进行分析并查看结果。
Blazor 页面实施
Blazor 页面提供了一个简单的界面,用户可以在其中上传多个收据图像,然后查看表格中显示的分析结果。这是该页面的代码:
@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;
}
}
}
```该页面允许用户上传收据,并在表格中显示分析结果,其中包含商店名称、日期、总金额和商品列表。
<p对齐=“中心”>
<img src="https://imgur.com/BLswKhm.png">
</p>
## 步骤 3:.NET Aspire
<p对齐=“中心”>
<img src="https://imgur.com/ja56RWN.png">
</p>
### 什么是.NET Aspire?
.NET Aspire 是一组强大的工具、模板和包,用于构建可观察的、生产就绪的应用程序。 .NET Aspire 通过一系列处理特定云原生问题的 NuGet 包来交付。云原生应用程序通常由小型、互连的部分或微服务组成,而不是单个、整体的代码库。云原生应用程序通常会消耗大量服务,例如数据库、消息传递和缓存。有关支持的信息,请参阅 .NET Aspire 支持政策。
分布式应用程序是一种跨多个节点使用计算资源的应用程序,例如在不同主机上运行的容器。此类节点必须通过网络边界进行通信,以便向用户传递响应。云原生应用程序是一种特定类型的分布式应用程序,它充分利用云基础设施的可扩展性、弹性和可管理性。
在该项目中使用 **.NET Aspire** 可以带来多种好处,可以提高整体系统质量,例如:
### 1. **集中日志记录**
.NET Aspire 自动集成整个应用程序的日志记录,这意味着您不必为每个服务手动配置日志记录。这可确保日志一致并存储在集中位置,从而使调试和监控变得更加容易。
例如,`AiApiClient` 类使用日志记录发送到 AI 服务的图像字节、API 响应以及分析过程中发生的任何错误。
```csharp
_logger.LogInformation("Sending analyze request with image bytes of length: {Length}",
imageBytes.Length);
<p对齐=“中心”>

2. 自动指标收集
.NET Aspire 还自动跟踪和报告重要的应用程序指标,例如响应时间、请求计数和错误率。这可以帮助您了解应用程序的执行情况并快速检测任何瓶颈或问题。
<p对齐=“中心”>

3. 提高性能
.NET Aspire 优化了 HTTP 调用,这有助于缩短响应时间并减少不必要的资源消耗。它提供连接池、请求重试和智能路由等功能。
4. 无缝集成
.NET Aspire 简化了各种服务(如本项目中的 AI 和 API 服务)的集成并简化了部署过程。您无需担心低级配置,因为 Aspire 会为您处理与基础设施相关的任务。
<p对齐=“中心”>

结论人工智能不再只是一个流行词或我们在科幻电影中看到的东西。它正在积极解决当今的现实世界问题,就像我们在这个项目中解决的问题一样——从收据中提取结构化数据。在 Azure OpenAI、.NET Aspire 和 Blazor 的帮助下,我们可以自动化原本耗时且容易出错的手动任务。 AI 不仅仅像 ChatGPT 那样聊天或响应提示;它可以解释图像,提取有价值的信息,并在几秒钟内为我们提供可操作的见解。
通过使用 Azure OpenAI 进行收据分析,并使用 .NET Aspire 与日志记录和指标无缝集成,我们创建了一个功能强大且可扩展的解决方案。人工智能在简化业务流程、自动化繁琐任务和提高准确性方面的潜力是巨大的,这只是其应用的一个例子。
这篇文章是 Calendario de Adviento de Inteligencia Artificial 2024 en Español 的一部分,该活动展示了现实世界的人工智能应用,并向西班牙语技术社区介绍最新趋势。如果您想更深入地了解人工智能及其可能性,那么本次活动是一个很好的起点。
人工智能正在改变我们的工作方式,这个项目只是对可能性的一瞥。人工智能的真正力量在于它解决实际问题的能力——无论是处理收据、分析图像还是预测趋势。我们只是触及了表面。
源代码
该项目的完整源代码可在 GitHub 上找到。请随意下载它,探索 AI 和 API 服务如何协同工作,并根据您自己的用例进行调整。如果您遇到任何问题,或者您有改进的想法,请立即创建问题或提交拉取请求。我们始终欢迎您的贡献,您的反馈将有助于使这个项目变得更好!
快乐编码!