From 64df87deb526e77f58db95d55ed4ec9c3d1c1ab3 Mon Sep 17 00:00:00 2001 From: David Pine Date: Fri, 13 Oct 2023 15:54:12 -0500 Subject: [PATCH 1/7] Upgrade to .NET 8 --- app/app.sln | 3 +- .../Extensions/SearchClientExtensions.cs | 30 +++---- app/backend/GlobalUsings.cs | 7 +- app/backend/MinimalApi.csproj | 21 ++--- app/backend/Pages/Error.cshtml.cs | 4 - .../Services/AzureBlobStorageService.cs | 12 +-- .../Services/ReadRetrieveReadChatService.cs | 42 ++++------ app/frontend/ClientApp.csproj | 21 ++--- .../Components/SupportingContent.razor.cs | 2 +- app/frontend/Components/VoiceDialog.razor.cs | 2 +- app/frontend/Pages/Chat.razor.cs | 4 +- app/frontend/Pages/Docs.razor.cs | 2 +- app/frontend/Pages/VoiceChat.razor.cs | 2 +- app/frontend/Services/ApiClient.cs | 16 ++-- app/frontend/Services/OpenAIPromptQueue.cs | 14 ++-- .../EmbedFunctions/EmbedFunctions.csproj | 15 ++-- .../EmbedFunctions/EmbeddingFunction.cs | 17 ++-- .../Services/AzureSearchEmbedService.cs | 78 ++++++++----------- .../Services/EmbedServiceFactory.cs | 15 ++-- .../Services/EmbeddingAggregateService.cs | 19 ++--- app/prepdocs/PrepareDocs/PrepareDocs.csproj | 15 ++-- app/prepdocs/PrepareDocs/Program.cs | 22 +++--- .../Shared/Models/UploadDocumentsResponse.cs | 2 +- app/shared/Shared/Shared.csproj | 3 +- .../ClientApp.Tests/ClientApp.Tests.csproj | 13 ++-- 25 files changed, 162 insertions(+), 219 deletions(-) diff --git a/app/app.sln b/app/app.sln index 61d64b61..ee222295 100644 --- a/app/app.sln +++ b/app/app.sln @@ -14,7 +14,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\.github\workflows\azure-dev.yml = ..\.github\workflows\azure-dev.yml Directory.Build.props = Directory.Build.props ..\.github\workflows\dotnet-build.yml = ..\.github\workflows\dotnet-build.yml - global.json = global.json ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md EndProjectSection @@ -25,7 +24,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientApp.Tests", "tests\Cl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PrepareDocs", "prepdocs\PrepareDocs\PrepareDocs.csproj", "{9C39AF95-2BB1-48CF-BD12-D214C0BD6076}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbedFunctions", "functions\EmbedFunctions\EmbedFunctions.csproj", "{54099AF3-CFA8-4EA9-9118-A26E3E63745B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmbedFunctions", "functions\EmbedFunctions\EmbedFunctions.csproj", "{54099AF3-CFA8-4EA9-9118-A26E3E63745B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/app/backend/Extensions/SearchClientExtensions.cs b/app/backend/Extensions/SearchClientExtensions.cs index d1bed905..109e2a4a 100644 --- a/app/backend/Extensions/SearchClientExtensions.cs +++ b/app/backend/Extensions/SearchClientExtensions.cs @@ -35,21 +35,23 @@ internal static async Task QueryDocumentsAsync( Size = top, }; - if (embedding != null && overrides?.RetrievalMode != "Text") - { - var k = useSemanticRanker ? 50 : top; - var vectorQuery = new SearchQueryVector - { - // if semantic ranker is enabled, we need to set the rank to a large number to get more - // candidates for semantic reranking - KNearestNeighborsCount = useSemanticRanker ? 50 : top, - Value = embedding, - }; - vectorQuery.Fields.Add("embedding"); - searchOption.Vectors.Add(vectorQuery); - } + // TODO: Fix this as SearchQueryVector doesn't exist anymore + //if (embedding != null && overrides?.RetrievalMode != "Text") + //{ + // var k = useSemanticRanker ? 50 : top; + // var vectorQuery = new SearchQueryVector + // { + // // if semantic ranker is enabled, we need to set the rank to a large number to get more + // // candidates for semantic reranking + // KNearestNeighborsCount = useSemanticRanker ? 50 : top, + // Value = embedding, + // }; + // vectorQuery.Fields.Add("embedding"); + // searchOption.Vectors.Add(vectorQuery); + //} - var searchResultResponse = await searchClient.SearchAsync(query, searchOption, cancellationToken); + var searchResultResponse = await searchClient.SearchAsync( + query, searchOption, cancellationToken); if (searchResultResponse.Value is null) { throw new InvalidOperationException("fail to get search result"); diff --git a/app/backend/GlobalUsings.cs b/app/backend/GlobalUsings.cs index 258b8220..ffb07be4 100644 --- a/app/backend/GlobalUsings.cs +++ b/app/backend/GlobalUsings.cs @@ -13,14 +13,9 @@ global using Azure.Storage.Blobs.Models; global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc.RazorPages; -global using Microsoft.ML; -global using Microsoft.ML.Transforms.Text; global using Microsoft.SemanticKernel; -global using Microsoft.SemanticKernel.AI; +global using Microsoft.SemanticKernel.AI.ChatCompletion; global using Microsoft.SemanticKernel.AI.Embeddings; -global using Microsoft.SemanticKernel.AI.TextCompletion; -global using Microsoft.SemanticKernel.Memory; -global using Microsoft.SemanticKernel.Orchestration; global using MinimalApi.Extensions; global using MinimalApi.Services; global using PdfSharpCore.Pdf; diff --git a/app/backend/MinimalApi.csproj b/app/backend/MinimalApi.csproj index bba36b37..c168b292 100644 --- a/app/backend/MinimalApi.csproj +++ b/app/backend/MinimalApi.csproj @@ -1,9 +1,10 @@  - net7.0 + net8.0 enable enable + preview 6c0daa7e-5118-4a21-8aeb-f7b977fe2f01 true Linux @@ -12,18 +13,18 @@ - - - - + + + + - + - + - - - + + + diff --git a/app/backend/Pages/Error.cshtml.cs b/app/backend/Pages/Error.cshtml.cs index 12e265f2..383429d1 100644 --- a/app/backend/Pages/Error.cshtml.cs +++ b/app/backend/Pages/Error.cshtml.cs @@ -10,9 +10,5 @@ public sealed class ErrorModel : PageModel public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) => _logger = logger; - public void OnGet() => RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; } diff --git a/app/backend/Services/AzureBlobStorageService.cs b/app/backend/Services/AzureBlobStorageService.cs index 9515649c..f0ac3a2b 100644 --- a/app/backend/Services/AzureBlobStorageService.cs +++ b/app/backend/Services/AzureBlobStorageService.cs @@ -2,19 +2,15 @@ namespace MinimalApi.Services; -internal sealed class AzureBlobStorageService +internal sealed class AzureBlobStorageService(BlobContainerClient container) { internal static DefaultAzureCredential DefaultCredential { get; } = new(); - private readonly BlobContainerClient _container; - - public AzureBlobStorageService(BlobContainerClient container) => _container = container; - internal async Task UploadFilesAsync(IEnumerable files, CancellationToken cancellationToken) { try { - var uploadedFiles = new List(); + List uploadedFiles = []; foreach (var file in files) { var fileName = file.FileName; @@ -25,7 +21,7 @@ internal async Task UploadFilesAsync(IEnumerable ReplyAsync( : throw new InvalidOperationException("Use question is null"); if (overrides?.RetrievalMode != "Text" && embedding is not null) { - embeddings = (await embedding.GenerateEmbeddingAsync(question)).ToArray(); + embeddings = (await embedding.GenerateEmbeddingAsync(question, cancellationToken: cancellationToken)).ToArray(); } // step 1 @@ -66,12 +65,7 @@ standard plan AND dental AND employee benefit. getQueryChat.AddUserMessage(question); var result = await chat.GetChatCompletionsAsync( getQueryChat, - new ChatRequestSettings - { - Temperature = 0, - MaxTokens = 128, - }, - cancellationToken); + cancellationToken: cancellationToken); if (result.Count != 1) { @@ -80,7 +74,7 @@ standard plan AND dental AND employee benefit. query = result[0].ModelResult.GetOpenAIChatResult().Choice.Message.Content; } - + // step 2 // use query to search related docs var documentContents = await _searchClient.QueryDocumentsAsync(query, embeddings, overrides, cancellationToken); @@ -93,7 +87,9 @@ standard plan AND dental AND employee benefit. Console.WriteLine(documentContents); // step 3 // put together related docs and conversation history to generate answer - var answerChat = chat.CreateNewChat($@"You are a system assistant who helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers"); + var answerChat = chat.CreateNewChat( + "You are a system assistant who helps the company employees with their healthcare " + + "plan questions, and questions about the employee handbook. Be brief in your answers"); // add chat history foreach (var turn in history) @@ -119,12 +115,7 @@ You answer needs to be a json object with the following format. // get answer var answer = await chat.GetChatCompletionsAsync( answerChat, - new ChatRequestSettings - { - Temperature = overrides?.Temperature ?? 0.7, - MaxTokens = 1024, - }, - cancellationToken); + cancellationToken: cancellationToken); var answerJson = answer[0].ModelResult.GetOpenAIChatResult().Choice.Message.Content; var answerObject = JsonSerializer.Deserialize(answerJson); var ans = answerObject.GetProperty("answer").GetString() ?? throw new InvalidOperationException("Failed to get answer"); @@ -149,13 +140,8 @@ Return the follow-up question as a json string list. ]"); var followUpQuestions = await chat.GetChatCompletionsAsync( - followUpQuestionChat, - new ChatRequestSettings - { - Temperature = 0, - MaxTokens = 256, - }, - cancellationToken); + followUpQuestionChat, + cancellationToken: cancellationToken); var followUpQuestionsJson = followUpQuestions[0].ModelResult.GetOpenAIChatResult().Choice.Message.Content; var followUpQuestionsObject = JsonSerializer.Deserialize(followUpQuestionsJson); diff --git a/app/frontend/ClientApp.csproj b/app/frontend/ClientApp.csproj index 55d0e5ef..d13bbd07 100644 --- a/app/frontend/ClientApp.csproj +++ b/app/frontend/ClientApp.csproj @@ -1,25 +1,26 @@  - net7.0 + net8.0 enable enable + preview service-worker-assets.js true 48daa172-8fe4-4b81-94b2-0d5a3a5ad30e - - - - - - - + + + + + + + - - + + diff --git a/app/frontend/Components/SupportingContent.razor.cs b/app/frontend/Components/SupportingContent.razor.cs index 95345625..b3980ec9 100644 --- a/app/frontend/Components/SupportingContent.razor.cs +++ b/app/frontend/Components/SupportingContent.razor.cs @@ -6,7 +6,7 @@ public sealed partial class SupportingContent { [Parameter, EditorRequired] public required string[] DataPoints { get; set; } - private ParsedSupportingContentItem[] _supportingContent = Array.Empty(); + private ParsedSupportingContentItem[] _supportingContent = []; protected override void OnParametersSet() { diff --git a/app/frontend/Components/VoiceDialog.razor.cs b/app/frontend/Components/VoiceDialog.razor.cs index ba671239..17616c9f 100644 --- a/app/frontend/Components/VoiceDialog.razor.cs +++ b/app/frontend/Components/VoiceDialog.razor.cs @@ -4,7 +4,7 @@ namespace ClientApp.Components; public sealed partial class VoiceDialog : IDisposable { - private SpeechSynthesisVoice[] _voices = Array.Empty(); + private SpeechSynthesisVoice[] _voices = []; private readonly IList _voiceSpeeds = Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList(); private VoicePreferences? _voicePreferences; diff --git a/app/frontend/Pages/Chat.razor.cs b/app/frontend/Pages/Chat.razor.cs index 3f3a4103..f5c5aa50 100644 --- a/app/frontend/Pages/Chat.razor.cs +++ b/app/frontend/Pages/Chat.razor.cs @@ -9,7 +9,7 @@ public sealed partial class Chat private string _lastReferenceQuestion = ""; private bool _isReceivingResponse = false; - private readonly Dictionary _questionAndAnswerMap = new(); + private readonly Dictionary _questionAndAnswerMap = []; [Inject] public required ISessionStorageService SessionStorage { get; set; } @@ -48,7 +48,7 @@ private async Task OnAskClickedAsync() history.Add(new ChatTurn(_userQuestion)); - var request = new ChatRequest(history.ToArray(), Settings.Approach, Settings.Overrides); + var request = new ChatRequest([.. history], Settings.Approach, Settings.Overrides); var result = await ApiClient.ChatConversationAsync(request); _questionAndAnswerMap[_currentQuestion] = result.Response; diff --git a/app/frontend/Pages/Docs.razor.cs b/app/frontend/Pages/Docs.razor.cs index b1a86b89..4d3d0083 100644 --- a/app/frontend/Pages/Docs.razor.cs +++ b/app/frontend/Pages/Docs.razor.cs @@ -14,7 +14,7 @@ public sealed partial class Docs : IDisposable // Store a cancelation token that will be used to cancel if the user disposes of this component. private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly HashSet _documents = new(); + private readonly HashSet _documents = []; [Inject] public required ApiClient Client { get; set; } diff --git a/app/frontend/Pages/VoiceChat.razor.cs b/app/frontend/Pages/VoiceChat.razor.cs index 62de2bd3..ea847fe5 100644 --- a/app/frontend/Pages/VoiceChat.razor.cs +++ b/app/frontend/Pages/VoiceChat.razor.cs @@ -11,7 +11,7 @@ public sealed partial class VoiceChat : IDisposable private bool _isReadingResponse = false; private IDisposable? _recognitionSubscription; private VoicePreferences? _voicePreferences; - private readonly Dictionary _questionAndAnswerMap = new(); + private readonly Dictionary _questionAndAnswerMap = []; private readonly MarkdownPipeline _pipeline = new MarkdownPipelineBuilder() .ConfigureNewLine("\n") .UseAdvancedExtensions() diff --git a/app/frontend/Services/ApiClient.cs b/app/frontend/Services/ApiClient.cs index 46c8b5ca..c751bbc6 100644 --- a/app/frontend/Services/ApiClient.cs +++ b/app/frontend/Services/ApiClient.cs @@ -4,15 +4,11 @@ namespace ClientApp.Services; -public sealed class ApiClient +public sealed class ApiClient(HttpClient httpClient) { - private readonly HttpClient _httpClient; - - public ApiClient(HttpClient httpClient) => _httpClient = httpClient; - public async Task RequestImageAsync(PromptRequest request) { - var response = await _httpClient.PostAsJsonAsync( + var response = await httpClient.PostAsJsonAsync( "api/images", request, SerializerOptions.Default); response.EnsureSuccessStatusCode(); @@ -38,7 +34,7 @@ public async Task UploadDocumentsAsync( content.Add(fileContent, file.Name, file.Name); } - var response = await _httpClient.PostAsync("api/documents", content); + var response = await httpClient.PostAsync("api/documents", content); response.EnsureSuccessStatusCode(); @@ -58,7 +54,7 @@ public async Task UploadDocumentsAsync( public async IAsyncEnumerable GetDocumentsAsync( [EnumeratorCancellation] CancellationToken cancellationToken) { - var response = await _httpClient.GetAsync("api/documents", cancellationToken); + var response = await httpClient.GetAsync("api/documents", cancellationToken); if (response.IsSuccessStatusCode) { @@ -97,7 +93,7 @@ private async Task> PostRequestAsync( using var body = new StringContent( json, Encoding.UTF8, "application/json"); - var response = await _httpClient.PostAsync(apiRoute, body); + var response = await httpClient.PostAsync(apiRoute, body); if (response.IsSuccessStatusCode) { @@ -113,7 +109,7 @@ private async Task> PostRequestAsync( var answer = new ApproachResponse( $"HTTP {(int)response.StatusCode} : {response.ReasonPhrase ?? "☹️ Unknown error..."}", null, - Array.Empty(), + [], "Unable to retrieve valid response from the server."); return result with diff --git a/app/frontend/Services/OpenAIPromptQueue.cs b/app/frontend/Services/OpenAIPromptQueue.cs index f432ef82..5d64df30 100644 --- a/app/frontend/Services/OpenAIPromptQueue.cs +++ b/app/frontend/Services/OpenAIPromptQueue.cs @@ -2,15 +2,13 @@ namespace ClientApp.Services; -public sealed class OpenAIPromptQueue +public sealed class OpenAIPromptQueue( + IServiceProvider provider, + ILogger logger) { - private readonly IServiceProvider _provider; - private readonly ILogger _logger; private readonly StringBuilder _responseBuffer = new(); private Task? _processPromptTask = null; - public OpenAIPromptQueue(IServiceProvider provider, ILogger logger) => (_provider, _logger) = (provider, logger); - public void Enqueue(string prompt, Func handler) { if (_processPromptTask is not null) @@ -27,7 +25,7 @@ public void Enqueue(string prompt, Func handler) new PromptRequest { Prompt = prompt }, options); using var body = new StringContent(json, Encoding.UTF8, "application/json"); - using var scope = _provider.CreateScope(); + using var scope = provider.CreateScope(); var factory = scope.ServiceProvider.GetRequiredService(); using var client = factory.CreateClient(typeof(ApiClient).Name); @@ -47,7 +45,7 @@ public void Enqueue(string prompt, Func handler) _responseBuffer.Append(chunk.Text); - var responseText = NormalizeResponseText(_responseBuffer, _logger); + var responseText = NormalizeResponseText(_responseBuffer, logger); await handler( new PromptResponse( prompt, responseText)); @@ -65,7 +63,7 @@ await handler( { if (_responseBuffer.Length > 0) { - var responseText = NormalizeResponseText(_responseBuffer, _logger); + var responseText = NormalizeResponseText(_responseBuffer, logger); await handler( new PromptResponse( prompt, responseText, true)); diff --git a/app/functions/EmbedFunctions/EmbedFunctions.csproj b/app/functions/EmbedFunctions/EmbedFunctions.csproj index 1477a363..0d44656d 100644 --- a/app/functions/EmbedFunctions/EmbedFunctions.csproj +++ b/app/functions/EmbedFunctions/EmbedFunctions.csproj @@ -1,23 +1,24 @@  - net7.0 + net8.0 v4 Exe enable enable + preview feba7d78-952f-423c-95ed-d6a4051dd15f - - - + + + - - + + - + diff --git a/app/functions/EmbedFunctions/EmbeddingFunction.cs b/app/functions/EmbedFunctions/EmbeddingFunction.cs index 15ea0388..ed1ecf7f 100644 --- a/app/functions/EmbedFunctions/EmbeddingFunction.cs +++ b/app/functions/EmbedFunctions/EmbeddingFunction.cs @@ -2,18 +2,11 @@ using Microsoft.Azure.Functions.Worker; -public sealed class EmbeddingFunction +public sealed class EmbeddingFunction( + EmbeddingAggregateService embeddingAggregateService, + ILoggerFactory loggerFactory) { - private readonly EmbeddingAggregateService _embeddingAggregateService; - private readonly ILogger _logger; - - public EmbeddingFunction( - EmbeddingAggregateService embeddingAggregateService, - ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _embeddingAggregateService = embeddingAggregateService; - } + private readonly ILogger _logger = loggerFactory.CreateLogger(); [Function(name: "embed-blob")] public Task EmbedAsync( @@ -21,5 +14,5 @@ public Task EmbedAsync( blobPath: "content/{name}", Connection = "AzureStorageAccountEndpoint")] Stream blobStream, string name, - BlobClient client) => _embeddingAggregateService.EmbedBlobAsync(client, blobStream, blobName: name); + BlobClient client) => embeddingAggregateService.EmbedBlobAsync(client, blobStream, blobName: name); } diff --git a/app/functions/EmbedFunctions/Services/AzureSearchEmbedService.cs b/app/functions/EmbedFunctions/Services/AzureSearchEmbedService.cs index 82d1b784..aaa19167 100644 --- a/app/functions/EmbedFunctions/Services/AzureSearchEmbedService.cs +++ b/app/functions/EmbedFunctions/Services/AzureSearchEmbedService.cs @@ -2,31 +2,16 @@ namespace EmbedFunctions.Services; -internal sealed partial class AzureSearchEmbedService : IEmbedService +internal sealed partial class AzureSearchEmbedService( + SearchClient indexSectionClient, + SearchIndexClient searchIndexClient, + DocumentAnalysisClient documentAnalysisClient, + BlobContainerClient corpusContainerClient, + ILogger logger) : IEmbedService { [GeneratedRegex("[^0-9a-zA-Z_-]")] private static partial Regex MatchInSetRegex(); - private readonly SearchClient _indexSectionClient; - private readonly SearchIndexClient _searchIndexClient; - private readonly DocumentAnalysisClient _documentAnalysisClient; - private readonly BlobContainerClient _corpusContainerClient; - private readonly ILogger _logger; - - public AzureSearchEmbedService( - SearchClient indexSectionClient, - SearchIndexClient searchIndexClient, - DocumentAnalysisClient documentAnalysisClient, - BlobContainerClient corpusContainerClient, - ILogger logger) - { - _indexSectionClient = indexSectionClient; - _searchIndexClient = searchIndexClient; - _documentAnalysisClient = documentAnalysisClient; - _corpusContainerClient = corpusContainerClient; - _logger = logger; - } - public async Task EmbedBlobAsync(Stream blobStream, string blobName) { try @@ -56,7 +41,7 @@ public async Task EmbedBlobAsync(Stream blobStream, string blobName) } catch (Exception exception) { - _logger.LogError( + logger.LogError( exception, "Failed to embed blob '{BlobName}'", blobName); return false; @@ -65,12 +50,12 @@ public async Task EmbedBlobAsync(Stream blobStream, string blobName) private async Task EnsureSearchIndexAsync(string searchIndexName) { - var indexNames = _searchIndexClient.GetIndexNamesAsync(); + var indexNames = searchIndexClient.GetIndexNamesAsync(); await foreach (var page in indexNames.AsPages()) { if (page.Values.Any(indexName => indexName == searchIndexName)) { - _logger.LogWarning( + logger.LogWarning( "Search index '{SearchIndexName}' already exists", searchIndexName); return; } @@ -104,22 +89,22 @@ private async Task EnsureSearchIndexAsync(string searchIndexName) } }; - _logger.LogInformation( + logger.LogInformation( "Creating '{searchIndexName}' search index", searchIndexName); - await _searchIndexClient.CreateIndexAsync(index); + await searchIndexClient.CreateIndexAsync(index); } private async Task> GetDocumentTextAsync(Stream blobStream, string blobName) { - _logger.LogInformation( + logger.LogInformation( "Extracting text from '{Blob}' using Azure Form Recognizer", blobName); - AnalyzeDocumentOperation operation = _documentAnalysisClient.AnalyzeDocument( + AnalyzeDocumentOperation operation = documentAnalysisClient.AnalyzeDocument( WaitUntil.Started, "prebuilt-layout", blobStream); var offset = 0; - List pageMap = new(); + List pageMap = []; var results = await operation.WaitForCompletionAsync(); var pages = results.Value.Pages; @@ -150,7 +135,7 @@ private async Task> GetDocumentTextAsync(Stream blobSt // Build page text by replacing characters in table spans with table HTML StringBuilder pageText = new(); - HashSet addedTables = new(); + HashSet addedTables = []; for (int j = 0; j < tableChars.Length; j++) { if (tableChars[j] == -1) @@ -178,9 +163,12 @@ private static string TableToHtml(DocumentTable table) var rows = new List[table.RowCount]; for (int i = 0; i < table.RowCount; i++) { - rows[i] = table.Cells.Where(c => c.RowIndex == i) - .OrderBy(c => c.ColumnIndex) - .ToList(); + rows[i] = + [ + .. table.Cells.Where(c => c.RowIndex == i) + .OrderBy(c => c.ColumnIndex) +, + ]; } foreach (var rowCells in rows) @@ -214,13 +202,13 @@ private static string TableToHtml(DocumentTable table) private async Task UploadCorpusAsync(string corpusBlobName, string text) { - var blobClient = _corpusContainerClient.GetBlobClient(corpusBlobName); + var blobClient = corpusContainerClient.GetBlobClient(corpusBlobName); if (await blobClient.ExistsAsync()) { return; } - _logger.LogInformation("Uploading corpus '{CorpusBlobName}'", corpusBlobName); + logger.LogInformation("Uploading corpus '{CorpusBlobName}'", corpusBlobName); await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(text)); await blobClient.UploadAsync(stream, new BlobHttpHeaders @@ -243,7 +231,7 @@ private IEnumerable
CreateSections( var start = 0; var end = length; - _logger.LogInformation("Splitting '{BlobName}' into sections", blobName); + logger.LogInformation("Splitting '{BlobName}' into sections", blobName); while (start + SectionOverlap < length) { @@ -312,9 +300,9 @@ private IEnumerable
CreateSections( // If the section ends with an unclosed table, we need to start the next section with the table. // If table starts inside SentenceSearchLimit, we ignore it, as that will cause an infinite loop for tables longer than MaxSectionLength // If last table starts inside SectionOverlap, keep overlapping - if (_logger.IsEnabled(LogLevel.Warning)) + if (logger.IsEnabled(LogLevel.Warning)) { - _logger.LogWarning(""" + logger.LogWarning(""" Section ends with unclosed table, starting next section with the table at page {Offset} offset {Start} table start {LastTableStart} """, @@ -361,10 +349,10 @@ private static string BlobNameFromFilePage(string blobName, int page = 0) => Pat private async Task IndexSectionsAsync(string searchIndexName, IEnumerable
sections, string blobName) { - var infoLoggingEnabled = _logger.IsEnabled(LogLevel.Information); + var infoLoggingEnabled = logger.IsEnabled(LogLevel.Information); if (infoLoggingEnabled) { - _logger.LogInformation(""" + logger.LogInformation(""" Indexing sections from '{BlobName}' into search index '{SearchIndexName}' """, blobName, @@ -390,11 +378,11 @@ Indexing sections from '{BlobName}' into search index '{SearchIndexName}' if (iteration % 1_000 is 0) { // Every one thousand documents, batch create. - IndexDocumentsResult result = await _indexSectionClient.IndexDocumentsAsync(batch); + IndexDocumentsResult result = await indexSectionClient.IndexDocumentsAsync(batch); int succeeded = result.Results.Count(r => r.Succeeded); if (infoLoggingEnabled) { - _logger.LogInformation(""" + logger.LogInformation(""" Indexed {Count} sections, {Succeeded} succeeded """, batch.Actions.Count, @@ -409,11 +397,11 @@ Indexing sections from '{BlobName}' into search index '{SearchIndexName}' { // Any remaining documents, batch create. var index = new SearchIndex($"index-{batch.Actions.Count}"); - IndexDocumentsResult result = await _indexSectionClient.IndexDocumentsAsync(batch); + IndexDocumentsResult result = await indexSectionClient.IndexDocumentsAsync(batch); int succeeded = result.Results.Count(r => r.Succeeded); - if (_logger.IsEnabled(LogLevel.Information)) + if (logger.IsEnabled(LogLevel.Information)) { - _logger.LogInformation(""" + logger.LogInformation(""" Indexed {Count} sections, {Succeeded} succeeded """, batch.Actions.Count, diff --git a/app/functions/EmbedFunctions/Services/EmbedServiceFactory.cs b/app/functions/EmbedFunctions/Services/EmbedServiceFactory.cs index 8668aa51..246b2fdc 100644 --- a/app/functions/EmbedFunctions/Services/EmbedServiceFactory.cs +++ b/app/functions/EmbedFunctions/Services/EmbedServiceFactory.cs @@ -2,26 +2,21 @@ namespace EmbedFunctions.Services; -public sealed class EmbedServiceFactory +public sealed class EmbedServiceFactory(IEnumerable embedServices) { - private readonly IEnumerable _embedServices = - Array.Empty(); - - public EmbedServiceFactory(IEnumerable embedServices) => _embedServices = embedServices; - public IEmbedService GetEmbedService(EmbeddingType embeddingType) => embeddingType switch { EmbeddingType.AzureSearch => - _embedServices.OfType().Single(), + embedServices.OfType().Single(), EmbeddingType.Pinecone => - _embedServices.OfType().Single(), + embedServices.OfType().Single(), EmbeddingType.Qdrant => - _embedServices.OfType().Single(), + embedServices.OfType().Single(), EmbeddingType.Milvus => - _embedServices.OfType().Single(), + embedServices.OfType().Single(), _ => throw new ArgumentException( $"Unsupported embedding type: {embeddingType}", nameof(embeddingType)) diff --git a/app/functions/EmbedFunctions/Services/EmbeddingAggregateService.cs b/app/functions/EmbedFunctions/Services/EmbeddingAggregateService.cs index bc1f5a40..bf325732 100644 --- a/app/functions/EmbedFunctions/Services/EmbeddingAggregateService.cs +++ b/app/functions/EmbedFunctions/Services/EmbeddingAggregateService.cs @@ -2,25 +2,16 @@ namespace EmbedFunctions.Services; -public sealed class EmbeddingAggregateService +public sealed class EmbeddingAggregateService( + EmbedServiceFactory embedServiceFactory, + ILogger logger) { - private readonly EmbedServiceFactory _embedServiceFactory; - private readonly ILogger _logger; - - public EmbeddingAggregateService( - EmbedServiceFactory embedServiceFactory, - ILogger logger) - { - _embedServiceFactory = embedServiceFactory; - _logger = logger; - } - internal async Task EmbedBlobAsync(BlobClient client, Stream blobStream, string blobName) { try { var embeddingType = GetEmbeddingType(); - var embedService = _embedServiceFactory.GetEmbedService(embeddingType); + var embedService = embedServiceFactory.GetEmbedService(embeddingType); var result = await embedService.EmbedBlobAsync(blobStream, blobName); @@ -38,7 +29,7 @@ await client.SetMetadataAsync(new Dictionary } catch (Exception ex) { - _logger.LogError(ex, "Failed to embed: {Name}, error: {Message}", blobName, ex.Message); + logger.LogError(ex, "Failed to embed: {Name}, error: {Message}", blobName, ex.Message); } } diff --git a/app/prepdocs/PrepareDocs/PrepareDocs.csproj b/app/prepdocs/PrepareDocs/PrepareDocs.csproj index b5188169..b9d36e3e 100644 --- a/app/prepdocs/PrepareDocs/PrepareDocs.csproj +++ b/app/prepdocs/PrepareDocs/PrepareDocs.csproj @@ -2,20 +2,21 @@ Exe - net7.0 + net8.0 enable enable + preview - - - - + + + + - - + + diff --git a/app/prepdocs/PrepareDocs/Program.cs b/app/prepdocs/PrepareDocs/Program.cs index 896141f4..463127f8 100644 --- a/app/prepdocs/PrepareDocs/Program.cs +++ b/app/prepdocs/PrepareDocs/Program.cs @@ -186,7 +186,7 @@ static async ValueTask CreateSearchIndexAsync(AppOptions options) { VectorSearch = new() { - AlgorithmConfigurations = + Algorithms = { new HnswVectorSearchAlgorithmConfiguration(vectorSearchConfigName) } @@ -202,7 +202,7 @@ static async ValueTask CreateSearchIndexAsync(AppOptions options) { VectorSearchDimensions = 1536, IsSearchable = true, - VectorSearchConfiguration = vectorSearchConfigName, + SearchAnalyzerName = vectorSearchConfigName, // TODO: is this right? } }, SemanticSettings = new SemanticSettings @@ -341,7 +341,7 @@ static async ValueTask> GetDocumentTextAsync( WaitUntil.Started, "prebuilt-layout", stream); var offset = 0; - List pageMap = new(); + List pageMap = []; var results = await operation.WaitForCompletionAsync(); var pages = results.Value.Pages; @@ -372,7 +372,7 @@ static async ValueTask> GetDocumentTextAsync( // Build page text by replacing characters in table spans with table HTML StringBuilder pageText = new(); - HashSet addedTables = new(); + HashSet addedTables = []; for (int j = 0; j < tableChars.Length; j++) { if (tableChars[j] == -1) @@ -401,8 +401,8 @@ static IEnumerable
CreateSections( const int SentenceSearchLimit = 100; const int SectionOverlap = 100; - var sentenceEndings = new[] { '.', '!', '?' }; - var wordBreaks = new[] { ',', ';', ':', ' ', '(', ')', '[', ']', '{', '}', '\t', '\n' }; + char[] sentenceEndings = ['.', '!', '?']; + char[] wordBreaks = [',', ';', ':', ' ', '(', ')', '[', ']', '{', '}', '\t', '\n']; var allText = string.Concat(pageMap.Select(p => p.Text)); var length = allText.Length; var start = 0; @@ -528,7 +528,7 @@ Indexing sections from '{fileName}' into search index '{options.SearchIndexName} foreach (var section in sections) { var embeddings = await openAIClient.GetEmbeddingsAsync(options.EmbeddingModelName, new Azure.AI.OpenAI.EmbeddingsOptions(section.Content.Replace('\r', ' '))); - var embedding = embeddings.Value.Data.FirstOrDefault()?.Embedding.ToArray() ?? new float[0]; + var embedding = embeddings.Value.Data.FirstOrDefault()?.Embedding.ToArray() ?? []; batch.Actions.Add(new IndexDocumentsAction( IndexActionType.MergeOrUpload, new SearchDocument @@ -583,9 +583,11 @@ static string TableToHtml(DocumentTable table) var rows = new List[table.RowCount]; for (int i = 0; i < table.RowCount; i++) { - rows[i] = table.Cells.Where(c => c.RowIndex == i) - .OrderBy(c => c.ColumnIndex) - .ToList(); + rows[i] = + [ + .. table.Cells.Where(c => c.RowIndex == i) + .OrderBy(c => c.ColumnIndex) + ]; } foreach (var rowCells in rows) diff --git a/app/shared/Shared/Models/UploadDocumentsResponse.cs b/app/shared/Shared/Models/UploadDocumentsResponse.cs index f72307f4..a6263e84 100644 --- a/app/shared/Shared/Models/UploadDocumentsResponse.cs +++ b/app/shared/Shared/Models/UploadDocumentsResponse.cs @@ -13,5 +13,5 @@ public record class UploadDocumentsResponse( }; public static UploadDocumentsResponse FromError(string error) => - new(Array.Empty(), error); + new([], error); } diff --git a/app/shared/Shared/Shared.csproj b/app/shared/Shared/Shared.csproj index cfadb03d..c586634c 100644 --- a/app/shared/Shared/Shared.csproj +++ b/app/shared/Shared/Shared.csproj @@ -1,9 +1,10 @@ - net7.0 + net8.0 enable enable + preview diff --git a/app/tests/ClientApp.Tests/ClientApp.Tests.csproj b/app/tests/ClientApp.Tests/ClientApp.Tests.csproj index 5b88db03..16673c5f 100644 --- a/app/tests/ClientApp.Tests/ClientApp.Tests.csproj +++ b/app/tests/ClientApp.Tests/ClientApp.Tests.csproj @@ -1,18 +1,19 @@  - net7.0 + net8.0 enable enable false + preview - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all From f33ae39948b39d3ea44192d02010a03e53c52c84 Mon Sep 17 00:00:00 2001 From: David Pine Date: Sat, 14 Oct 2023 07:30:41 -0500 Subject: [PATCH 2/7] Update app/backend/Extensions/SearchClientExtensions.cs --- .../Extensions/SearchClientExtensions.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/backend/Extensions/SearchClientExtensions.cs b/app/backend/Extensions/SearchClientExtensions.cs index 109e2a4a..cdb19188 100644 --- a/app/backend/Extensions/SearchClientExtensions.cs +++ b/app/backend/Extensions/SearchClientExtensions.cs @@ -35,20 +35,19 @@ internal static async Task QueryDocumentsAsync( Size = top, }; - // TODO: Fix this as SearchQueryVector doesn't exist anymore - //if (embedding != null && overrides?.RetrievalMode != "Text") - //{ - // var k = useSemanticRanker ? 50 : top; - // var vectorQuery = new SearchQueryVector - // { - // // if semantic ranker is enabled, we need to set the rank to a large number to get more - // // candidates for semantic reranking - // KNearestNeighborsCount = useSemanticRanker ? 50 : top, - // Value = embedding, - // }; - // vectorQuery.Fields.Add("embedding"); - // searchOption.Vectors.Add(vectorQuery); - //} + if (embedding != null && overrides?.RetrievalMode != "Text") + { + var k = useSemanticRanker ? 50 : top; + var vectorQuery = new RawVectorQuery + { + // if semantic ranker is enabled, we need to set the rank to a large number to get more + // candidates for semantic reranking + KNearestNeighborsCount = useSemanticRanker ? 50 : top, + Value = embedding, + }; + vectorQuery.Fields.Add("embedding"); + searchOption.Vectors.Add(vectorQuery); + } var searchResultResponse = await searchClient.SearchAsync( query, searchOption, cancellationToken); From 7aaf26b4f185143e56a8a5e0d7403103367abe83 Mon Sep 17 00:00:00 2001 From: David Pine Date: Sat, 14 Oct 2023 07:54:42 -0500 Subject: [PATCH 3/7] Pulled a few bits from Aaron's PR --- .devcontainer/devcontainer.json | 81 +++++++++--------- .github/workflows/azure-dev.yml | 2 +- .github/workflows/dotnet-build.yml | 2 +- .vscode/extensions.json | 22 +++-- .vscode/launch.json | 82 +++++++++---------- .vscode/settings.json | 24 ++++-- .../Extensions/SearchClientExtensions.cs | 4 +- 7 files changed, 117 insertions(+), 100 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a0d9da6d..3b60b86a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,42 +1,45 @@ { - "name": "Azure Developer CLI", - "image": "mcr.microsoft.com/devcontainers/dotnet:0-7.0-bullseye", - "features": { - "ghcr.io/devcontainers/features/docker-in-docker:2": {}, - "ghcr.io/devcontainers/features/azure-cli:1.2.1": {}, - "ghcr.io/devcontainers/features/github-cli:1.0.9": {}, - "ghcr.io/azure/azure-dev/azd:latest": {} + "name": "Azure Search OpenAI Demo - C#", + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/azure-cli:1.2.1": {}, + "ghcr.io/devcontainers/features/github-cli:1.0.9": {}, + "ghcr.io/devcontainers/features/powershell:1.1.0": {}, + "ghcr.io/azure/azure-dev/azd:latest": {}, + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "8.0" }, - "customizations": { - "vscode": { - "extensions": [ - "ms-azuretools.azure-dev", - "ms-azuretools.vscode-bicep", - "ms-azuretools.vscode-docker", - "ms-vscode.vscode-node-azure-pack", - "ms-dotnettools.csharp", - "ms-dotnettools.vscode-dotnet-runtime", - "ms-azuretools.vscode-azurefunctions", - "ms-azuretools.vscode-cosmosdb", - "ms-azuretools.vscode-azurestorage", - "ms-vscode.azurecli", - "ms-kubernetes-tools.vscode-aks-tools", - "ms-kubernetes-tools.aks-devx-tools", - "ms-azuretools.vscode-azurecontainerapps", - "ms-azuretools.vscode-azureeventgrid", - "ms-kubernetes-tools.vscode-kubernetes-tools", - "ipedrazas.kubernetes-snippets", - "redhat.vscode-yaml", - "GitHub.vscode-github-actions" - ] - } - }, - "forwardPorts": [ - 5000 - ], - "postCreateCommand": "", - "remoteUser": "vscode", - "hostRequirements": { - "memory": "8gb" + "ghcr.io/devcontainers/features/node:1": { + "version": "20.6.1" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-azuretools.vscode-docker", + "ms-azuretools.vscode-azurefunctions", + "ms-azuretools.vscode-azurestorage", + "ms-azuretools.vscode-azurecontainerapps", + "ms-dotnettools.csdevkit", + "ms-dotnettools.vscode-dotnet-runtime", + "ms-dotnettools.blazorwasm-companion", + "ms-kubernetes-tools.vscode-aks-tools", + "ms-kubernetes-tools.aks-devx-tools", + "ms-kubernetes-tools.vscode-kubernetes-tools", + "ipedrazas.kubernetes-snippets", + "redhat.vscode-yaml", + "GitHub.vscode-github-actions", + "esbenp.prettier-vscode" + ] } -} + }, + "forwardPorts": [ + 5000 + ], + "postCreateCommand": "", + "remoteUser": "vscode", + "hostRequirements": { + "memory": "8gb" + } +} \ No newline at end of file diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml index 9dd323b2..b6b65e5b 100644 --- a/.github/workflows/azure-dev.yml +++ b/.github/workflows/azure-dev.yml @@ -54,7 +54,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Log in with Azure (Federated Credentials) if: ${{ env.AZURE_CLIENT_ID != '' }} diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index a1b91b15..e5a1badb 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore ./app/app.sln - name: Build diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4747391c..b63007d0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,19 @@ { "recommendations": [ - "ms-azuretools.azure-dev", - "ms-azuretools.vscode-bicep", - "ms-dotnettools.blazorwasm-companion", - "ms-dotnettools.csharp", - "ms-dotnettools.vscode-dotnet-runtime", + "ms-azuretools.vscode-azurefunctions", + "ms-dotnettools.csharp", + "ms-azuretools.vscode-docker", + "ms-azuretools.vscode-azurestorage", + "ms-azuretools.vscode-azurecontainerapps", + "ms-dotnettools.csdevkit", + "ms-dotnettools.vscode-dotnet-runtime", + "ms-dotnettools.blazorwasm-companion", + "ms-kubernetes-tools.vscode-aks-tools", + "ms-kubernetes-tools.aks-devx-tools", + "ms-kubernetes-tools.vscode-kubernetes-tools", + "ipedrazas.kubernetes-snippets", + "redhat.vscode-yaml", + "GitHub.vscode-github-actions", + "esbenp.prettier-vscode" ] -} \ No newline at end of file + } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index ef1c3628..b7d51530 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,50 +4,48 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch (current file)", - "script": "${file}", - "args": [], - "cwd": "${file}" + { + "name": "Frontend: Blazor client", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}//app/backend/bin/Debug/net8.0/ClientApp.dll", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" }, - { - "name": "Frontend: Blazor client", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/app/frontend/bin/Debug/net7.0/ClientApp.dll", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "envFile": "${input:dotEnvFilePath}" + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" }, - { - "name": "Backend: Minimal API", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/app/backend/bin/Debug/net7.0/MinimalApi.dll", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole", - "envFile": "${input:dotEnvFilePath}" - } + "envFile": "${input:dotEnvFilePath}" + }, + { + "name": "Backend: Minimal API", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/app/backend/bin/Debug/net8.0/MinimalApi.dll", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole", + "envFile": "${input:dotEnvFilePath}" + } ], "inputs": [ - { - "id": "dotEnvFilePath", - "type": "command", - "command": "azure-dev.commands.getDotEnvFilePath" - } + { + "id": "dotEnvFilePath", + "type": "command", + "command": "azure-dev.commands.getDotEnvFilePath" + } + ], + "compounds": [ + { + "name": "Full Stack", + "configurations": ["Backend: Minimal API", "Frontend: Blazor client"] + } ] -} \ No newline at end of file + } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a72fd64..e80ebc9e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,24 @@ { "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true }, "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true }, "[css]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true }, "search.exclude": { - "static": true + "static": true }, - "dotnet.defaultSolution": "app/app.sln" -} + "dotnet.defaultSolution": "app/app.sln", + "azureFunctions.deploySubpath": "app/functions/EmbedFunctions/bin/Release/net8.0/publish", + "azureFunctions.projectLanguage": "C#", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.projectSubpath": "app/functions/EmbedFunctions", + "azureFunctions.preDeployTask": "publish (functions)" + } \ No newline at end of file diff --git a/app/backend/Extensions/SearchClientExtensions.cs b/app/backend/Extensions/SearchClientExtensions.cs index cdb19188..7d6fe6ec 100644 --- a/app/backend/Extensions/SearchClientExtensions.cs +++ b/app/backend/Extensions/SearchClientExtensions.cs @@ -43,10 +43,10 @@ internal static async Task QueryDocumentsAsync( // if semantic ranker is enabled, we need to set the rank to a large number to get more // candidates for semantic reranking KNearestNeighborsCount = useSemanticRanker ? 50 : top, - Value = embedding, + Vector = embedding, }; vectorQuery.Fields.Add("embedding"); - searchOption.Vectors.Add(vectorQuery); + searchOption.VectorQueries.Add(vectorQuery); } var searchResultResponse = await searchClient.SearchAsync( From 8ce655e818291980ad12a81eca52ac429ff2da67 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 16 Oct 2023 08:07:52 -0500 Subject: [PATCH 4/7] CPM --- .gitignore | 1 - app/Directory.Packages.props | 47 +++++++++++++++++++ app/app.sln | 2 + app/backend/MinimalApi.csproj | 28 +++++------ app/frontend/ClientApp.csproj | 22 ++++----- .../EmbedFunctions/EmbedFunctions.csproj | 22 ++++----- app/nuget.config | 19 ++++++++ app/prepdocs/PrepareDocs/PrepareDocs.csproj | 16 +++---- .../ClientApp.Tests/ClientApp.Tests.csproj | 12 ++--- 9 files changed, 118 insertions(+), 51 deletions(-) create mode 100644 app/Directory.Packages.props create mode 100644 app/nuget.config diff --git a/.gitignore b/.gitignore index 08805241..7f9f6e57 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ dotnet/.config *.user *.userosscache *.sln.docstates -nuget.config # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/app/Directory.Packages.props b/app/Directory.Packages.props new file mode 100644 index 00000000..167e5fbc --- /dev/null +++ b/app/Directory.Packages.props @@ -0,0 +1,47 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/app.sln b/app/app.sln index ee222295..05b1fd31 100644 --- a/app/app.sln +++ b/app/app.sln @@ -13,8 +13,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\.gitignore = ..\.gitignore ..\.github\workflows\azure-dev.yml = ..\.github\workflows\azure-dev.yml Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props ..\.github\workflows\dotnet-build.yml = ..\.github\workflows\dotnet-build.yml ..\LICENSE = ..\LICENSE + nuget.config = nuget.config ..\README.md = ..\README.md EndProjectSection EndProject diff --git a/app/backend/MinimalApi.csproj b/app/backend/MinimalApi.csproj index f37750ee..d25c40a8 100644 --- a/app/backend/MinimalApi.csproj +++ b/app/backend/MinimalApi.csproj @@ -12,20 +12,20 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/app/frontend/ClientApp.csproj b/app/frontend/ClientApp.csproj index d13bbd07..b87dd1df 100644 --- a/app/frontend/ClientApp.csproj +++ b/app/frontend/ClientApp.csproj @@ -11,17 +11,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/functions/EmbedFunctions/EmbedFunctions.csproj b/app/functions/EmbedFunctions/EmbedFunctions.csproj index 0d44656d..56ed8ab8 100644 --- a/app/functions/EmbedFunctions/EmbedFunctions.csproj +++ b/app/functions/EmbedFunctions/EmbedFunctions.csproj @@ -9,17 +9,17 @@ feba7d78-952f-423c-95ed-d6a4051dd15f - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/nuget.config b/app/nuget.config new file mode 100644 index 00000000..685cdd93 --- /dev/null +++ b/app/nuget.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/prepdocs/PrepareDocs/PrepareDocs.csproj b/app/prepdocs/PrepareDocs/PrepareDocs.csproj index b9d36e3e..ad7294fa 100644 --- a/app/prepdocs/PrepareDocs/PrepareDocs.csproj +++ b/app/prepdocs/PrepareDocs/PrepareDocs.csproj @@ -9,14 +9,14 @@ - - - - - - - - + + + + + + + + diff --git a/app/tests/ClientApp.Tests/ClientApp.Tests.csproj b/app/tests/ClientApp.Tests/ClientApp.Tests.csproj index 16673c5f..c2ea7353 100644 --- a/app/tests/ClientApp.Tests/ClientApp.Tests.csproj +++ b/app/tests/ClientApp.Tests/ClientApp.Tests.csproj @@ -9,15 +9,15 @@ - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From ae9b6b66cd5ee2807662fa4292b4a3709676d54c Mon Sep 17 00:00:00 2001 From: XiaoYun Zhang Date: Tue, 24 Oct 2023 15:35:47 -0700 Subject: [PATCH 5/7] fix docker to include directory.packages.prop --- app/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Dockerfile b/app/Dockerfile index c5b949b1..e934fdc3 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,13 +1,14 @@ #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["Directory.Build.props", "."] +COPY ["Directory.Packages.props", "."] COPY ["backend/", "backend/"] COPY ["frontend/", "frontend/"] COPY ["shared/", "shared/"] From db0553e3e2e4d4b1070516be6398089d370212ac Mon Sep 17 00:00:00 2001 From: XiaoYun Zhang Date: Tue, 24 Oct 2023 22:02:48 -0700 Subject: [PATCH 6/7] update port from 80 to 8080 --- app/Dockerfile | 2 +- app/prepdocs/PrepareDocs/Program.cs | 18 ++++++++++++------ infra/app/web.bicep | 2 +- infra/core/host/container-app-upsert.bicep | 2 +- infra/core/host/container-app.bicep | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/Dockerfile b/app/Dockerfile index e934fdc3..ef04bfe3 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -2,7 +2,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -EXPOSE 80 +EXPOSE 8080 EXPOSE 443 FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build diff --git a/app/prepdocs/PrepareDocs/Program.cs b/app/prepdocs/PrepareDocs/Program.cs index 463127f8..8d6f849c 100644 --- a/app/prepdocs/PrepareDocs/Program.cs +++ b/app/prepdocs/PrepareDocs/Program.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +using Azure.Search.Documents.Indexes.Models; + s_rootCommand.SetHandler( async (context) => { @@ -181,20 +183,24 @@ static async ValueTask CreateSearchIndexAsync(AppOptions options) } string vectorSearchConfigName = "my-vector-config"; - + string vectorSearchProfile = "my-vector-profile"; var index = new SearchIndex(options.SearchIndexName) { VectorSearch = new() { Algorithms = - { - new HnswVectorSearchAlgorithmConfiguration(vectorSearchConfigName) - } + { + new HnswVectorSearchAlgorithmConfiguration(vectorSearchConfigName) + }, + Profiles = + { + new VectorSearchProfile(vectorSearchProfile, vectorSearchConfigName) + } }, Fields = { new SimpleField("id", SearchFieldDataType.String) { IsKey = true }, - new SearchableField("content") { AnalyzerName = "en.microsoft" }, + new SearchableField("content") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft }, new SimpleField("category", SearchFieldDataType.String) { IsFacetable = true }, new SimpleField("sourcepage", SearchFieldDataType.String) { IsFacetable = true }, new SimpleField("sourcefile", SearchFieldDataType.String) { IsFacetable = true }, @@ -202,7 +208,7 @@ static async ValueTask CreateSearchIndexAsync(AppOptions options) { VectorSearchDimensions = 1536, IsSearchable = true, - SearchAnalyzerName = vectorSearchConfigName, // TODO: is this right? + VectorSearchProfile = vectorSearchProfile, } }, SemanticSettings = new SemanticSettings diff --git a/infra/app/web.bicep b/infra/app/web.bicep index fba3c664..58300dab 100644 --- a/infra/app/web.bicep +++ b/infra/app/web.bicep @@ -129,7 +129,7 @@ module app '../core/host/container-app-upsert.bicep' = { value: openAiEmbeddingDeployment } ] - targetPort: 80 + targetPort: 8080 } } diff --git a/infra/core/host/container-app-upsert.bicep b/infra/core/host/container-app-upsert.bicep index 72d4058d..c59357bc 100644 --- a/infra/core/host/container-app-upsert.bicep +++ b/infra/core/host/container-app-upsert.bicep @@ -64,7 +64,7 @@ param external bool = true param serviceBinds array = [] @description('The target port for the container') -param targetPort int = 80 +param targetPort int = 8080 resource existingApp 'Microsoft.App/containerApps@2023-04-01-preview' existing = if (exists) { name: name diff --git a/infra/core/host/container-app.bicep b/infra/core/host/container-app.bicep index 397b12b3..bc0e5ca5 100644 --- a/infra/core/host/container-app.bicep +++ b/infra/core/host/container-app.bicep @@ -68,7 +68,7 @@ param serviceBinds array = [] param serviceType string = '' @description('The target port for the container') -param targetPort int = 80 +param targetPort int = 8080 resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) { name: identityName From 471a1a3240e984546f0a68465d1e72c0a58fc81a Mon Sep 17 00:00:00 2001 From: XiaoYun Zhang Date: Tue, 24 Oct 2023 22:14:43 -0700 Subject: [PATCH 7/7] fix build error --- app/frontend/Services/ApiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/Services/ApiClient.cs b/app/frontend/Services/ApiClient.cs index 73fb6d9e..4622cfeb 100644 --- a/app/frontend/Services/ApiClient.cs +++ b/app/frontend/Services/ApiClient.cs @@ -18,7 +18,7 @@ public sealed class ApiClient(HttpClient httpClient) public async Task ShowLogoutButtonAsync() { - var response = await _httpClient.GetAsync("api/enableLogout"); + var response = await httpClient.GetAsync("api/enableLogout"); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync();