Skip to content

Commit 4c572c8

Browse files
PR Feedback
1 parent 27204c7 commit 4c572c8

13 files changed

Lines changed: 93 additions & 273 deletions

File tree

.github/workflows/Build-Test-And-Deploy.yml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,20 @@ jobs:
147147
emailsender-secret=keyvaultref:$KEYVAULTURI/secrets/authmessagesender-secretkey,identityref:$MANAGEDIDENTITYID emailsender-name=keyvaultref:$KEYVAULTURI/secrets/authmessagesender-sendfromname,identityref:$MANAGEDIDENTITYID \
148148
emailsender-email=keyvaultref:$KEYVAULTURI/secrets/authmessagesender-sendfromemail,identityref:$MANAGEDIDENTITYID connectionstring=keyvaultref:$KEYVAULTURI/secrets/connectionstrings-essentialcsharpwebcontextconnection,identityref:$MANAGEDIDENTITYID \
149149
captcha-sitekey=keyvaultref:$KEYVAULTURI/secrets/captcha-sitekey,identityref:$MANAGEDIDENTITYID captcha-secretkey=keyvaultref:$KEYVAULTURI/secrets/captcha-secretkey,identityref:$MANAGEDIDENTITYID \
150-
appinsights-connectionstring=keyvaultref:$KEYVAULTURI/secrets/applicationinsights-connectionstring,identityref:$MANAGEDIDENTITYID
150+
appinsights-connectionstring=keyvaultref:$KEYVAULTURI/secrets/applicationinsights-connectionstring,identityref:$MANAGEDIDENTITYID \
151+
ai-endpoint=keyvaultref:$KEYVAULTURI/secrets/AIOptions--Endpoint,identityref:$MANAGEDIDENTITYID ai-apikey=keyvaultref:$KEYVAULTURI/secrets/AIOptions--ApiKey,identityref:$MANAGEDIDENTITYID \
152+
ai-vectordeployment=keyvaultref:$KEYVAULTURI/secrets/AIOptions--VectorGenerationDeploymentName,identityref:$MANAGEDIDENTITYID ai-chatdeployment=keyvaultref:$KEYVAULTURI/secrets/AIOptions--ChatDeploymentName,identityref:$MANAGEDIDENTITYID \
153+
ai-systemprompt=keyvaultref:$KEYVAULTURI/secrets/AIOptions--SystemPrompt,identityref:$MANAGEDIDENTITYID \
154+
postgres-vectorstore-connectionstring=keyvaultref:$KEYVAULTURI/secrets/connectionstrings--PostgresVectorDb,identityref:$MANAGEDIDENTITYID
151155
az containerapp update --name $CONTAINER_APP_NAME --resource-group $RESOURCEGROUP --replace-env-vars Authentication__github__clientId=secretref:github-clientid Authentication__github__clientSecret=secretref:github-clientsecret \
152156
Authentication__microsoft__clientId=secretref:msft-clientid Authentication__microsoft__clientSecret=secretref:msft-clientsecret AuthMessageSender__ApiKey=secretref:emailsender-apikey AuthMessageSender__SecretKey=secretref:emailsender-secret \
153157
AuthMessageSender__SendFromName=secretref:emailsender-name AuthMessageSender__SendFromEmail=secretref:emailsender-email ConnectionStrings__EssentialCSharpWebContextConnection=secretref:connectionstring ASPNETCORE_ENVIRONMENT=Staging \
154-
AZURE_CLIENT_ID=$AZURECLIENTID HCaptcha__SiteKey=secretref:captcha-sitekey HCaptcha__SecretKey=secretref:captcha-secretkey ApplicationInsights__ConnectionString=secretref:appinsights-connectionstring
158+
AZURE_CLIENT_ID=$AZURECLIENTID HCaptcha__SiteKey=secretref:captcha-sitekey HCaptcha__SecretKey=secretref:captcha-secretkey ApplicationInsights__ConnectionString=secretref:appinsights-connectionstring \
159+
AIOptions__Endpoint=secretref:ai-endpoint AIOptions__ApiKey=secretref:ai-apikey AIOptions__VectorGenerationDeploymentName=secretref:ai-vectordeployment AIOptions__ChatDeploymentName=secretref:ai-chatdeployment \
160+
AIOptions__SystemPrompt=secretref:ai-systemprompt ConnectionStrings__PostgresVectorStore=secretref:postgres-vectorstore-connectionstring
155161
156162
- name: Logout of Azure CLI
157-
if: "always()"
163+
if: always()
158164
uses: azure/CLI@v2
159165
with:
160166
inlineScript: |
@@ -233,14 +239,19 @@ jobs:
233239
emailsender-secret=keyvaultref:$KEYVAULTURI/secrets/authmessagesender-secretkey,identityref:$MANAGEDIDENTITYID emailsender-name=keyvaultref:$KEYVAULTURI/secrets/authmessagesender-sendfromname,identityref:$MANAGEDIDENTITYID \
234240
emailsender-email=keyvaultref:$KEYVAULTURI/secrets/authmessagesender-sendfromemail,identityref:$MANAGEDIDENTITYID connectionstring=keyvaultref:$KEYVAULTURI/secrets/connectionstrings-essentialcsharpwebcontextconnection,identityref:$MANAGEDIDENTITYID \
235241
captcha-sitekey=keyvaultref:$KEYVAULTURI/secrets/captcha-sitekey,identityref:$MANAGEDIDENTITYID captcha-secretkey=keyvaultref:$KEYVAULTURI/secrets/captcha-secretkey,identityref:$MANAGEDIDENTITYID \
236-
appinsights-connectionstring=keyvaultref:$KEYVAULTURI/secrets/applicationinsights-connectionstring,identityref:$MANAGEDIDENTITYID
242+
appinsights-connectionstring=keyvaultref:$KEYVAULTURI/secrets/applicationinsights-connectionstring,identityref:$MANAGEDIDENTITYID \
243+
ai-endpoint=keyvaultref:$KEYVAULTURI/secrets/AIOptions--Endpoint,identityref:$MANAGEDIDENTITYID ai-apikey=keyvaultref:$KEYVAULTURI/secrets/AIOptions--ApiKey,identityref:$MANAGEDIDENTITYID \
244+
ai-vectordeployment=keyvaultref:$KEYVAULTURI/secrets/AIOptions--VectorGenerationDeploymentName,identityref:$MANAGEDIDENTITYID ai-chatdeployment=keyvaultref:$KEYVAULTURI/secrets/AIOptions--ChatDeploymentName,identityref:$MANAGEDIDENTITYID \
245+
ai-systemprompt=keyvaultref:$KEYVAULTURI/secrets/AIOptions--SystemPrompt,identityref:$MANAGEDIDENTITYID \
246+
postgres-vectorstore-connectionstring=keyvaultref:$KEYVAULTURI/secrets/connectionstrings--PostgresVectorDb,identityref:$MANAGEDIDENTITYID
237247
az containerapp update --name $CONTAINER_APP_NAME --resource-group $RESOURCEGROUP --replace-env-vars Authentication__github__clientId=secretref:github-clientid Authentication__github__clientSecret=secretref:github-clientsecret \
238248
Authentication__microsoft__clientId=secretref:msft-clientid Authentication__microsoft__clientSecret=secretref:msft-clientsecret AuthMessageSender__ApiKey=secretref:emailsender-apikey AuthMessageSender__SecretKey=secretref:emailsender-secret \
239249
AuthMessageSender__SendFromName=secretref:emailsender-name AuthMessageSender__SendFromEmail=secretref:emailsender-email ConnectionStrings__EssentialCSharpWebContextConnection=secretref:connectionstring ASPNETCORE_ENVIRONMENT=Production \
240-
AZURE_CLIENT_ID=$AZURECLIENTID HCaptcha__SiteKey=secretref:captcha-sitekey HCaptcha__SecretKey=secretref:captcha-secretkey ApplicationInsights__ConnectionString=secretref:appinsights-connectionstring
250+
AZURE_CLIENT_ID=$AZURECLIENTID HCaptcha__SiteKey=secretref:captcha-sitekey HCaptcha__SecretKey=secretref:captcha-secretkey ApplicationInsights__ConnectionString=secretref:appinsights-connectionstring \
251+
AIOptions__Endpoint=secretref:ai-endpoint AIOptions__ApiKey=secretref:ai-apikey ConnectionStrings__PostgresVectorStore=secretref:postgres-vectorstore-connectionstring
241252
242253
- name: Logout of Azure CLI
243-
if: "always()"
254+
if: always()
244255
uses: azure/CLI@v2
245256
with:
246257
inlineScript: |

