From 96fd8adf0fe6dfd87665d5174bb16ff4b99c3c26 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Mon, 27 Jul 2026 17:57:30 +0200 Subject: [PATCH] Refactor embedding request creation and processing Consolidate embedding request logic into a new EmbeddingRequest record with static factory methods for IFormFile and stream input. Remove FormFileToEmbeddingRequestExecutor and FormFileEmbeddingRequest, updating all usages and dependency injection accordingly. Clean up related code in Program.cs, VectorSearchService, and _Imports.razor. Enhance XML documentation for EmbeddingRequest to clarify intent and usage. --- .../Components/Pages/Documents.razor | 2 +- .../Components/_Imports.razor | 1 + .../Endpoints/DocumentEndpoints.cs | 2 +- SqlDatabaseVectorSearch/Program.cs | 5 +--- .../Services/VectorSearchService.cs | 2 +- .../Workflows/EmbeddingRequest.cs | 26 +++++++++++++++++++ .../FormFileToEmbeddingRequestExecutor.cs | 18 ------------- 7 files changed, 31 insertions(+), 25 deletions(-) create mode 100644 SqlDatabaseVectorSearch/Workflows/EmbeddingRequest.cs delete mode 100644 SqlDatabaseVectorSearch/Workflows/FormFileToEmbeddingRequestExecutor.cs diff --git a/SqlDatabaseVectorSearch/Components/Pages/Documents.razor b/SqlDatabaseVectorSearch/Components/Pages/Documents.razor index 486826c..165064a 100644 --- a/SqlDatabaseVectorSearch/Components/Pages/Documents.razor +++ b/SqlDatabaseVectorSearch/Components/Pages/Documents.razor @@ -177,7 +177,7 @@ else var vectorSearchService = scope.ServiceProvider.GetRequiredService(); var documentId = string.IsNullOrWhiteSpace(Model.DocumentId) ? null : (Guid?)Guid.Parse(Model.DocumentId); - //await vectorSearchService.ImportAsync(stream, fileName, MimeUtility.GetMimeMapping(fileName), documentId); + await vectorSearchService.ImportAsync(EmbeddingRequest.Create(stream, fileName, documentId)); ToastService.Notify(await CreateToastMessageAsync(ToastType.Success, "Upload document", $"The document {fileName} has been successfully uploaded and indexed.")); diff --git a/SqlDatabaseVectorSearch/Components/_Imports.razor b/SqlDatabaseVectorSearch/Components/_Imports.razor index ef23f97..6ef6fce 100644 --- a/SqlDatabaseVectorSearch/Components/_Imports.razor +++ b/SqlDatabaseVectorSearch/Components/_Imports.razor @@ -13,4 +13,5 @@ @using SqlDatabaseVectorSearch.Extensions @using SqlDatabaseVectorSearch.Models @using SqlDatabaseVectorSearch.Services +@using SqlDatabaseVectorSearch.Workflows @using BlazorBootstrap \ No newline at end of file diff --git a/SqlDatabaseVectorSearch/Endpoints/DocumentEndpoints.cs b/SqlDatabaseVectorSearch/Endpoints/DocumentEndpoints.cs index f42caeb..a167f3d 100644 --- a/SqlDatabaseVectorSearch/Endpoints/DocumentEndpoints.cs +++ b/SqlDatabaseVectorSearch/Endpoints/DocumentEndpoints.cs @@ -23,7 +23,7 @@ public class DocumentEndpoints : IEndpointRouteHandlerBuilder documentsApiGroup.MapPost(string.Empty, async (IFormFile file, VectorSearchService vectorSearchService, CancellationToken cancellationToken, [Description("The unique identifier of the document. If not provided, a new one will be generated. If you specify an existing documentId, the corresponding document will be overwritten.")] Guid? documentId = null) => { - var result = await vectorSearchService.ImportAsync(new FormFileEmbeddingRequest(file, documentId), cancellationToken); + var result = await vectorSearchService.ImportAsync(EmbeddingRequest.FromFormFile(file, documentId), cancellationToken); return TypedResults.Ok(result); }) .DisableAntiforgery() diff --git a/SqlDatabaseVectorSearch/Program.cs b/SqlDatabaseVectorSearch/Program.cs index 716dd7e..afdbb93 100644 --- a/SqlDatabaseVectorSearch/Program.cs +++ b/SqlDatabaseVectorSearch/Program.cs @@ -92,18 +92,15 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); // This executor is registered as scoped because it uses the DbContext, which is also scoped. builder.AddWorkflow("EmbeddingWorkflow", (services, key) => { - var formfileToConversionRequestExecutor = services.GetRequiredService(); var generateEmbeddingExecutor = services.GetRequiredService(); var storeEmbeddingExecutor = services.GetRequiredService(); - var workflow = new WorkflowBuilder(formfileToConversionRequestExecutor).WithName(key) - .AddEdge(formfileToConversionRequestExecutor, generateEmbeddingExecutor) + var workflow = new WorkflowBuilder(generateEmbeddingExecutor).WithName(key) .AddEdge(generateEmbeddingExecutor, storeEmbeddingExecutor) .WithOutputFrom(storeEmbeddingExecutor) .Build(validateOrphans: true); diff --git a/SqlDatabaseVectorSearch/Services/VectorSearchService.cs b/SqlDatabaseVectorSearch/Services/VectorSearchService.cs index 71f6045..e6edd4e 100644 --- a/SqlDatabaseVectorSearch/Services/VectorSearchService.cs +++ b/SqlDatabaseVectorSearch/Services/VectorSearchService.cs @@ -17,7 +17,7 @@ namespace SqlDatabaseVectorSearch.Services; public partial class VectorSearchService([FromKeyedServices("EmbeddingWorkflow")] Workflow workflow, [FromKeyedServices("ReformulationAgent")] AIAgent reformulationAgent, [FromKeyedServices("RagAgent")] AIAgent ragAgent, [FromKeyedServices("RagAgent")] AgentSessionStore sessionStore) { - public async Task ImportAsync(FormFileEmbeddingRequest request, CancellationToken cancellationToken = default) + public async Task ImportAsync(EmbeddingRequest request, CancellationToken cancellationToken = default) { await using var run = await InProcessExecution.RunAsync(workflow, request, cancellationToken: cancellationToken); var events = run.NewEvents.ToList(); diff --git a/SqlDatabaseVectorSearch/Workflows/EmbeddingRequest.cs b/SqlDatabaseVectorSearch/Workflows/EmbeddingRequest.cs new file mode 100644 index 0000000..9837162 --- /dev/null +++ b/SqlDatabaseVectorSearch/Workflows/EmbeddingRequest.cs @@ -0,0 +1,26 @@ +namespace SqlDatabaseVectorSearch.Workflows; + +public record class EmbeddingRequest(Stream Content, string FileName, string ContentType, Guid? DocumentId) +{ + /// + /// Creates an from an uploaded . + /// + /// The uploaded file. + /// The optional identifier of the document to overwrite. + public static EmbeddingRequest FromFormFile(IFormFile file, Guid? documentId = null) => Create(file.OpenReadStream(), Path.GetFileName(file.FileName), documentId); + + /// + /// Creates an from a content stream, inferring the content type from the file name. + /// + /// The stream that contains the document content. + /// The name of the document. + /// The optional identifier of the document to overwrite. + /// + /// The content type is inferred from the file name because the content type declared by the client is not always reliable (for example, for Markdown files). + /// + public static EmbeddingRequest Create(Stream content, string fileName, Guid? documentId = null) + { + var name = Path.GetFileName(fileName); + return new EmbeddingRequest(content, name, MimeMapping.MimeUtility.GetMimeMapping(name), documentId); + } +} \ No newline at end of file diff --git a/SqlDatabaseVectorSearch/Workflows/FormFileToEmbeddingRequestExecutor.cs b/SqlDatabaseVectorSearch/Workflows/FormFileToEmbeddingRequestExecutor.cs deleted file mode 100644 index 4e93aa2..0000000 --- a/SqlDatabaseVectorSearch/Workflows/FormFileToEmbeddingRequestExecutor.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.Agents.AI.Workflows; - -namespace SqlDatabaseVectorSearch.Workflows; - -public partial class FormFileToEmbeddingRequestExecutor() : Executor(nameof(FormFileToEmbeddingRequestExecutor)) -{ - [MessageHandler] - private ValueTask HandleAsync(FormFileEmbeddingRequest request, IWorkflowContext context, CancellationToken cancellationToken) - { - // Note: file.ContentType is not 100% reliable (for example, for markdown file). - var embeddingRequest = new EmbeddingRequest(request.File.OpenReadStream(), Path.GetFileName(request.File.FileName), MimeMapping.MimeUtility.GetMimeMapping(request.File.FileName), request.DocumentId); - return ValueTask.FromResult(embeddingRequest); - } -} - -public record class FormFileEmbeddingRequest(IFormFile File, Guid? DocumentId); - -public record class EmbeddingRequest(Stream Content, string FileName, string ContentType, Guid? DocumentId); \ No newline at end of file