EssentialCSharp.Chat.Shared/EssentialCSharp.Chat.Common.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
<PropertyGroup>
44
<TargetFramework>net9.0</TargetFramework>
5-
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
75
</PropertyGroup>
86

97
<ItemGroup>

EssentialCSharp.Chat.Shared/Extensions/ServiceCollectionExtensions.cs

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Azure.AI.OpenAI;
12
using EssentialCSharp.Chat.Common.Services;
23
using Microsoft.Extensions.Configuration;
34
using Microsoft.Extensions.DependencyInjection;
@@ -12,42 +13,40 @@ public static class ServiceCollectionExtensions
1213
/// </summary>
1314
/// <param name="services">The service collection to add services to</param>
1415
/// <param name="aiOptions">The AI configuration options</param>
16+
/// <param name="postgresConnectionString">The PostgreSQL connection string for the vector store</param>
1517
/// <returns>The service collection for chaining</returns>
16-
public static IServiceCollection AddAzureOpenAIServices(this IServiceCollection services, AIOptions aiOptions)
18+
public static IServiceCollection AddAzureOpenAIServices(this IServiceCollection services, AIOptions aiOptions, string postgresConnectionString)
1719
{
18-
// Validate required configuration
19-
if (aiOptions == null)
20-
{
21-
throw new InvalidOperationException("AIOptions cannot be null.");
22-
}
23-
2420
if (string.IsNullOrEmpty(aiOptions.Endpoint) ||
2521
string.IsNullOrEmpty(aiOptions.ApiKey))
26-
{
27-
throw new InvalidOperationException("Azure OpenAI Endpoint and ApiKey must be properly configured in AIOptions. Please update your configuration with valid values.");
28-
}
22+
// Register Azure OpenAI services
23+
#pragma warning disable SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
24+
services.AddAzureOpenAIEmbeddingGenerator(
25+
aiOptions.VectorGenerationDeploymentName,
26+
aiOptions.Endpoint,
27+
aiOptions.ApiKey);
2928

30-
if (string.IsNullOrEmpty(aiOptions.PostgresConnectionString) ||
31-
aiOptions.PostgresConnectionString.Contains("your-postgres-connection-string"))
32-
{
33-
throw new InvalidOperationException("PostgreSQL connection string must be properly configured in AIOptions for vector store. Please update your configuration with a valid connection string.");
34-
}
29+
services.AddAzureOpenAIChatClient(
30+
aiOptions.ChatDeploymentName,
31+
aiOptions.Endpoint,
32+
aiOptions.ApiKey);
3533

36-
#pragma warning disable SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates.
34+
services.AddSingleton(provider =>
35+
new AzureOpenAIClient(new Uri(aiOptions.Endpoint), new Azure.AzureKeyCredential(aiOptions.ApiKey)));
3736

3837
// Register Azure OpenAI services
3938
services.AddAzureOpenAIEmbeddingGenerator(
4039
aiOptions.VectorGenerationDeploymentName,
4140
aiOptions.Endpoint,
4241
aiOptions.ApiKey);
4342

44-
services.AddAzureOpenAIChatClient(
43+
services.AddAzureOpenAIChatCompletion(
4544
aiOptions.ChatDeploymentName,
4645
aiOptions.Endpoint,
4746
aiOptions.ApiKey);
4847

4948
// Add PostgreSQL vector store
50-
services.AddPostgresVectorStore(aiOptions.PostgresConnectionString);
49+
services.AddPostgresVectorStore(postgresConnectionString);
5150

5251
#pragma warning restore SKEXP0010
5352

@@ -72,9 +71,15 @@ public static IServiceCollection AddAzureOpenAIServices(this IServiceCollection
7271
services.Configure<AIOptions>(configuration.GetSection("AIOptions"));
7372

7473
var aiOptions = configuration.GetSection("AIOptions").Get<AIOptions>();
74+
if (aiOptions == null)
75+
{
76+
throw new InvalidOperationException("AIOptions section is missing from configuration.");
77+
}
78+
79+
// Get PostgreSQL connection string using the standard method
80+
var postgresConnectionString = configuration.GetConnectionString("PostgresVectorStore") ??
81+
throw new InvalidOperationException("Connection string 'PostgresVectorStore' not found.");
7582

76-
return aiOptions == null
77-
? throw new InvalidOperationException("AIOptions section is missing from configuration.")
78-
: services.AddAzureOpenAIServices(aiOptions);
83+
return services.AddAzureOpenAIServices(aiOptions, postgresConnectionString);
7984
}
8085
}

EssentialCSharp.Chat.Shared/Models/AIOptions.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,4 @@ public class AIOptions
2626
/// The API key for accessing Azure OpenAI services.
2727
/// </summary>
2828
public string ApiKey { get; set; } = string.Empty;
29-
30-
/// <summary>
31-
/// The PostgreSQL connection string for the vector store.
32-
/// </summary>
33-
public string PostgresConnectionString { get; set; } = string.Empty;
3429
}

EssentialCSharp.Chat.Shared/Services/AIChatService.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@ public class AIChatService
1616
private readonly OpenAIResponseClient _ResponseClient;
1717
private readonly AISearchService _SearchService;
1818

19-
public AIChatService(IOptions<AIOptions> options, AISearchService searchService)
19+
public AIChatService(IOptions<AIOptions> options, AISearchService searchService, AzureOpenAIClient azureClient)
2020
{
2121
_Options = options.Value;
2222
_SearchService = searchService;
2323

2424
// Initialize Azure OpenAI client and get the Response Client from it
25-
_AzureClient = new AzureOpenAIClient(
26-
new Uri(_Options.Endpoint),
27-
new System.ClientModel.ApiKeyCredential(_Options.ApiKey));
25+
_AzureClient = azureClient;
2826

2927
_ResponseClient = _AzureClient.GetOpenAIResponseClient(_Options.ChatDeploymentName);
3028
}
@@ -105,7 +103,7 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService)
105103
/// </summary>
106104
private async Task<string> EnrichPromptWithContext(string prompt, bool enableContextualSearch, CancellationToken cancellationToken)
107105
{
108-
if (!enableContextualSearch || _SearchService == null)
106+
if (!enableContextualSearch)
109107
{
110108
return prompt;
111109
}

EssentialCSharp.Chat.Shared/Services/ChunkingResultExtensions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace EssentialCSharp.Chat.Common.Services;
66

7-
public static class ChunkingResultExtensions
7+
public static partial class ChunkingResultExtensions
88
{
99
public static List<BookContentChunk> ToBookContentChunks(this FileChunkingResult result)
1010
{
@@ -41,8 +41,9 @@ private static int ExtractChapterNumber(string fileName)
4141
{
4242
// Example: "Chapter01.md" -> 1
4343
// Regex: Chapter(?<ChapterNumber>[0-9]{2})
44-
var match = System.Text.RegularExpressions.Regex.Match(fileName, @"Chapter(?<ChapterNumber>\d{2})");
44+
var match = ChapterNumberRegex().Match(fileName);
4545
if (match.Success && int.TryParse(match.Groups["ChapterNumber"].Value, out int chapterNumber))
46+
4647
{
4748
return chapterNumber;
4849
}
@@ -54,4 +55,7 @@ private static string ComputeSha256Hash(string text)
5455
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(text));
5556
return Convert.ToHexStringLower(bytes);
5657
}
58+
59+
[System.Text.RegularExpressions.GeneratedRegex(@"Chapter(?<ChapterNumber>\d{2})")]
60+
private static partial System.Text.RegularExpressions.Regex ChapterNumberRegex();
5761
}

EssentialCSharp.Chat.Shared/Services/EmbeddingService.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,18 @@ public async Task GenerateBookContentEmbeddingsAndUploadToVectorStore(IEnumerabl
4242
CancellationToken = cancellationToken
4343
};
4444

45+
int uploadedCount = 0;
46+
4547
await Parallel.ForEachAsync(bookContents, parallelOptions, async (chunk, cancellationToken) =>
4648
{
4749
// Generate the text embedding using the new method.
4850
chunk.TextEmbedding = await GenerateEmbeddingAsync(chunk.ChunkText, cancellationToken);
4951

5052
await collection.UpsertAsync(chunk, cancellationToken);
5153
Console.WriteLine($"Uploaded chunk '{chunk.Id}' to collection '{collectionName}' for file '{chunk.FileName}' with heading '{chunk.Heading}'.");
54+
55+
Interlocked.Increment(ref uploadedCount);
5256
});
53-
Console.WriteLine($"Successfully generated embeddings and uploaded {bookContents.Count()} chunks to collection '{collectionName}'.");
57+
Console.WriteLine($"Successfully generated embeddings and uploaded {uploadedCount} chunks to collection '{collectionName}'.");
5458
}
5559
}

EssentialCSharp.Chat.Shared/Services/FileChunkingResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ public class FileChunkingResult
99
public string FilePath { get; set; } = string.Empty;
1010
public int OriginalCharCount { get; set; }
1111
public int ChunkCount { get; set; }
12-
public List<string> Chunks { get; set; } = new();
12+
public List<string> Chunks { get; set; } = [];
1313
public int TotalChunkCharacters { get; set; }
1414
}

EssentialCSharp.Chat.Tests/EssentialCSharp.Chat.Tests.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
<PropertyGroup>
44
<TargetFramework>net9.0</TargetFramework>
5-
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
75
<IsPackable>false</IsPackable>
86
</PropertyGroup>
97

0 commit comments

Comments
 (0)