From a052e9f65c25d4a671a737d76245f4aecfa76a57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:41:43 +0000 Subject: [PATCH 1/8] Initial plan From 5bd6d4d79847278494a0b253e294345dc41fc39f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:49:33 +0000 Subject: [PATCH 2/8] Remove Azure.AI.OpenAI package references from test projects Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- ...ons.AI.Evaluation.Integration.Tests.csproj | 2 +- .../Setup.cs | 43 ++++++++++++++----- .../IntegrationTestHelpers.cs | 32 ++++++++++++-- ...icrosoft.Extensions.AI.OpenAI.Tests.csproj | 2 +- .../aichatweb.Web/aichatweb.Web.csproj | 1 - .../aichatweb.Web/aichatweb.Web.csproj | 1 - 6 files changed, 63 insertions(+), 18 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj index 6e3332ebca6..579debd599f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs index 388ba1f1415..d4a244e5cee 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs @@ -3,8 +3,9 @@ using System; using System.ClientModel; -using Azure.AI.OpenAI; +using Azure.Core; using Azure.Identity; +using OpenAI; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; @@ -15,22 +16,42 @@ internal static class Setup internal static ChatConfiguration CreateChatConfiguration() { - AzureOpenAIClient azureOpenAIClient = GetAzureOpenAIClient(); - IChatClient chatClient = azureOpenAIClient.GetChatClient(Settings.Current.DeploymentName).AsIChatClient(); + OpenAI.Chat.ChatClient openAIClient = GetOpenAIClient(); + IChatClient chatClient = openAIClient.AsIChatClient(); return new ChatConfiguration(chatClient); } - private static AzureOpenAIClient GetAzureOpenAIClient() + private static OpenAI.Chat.ChatClient GetOpenAIClient() { - var endpoint = new Uri(Settings.Current.Endpoint); - AzureOpenAIClientOptions options = new(); + // Use Azure endpoint with /openai/v1 suffix + var endpoint = new Uri(new Uri(Settings.Current.Endpoint), "/openai/v1"); var credential = new ChainedTokenCredential(new AzureCliCredential(), new DefaultAzureCredential()); - AzureOpenAIClient azureOpenAIClient = - OfflineOnly - ? new AzureOpenAIClient(endpoint, new ApiKeyCredential("Bogus"), options) - : new AzureOpenAIClient(endpoint, credential, options); + OpenAIClient client = OfflineOnly + ? new OpenAIClient(endpoint, new ApiKeyCredential("Bogus")) + : new OpenAIClient(endpoint, new AzureTokenCredentialWrapper(credential)); - return azureOpenAIClient; + return client.GetChatClient(Settings.Current.DeploymentName); + } + + /// Wraps an Azure TokenCredential for use with OpenAI client. + private sealed class AzureTokenCredentialWrapper : ApiKeyCredential + { + private readonly TokenCredential _tokenCredential; + + public AzureTokenCredentialWrapper(TokenCredential tokenCredential) + : base("placeholder") + { + _tokenCredential = tokenCredential; + } + + public override void Deconstruct(out string key) + { + // Get Azure token and use it as the API key + var token = _tokenCredential.GetToken( + new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), + default); + key = token.Token; + } } } diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs index 5c98f814868..903c6e178b6 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs @@ -3,7 +3,7 @@ using System; using System.ClientModel; -using Azure.AI.OpenAI; +using Azure.Core; using Azure.Identity; using OpenAI; @@ -25,13 +25,18 @@ internal static class IntegrationTestHelpers var endpoint = configuration["OpenAI:Endpoint"] ?? throw new InvalidOperationException("To use AzureOpenAI, set a value for OpenAI:Endpoint"); + // Use Azure endpoint with /openai/v1 suffix + var azureEndpointUri = new Uri(new Uri(endpoint), "/openai/v1"); + if (apiKey is not null) { - return new AzureOpenAIClient(new Uri(endpoint), new ApiKeyCredential(apiKey)); + return new OpenAIClient(azureEndpointUri, new ApiKeyCredential(apiKey)); } else { - return new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); + // Use Azure Identity authentication + var tokenCredential = new DefaultAzureCredential(); + return new OpenAIClient(azureEndpointUri, new AzureTokenCredentialWrapper(tokenCredential)); } } else if (apiKey is not null) @@ -41,4 +46,25 @@ internal static class IntegrationTestHelpers return null; } + + /// Wraps an Azure TokenCredential for use with OpenAI client. + private sealed class AzureTokenCredentialWrapper : ApiKeyCredential + { + private readonly TokenCredential _tokenCredential; + + public AzureTokenCredentialWrapper(TokenCredential tokenCredential) + : base("placeholder") + { + _tokenCredential = tokenCredential; + } + + public override void Deconstruct(out string key) + { + // Get Azure token and use it as the API key + var token = _tokenCredential.GetToken( + new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), + default); + key = token.Token; + } + } } diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/Microsoft.Extensions.AI.OpenAI.Tests.csproj b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/Microsoft.Extensions.AI.OpenAI.Tests.csproj index 536c250cb47..093260d779c 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/Microsoft.Extensions.AI.OpenAI.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/Microsoft.Extensions.AI.OpenAI.Tests.csproj @@ -32,7 +32,7 @@ - + diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj index 56e1e1ec23a..619003eb225 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj @@ -8,7 +8,6 @@ - diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj index ed4f3c914c8..16f0b1a2e8f 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/aichatweb.Web.csproj @@ -8,7 +8,6 @@ - From 07694e8b89d82a89f8cfde829011591bb0a07ad6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:58:07 +0000 Subject: [PATCH 3/8] Fix Azure authentication to use OpenAI client with token credentials Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Setup.cs | 31 +++++++----------- .../IntegrationTestHelpers.cs | 32 +++++-------------- 2 files changed, 19 insertions(+), 44 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs index d4a244e5cee..3221de32067 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs @@ -27,31 +27,22 @@ private static OpenAI.Chat.ChatClient GetOpenAIClient() var endpoint = new Uri(new Uri(Settings.Current.Endpoint), "/openai/v1"); var credential = new ChainedTokenCredential(new AzureCliCredential(), new DefaultAzureCredential()); - OpenAIClient client = OfflineOnly - ? new OpenAIClient(endpoint, new ApiKeyCredential("Bogus")) - : new OpenAIClient(endpoint, new AzureTokenCredentialWrapper(credential)); - - return client.GetChatClient(Settings.Current.DeploymentName); - } - - /// Wraps an Azure TokenCredential for use with OpenAI client. - private sealed class AzureTokenCredentialWrapper : ApiKeyCredential - { - private readonly TokenCredential _tokenCredential; - - public AzureTokenCredentialWrapper(TokenCredential tokenCredential) - : base("placeholder") + OpenAIClient client; + if (OfflineOnly) { - _tokenCredential = tokenCredential; + var options = new OpenAIClientOptions { Endpoint = endpoint }; + client = new OpenAIClient(new ApiKeyCredential("Bogus"), options); } - - public override void Deconstruct(out string key) + else { - // Get Azure token and use it as the API key - var token = _tokenCredential.GetToken( + // Get Azure token and use as API key + var token = credential.GetToken( new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), default); - key = token.Token; + var options = new OpenAIClientOptions { Endpoint = endpoint }; + client = new OpenAIClient(new ApiKeyCredential(token.Token), options); } + + return client.GetChatClient(Settings.Current.DeploymentName); } } diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs index 903c6e178b6..9d72def30e3 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs @@ -30,13 +30,18 @@ internal static class IntegrationTestHelpers if (apiKey is not null) { - return new OpenAIClient(azureEndpointUri, new ApiKeyCredential(apiKey)); + var options = new OpenAIClientOptions { Endpoint = azureEndpointUri }; + return new OpenAIClient(new ApiKeyCredential(apiKey), options); } else { - // Use Azure Identity authentication + // Use Azure Identity authentication - get token and use as API key var tokenCredential = new DefaultAzureCredential(); - return new OpenAIClient(azureEndpointUri, new AzureTokenCredentialWrapper(tokenCredential)); + var token = tokenCredential.GetToken( + new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), + default); + var options = new OpenAIClientOptions { Endpoint = azureEndpointUri }; + return new OpenAIClient(new ApiKeyCredential(token.Token), options); } } else if (apiKey is not null) @@ -46,25 +51,4 @@ internal static class IntegrationTestHelpers return null; } - - /// Wraps an Azure TokenCredential for use with OpenAI client. - private sealed class AzureTokenCredentialWrapper : ApiKeyCredential - { - private readonly TokenCredential _tokenCredential; - - public AzureTokenCredentialWrapper(TokenCredential tokenCredential) - : base("placeholder") - { - _tokenCredential = tokenCredential; - } - - public override void Deconstruct(out string key) - { - // Get Azure token and use it as the API key - var token = _tokenCredential.GetToken( - new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), - default); - key = token.Token; - } - } } From 954afe4d744ecde3a983ecc073aeac263689c094 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 04:01:58 +0000 Subject: [PATCH 4/8] Update template source to use OpenAI client for Azure OpenAI Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../ChatWithCustomData-CSharp.Web/Program.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs index f8d2826ab9a..e2fa6649163 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs @@ -10,12 +10,13 @@ #endif #if (IsOllama) using OllamaSharp; -#elif (IsOpenAI || IsGHModels) -using OpenAI; -using System.ClientModel; #else -using Azure.AI.OpenAI; +using OpenAI; using System.ClientModel; +#if (UseManagedIdentity && IsAzureOpenAI) +using Azure.Core; +using Azure.Identity; +#endif #endif var builder = WebApplication.CreateBuilder(args); @@ -61,12 +62,15 @@ #if (!UseManagedIdentity) // dotnet user-secrets set AzureOpenAI:Key YOUR-API-KEY #endif -var azureOpenAi = new AzureOpenAIClient( - new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Endpoint. See the README for details.")), +var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Endpoint. See the README for details.")), "/openai/v1"); #if (UseManagedIdentity) - new DefaultAzureCredential()); +var tokenCredential = new DefaultAzureCredential(); +var token = tokenCredential.GetToken(new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), default); +var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; +var azureOpenAi = new OpenAIClient(new ApiKeyCredential(token.Token), openAIOptions); #else - new ApiKeyCredential(builder.Configuration["AzureOpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Key. See the README for details."))); +var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; +var azureOpenAi = new OpenAIClient(new ApiKeyCredential(builder.Configuration["AzureOpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Key. See the README for details.")), openAIOptions); #endif #pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var chatClient = azureOpenAi.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); From b743590825de999a1b02635fade8276a1ec7a3c1 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 1 Oct 2025 00:43:31 -0700 Subject: [PATCH 5/8] Remove remaining package references to Azure.AI.OpenAI --- eng/packages/TestOnly.props | 1 - src/ProjectTemplates/GeneratedContent.targets | 2 -- .../ChatWithCustomData-CSharp.Web.csproj.in | 3 --- 3 files changed, 6 deletions(-) diff --git a/eng/packages/TestOnly.props b/eng/packages/TestOnly.props index d8748a4ae9c..5875491f919 100644 --- a/eng/packages/TestOnly.props +++ b/eng/packages/TestOnly.props @@ -2,7 +2,6 @@ - diff --git a/src/ProjectTemplates/GeneratedContent.targets b/src/ProjectTemplates/GeneratedContent.targets index 2d30c4faf5f..c7b3af3023b 100644 --- a/src/ProjectTemplates/GeneratedContent.targets +++ b/src/ProjectTemplates/GeneratedContent.targets @@ -35,7 +35,6 @@ 9.5.0 9.5.0-preview.1.25474.7 - 2.3.0-beta.2 1.0.0-beta.9 1.14.0 11.6.1 @@ -62,7 +61,6 @@ TemplatePackageVersion_Aspire=$(TemplatePackageVersion_Aspire); TemplatePackageVersion_Aspire_Preview=$(TemplatePackageVersion_Aspire_Preview); - TemplatePackageVersion_AzureAIOpenAI=$(TemplatePackageVersion_AzureAIOpenAI); TemplatePackageVersion_AzureAIProjects=$(TemplatePackageVersion_AzureAIProjects); TemplatePackageVersion_AzureIdentity=$(TemplatePackageVersion_AzureIdentity); TemplatePackageVersion_AzureSearchDocuments=$(TemplatePackageVersion_AzureSearchDocuments); diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in index 0e753e8c907..52fbf1b8044 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in @@ -16,17 +16,14 @@ #elif ((IsGHModels || IsOpenAI) && !IsAspire) #elif (IsAzureAiFoundry) - #endif --> - - From 1fc85ebaccda78b14455705ea2b5889d9d51eb66 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 1 Oct 2025 04:44:03 -0700 Subject: [PATCH 6/8] Use computed symbols for all bool checks in the template. Sort usings in Program.cs. --- .../.template.config/template.json | 16 +++++---- .../AppHost.cs | 14 ++++---- ...hatWithCustomData-CSharp.AppHost.csproj.in | 4 +-- .../ChatWithCustomData-CSharp.Web.csproj.in | 14 ++++---- .../Program.Aspire.cs | 16 ++++----- .../ChatWithCustomData-CSharp.Web/Program.cs | 36 +++++++++---------- .../ChatWithCustomData-CSharp.Web/README.md | 8 ++--- .../Services/IngestedChunk.cs | 4 +-- .../Services/IngestedDocument.cs | 4 +-- .../Services/Ingestion/DataIngestor.cs | 2 +- .../Services/Ingestion/PDFDirectorySource.cs | 4 +-- .../Services/SemanticSearch.cs | 2 +- .../src/ChatWithCustomData/README.Aspire.md | 6 ++-- .../aichatweb/README.md | 3 ++ .../aichatweb/Program.cs | 6 ++-- .../aichatweb/aichatweb.Web/Program.cs | 2 +- .../aichatweb/Program.cs | 10 +++--- .../aichatweb/README.md | 3 ++ 18 files changed, 80 insertions(+), 74 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json index cffbaa7598f..603ed1a2735 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json @@ -88,7 +88,7 @@ ] }, { - "condition": "(!UseLocalVectorStore)", + "condition": "(!IsLocalVectorStore)", "exclude": [ "ChatWithCustomData-CSharp.Web/Services/JsonVectorStore.cs" ] @@ -185,6 +185,10 @@ "defaultValue": "false", "description": "Create the project as a distributed application using Aspire." }, + "IsManagedIdentity": { + "type": "computed", + "value": "(UseManagedIdentity)" + }, "IsAspire": { "type": "computed", "value": "(UseAspire || VectorStore == \"qdrant\")" @@ -209,21 +213,21 @@ "type": "computed", "value": "(AiServiceProvider == \"azureaifoundry\")" }, - "UseAzureAISearch": { + "IsAzureAISearch": { "type": "computed", "value": "(VectorStore == \"azureaisearch\")" }, - "UseLocalVectorStore": { + "IsLocalVectorStore": { "type": "computed", "value": "(VectorStore == \"local\")" }, - "UseQdrant": { + "IsQdrant": { "type": "computed", "value": "(VectorStore == \"qdrant\")" }, - "UseAzure": { + "IsAzure": { "type": "computed", - "value": "(IsAzureOpenAI || IsAzureAiFoundry || UseAzureAISearch)" + "value": "(IsAzureOpenAI || IsAzureAIFoundry || IsAzureAISearch)" }, "ChatModel": { "type": "parameter", diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/AppHost.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/AppHost.cs index df2f6765d9e..a859ce397a1 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/AppHost.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/AppHost.cs @@ -29,7 +29,7 @@ modelName: "text-embedding-3-small", modelVersion: "1"); #endif -#if (UseAzureAISearch) +#if (IsAzureAISearch) // See https://learn.microsoft.com/dotnet/aspire/azure/local-provisioning#configuration // for instructions providing configuration values @@ -42,13 +42,13 @@ var chat = ollama.AddModel("chat", "llama3.2"); var embeddings = ollama.AddModel("embeddings", "all-minilm"); #endif -#if (UseAzureAISearch) // VECTOR DATABASE CONFIGURATION -#elif (UseQdrant) +#if (IsAzureAISearch) // VECTOR DATABASE CONFIGURATION +#elif (IsQdrant) var vectorDB = builder.AddQdrant("vectordb") .WithDataVolume() .WithLifetime(ContainerLifetime.Persistent); -#else // UseLocalVectorStore +#else // IsLocalVectorStore #endif var webApp = builder.AddProject("aichatweb-app"); @@ -65,15 +65,15 @@ .WithReference(openai) .WaitFor(openai); #endif -#if (UseAzureAISearch) // VECTOR DATABASE REFERENCES +#if (IsAzureAISearch) // VECTOR DATABASE REFERENCES webApp .WithReference(search) .WaitFor(search); -#elif (UseQdrant) +#elif (IsQdrant) webApp .WithReference(vectorDB) .WaitFor(vectorDB); -#else // UseLocalVectorStore +#else // IsLocalVectorStore #endif builder.Build().Run(); diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/ChatWithCustomData-CSharp.AppHost.csproj.in b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/ChatWithCustomData-CSharp.AppHost.csproj.in index 8f5b8baabc0..b7d8f13bdfa 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/ChatWithCustomData-CSharp.AppHost.csproj.in +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/ChatWithCustomData-CSharp.AppHost.csproj.in @@ -12,9 +12,9 @@ - diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in index 52fbf1b8044..05e9a9726de 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/ChatWithCustomData-CSharp.Web.csproj.in @@ -15,7 +15,7 @@ #elif ((IsGHModels || IsOpenAI) && !IsAspire) -#elif (IsAzureAiFoundry) +#elif (IsAzureAIFoundry) #endif --> @@ -26,24 +26,24 @@ - + - - +#elif (IsQdrant)--> - + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.Aspire.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.Aspire.cs index 243aa46c8d7..e7137ac6dd3 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.Aspire.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.Aspire.cs @@ -1,12 +1,10 @@ using Microsoft.Extensions.AI; +#if (IsOpenAI || IsGHModels) +using OpenAI; +#endif using ChatWithCustomData_CSharp.Web.Components; using ChatWithCustomData_CSharp.Web.Services; using ChatWithCustomData_CSharp.Web.Services.Ingestion; -#if (IsOllama) -#elif (IsOpenAI || IsGHModels) -using OpenAI; -#else // IsAzureOpenAI -#endif var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); @@ -20,7 +18,7 @@ c.EnableSensitiveData = builder.Environment.IsDevelopment()); builder.AddOllamaApiClient("embeddings") .AddEmbeddingGenerator(); -#elif (IsAzureAiFoundry) +#elif (IsAzureAIFoundry) #else // (IsOpenAI || IsAzureOpenAI || IsGHModels) #if (IsOpenAI) var openai = builder.AddOpenAIClient("openai"); @@ -34,15 +32,15 @@ openai.AddEmbeddingGenerator("text-embedding-3-small"); #endif -#if (UseAzureAISearch) +#if (IsAzureAISearch) builder.AddAzureSearchClient("search"); builder.Services.AddAzureAISearchCollection("data-ChatWithCustomData-CSharp.Web-chunks"); builder.Services.AddAzureAISearchCollection("data-ChatWithCustomData-CSharp.Web-documents"); -#elif (UseQdrant) +#elif (IsQdrant) builder.AddQdrantClient("vectordb"); builder.Services.AddQdrantCollection("data-ChatWithCustomData-CSharp.Web-chunks"); builder.Services.AddQdrantCollection("data-ChatWithCustomData-CSharp.Web-documents"); -#else // UseLocalVectorStore +#else // IsLocalVectorStore var vectorStorePath = Path.Combine(AppContext.BaseDirectory, "vector-store.db"); var vectorStoreConnectionString = $"Data Source={vectorStorePath}"; builder.Services.AddSqliteCollection("data-ChatWithCustomData-CSharp.Web-chunks", vectorStoreConnectionString); diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs index e2fa6649163..d15fb0b0b4b 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs @@ -1,23 +1,21 @@ -using Microsoft.Extensions.AI; -using ChatWithCustomData_CSharp.Web.Components; -using ChatWithCustomData_CSharp.Web.Services; -using ChatWithCustomData_CSharp.Web.Services.Ingestion; -#if(IsAzureOpenAI || UseAzureAISearch) +#if (!IsOllama) +using System.ClientModel; +#endif +#if (IsAzureAISearch && !IsManagedIdentity) using Azure; -#if (UseManagedIdentity) +#elif (IsManagedIdentity) +using Azure.Core; using Azure.Identity; #endif -#endif +using Microsoft.Extensions.AI; #if (IsOllama) using OllamaSharp; #else using OpenAI; -using System.ClientModel; -#if (UseManagedIdentity && IsAzureOpenAI) -using Azure.Core; -using Azure.Identity; -#endif #endif +using ChatWithCustomData_CSharp.Web.Components; +using ChatWithCustomData_CSharp.Web.Services; +using ChatWithCustomData_CSharp.Web.Services.Ingestion; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); @@ -52,18 +50,18 @@ var chatClient = openAIClient.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); #pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var embeddingGenerator = openAIClient.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator(); -#elif (IsAzureAiFoundry) +#elif (IsAzureAIFoundry) #else // IsAzureOpenAI // You will need to set the endpoint and key to your own values // You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: // cd this-project-directory // dotnet user-secrets set AzureOpenAI:Endpoint https://YOUR-DEPLOYMENT-NAME.openai.azure.com -#if (!UseManagedIdentity) +#if (!IsManagedIdentity) // dotnet user-secrets set AzureOpenAI:Key YOUR-API-KEY #endif var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Endpoint. See the README for details.")), "/openai/v1"); -#if (UseManagedIdentity) +#if (IsManagedIdentity) var tokenCredential = new DefaultAzureCredential(); var token = tokenCredential.GetToken(new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), default); var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; @@ -78,17 +76,17 @@ var embeddingGenerator = azureOpenAi.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator(); #endif -#if (UseAzureAISearch) +#if (IsAzureAISearch) // You will need to set the endpoint and key to your own values // You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: // cd this-project-directory // dotnet user-secrets set AzureAISearch:Endpoint https://YOUR-DEPLOYMENT-NAME.search.windows.net -#if (!UseManagedIdentity) +#if (!IsManagedIdentity) // dotnet user-secrets set AzureAISearch:Key YOUR-API-KEY #endif var azureAISearchEndpoint = new Uri(builder.Configuration["AzureAISearch:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureAISearch:Endpoint. See the README for details.")); -#if (UseManagedIdentity) +#if (IsManagedIdentity) var azureAISearchCredential = new DefaultAzureCredential(); #else var azureAISearchCredential = new AzureKeyCredential(builder.Configuration["AzureAISearch:Key"] @@ -96,7 +94,7 @@ #endif builder.Services.AddAzureAISearchCollection("data-ChatWithCustomData-CSharp.Web-chunks", azureAISearchEndpoint, azureAISearchCredential); builder.Services.AddAzureAISearchCollection("data-ChatWithCustomData-CSharp.Web-documents", azureAISearchEndpoint, azureAISearchCredential); -#else // UseLocalVectorStore +#else // IsLocalVectorStore var vectorStorePath = Path.Combine(AppContext.BaseDirectory, "vector-store.db"); var vectorStoreConnectionString = $"Data Source={vectorStorePath}"; builder.Services.AddSqliteCollection("data-ChatWithCustomData-CSharp.Web-chunks", vectorStoreConnectionString); diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/README.md b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/README.md index a828e164ff8..88dff74d315 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/README.md +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/README.md @@ -5,7 +5,7 @@ This project is an AI chat application that demonstrates how to chat with custom >[!NOTE] > Before running this project you need to configure the API keys or endpoints for the providers you have chosen. See below for details specific to your choices. -#### ---#if (UseAzure) +#### ---#if (IsAzure) ### Prerequisites To use Azure OpenAI or Azure AI Search, you need an Azure account. If you don't already have one, [create an Azure account](https://azure.microsoft.com/free/). @@ -104,7 +104,7 @@ To use Azure OpenAI, you will need an Azure account and an Azure OpenAI Service ### 2. Deploy the Models Deploy the `gpt-4o-mini` and `text-embedding-3-small` models to your Azure OpenAI Service resource. When creating those deployments, give them the same names as the models (`gpt-4o-mini` and `text-embedding-3-small`). See the Azure OpenAI documentation to learn how to [Deploy a model](https://learn.microsoft.com/azure/ai-services/openai/how-to/create-resource?pivots=web-portal#deploy-a-model). -#### ---#if (UseManagedIdentity) +#### ---#if (IsManagedIdentity) ### 3. Configure Azure OpenAI for Keyless Authentication This template is configured to use keyless authentication (also known as Managed Identity, with Entra ID). In the Azure Portal, when viewing the Azure OpenAI resource you just created, view access control settings and assign yourself the `Azure AI Developer` role. [Learn more about configuring authentication for local development](https://learn.microsoft.com/azure/developer/ai/keyless-connections?tabs=csharp%2Cazure-cli#authenticate-for-local-development). @@ -160,7 +160,7 @@ Make sure to replace `YOUR-AZURE-OPENAI-KEY` and `YOUR-AZURE-OPENAI-ENDPOINT` wi #### ---#endif #### ---#endif -#### ---#if (UseAzureAISearch) +#### ---#if (IsAzureAISearch) ## Configure Azure AI Search To use Azure AI Search, you will need an Azure account and an Azure AI Search resource. For detailed instructions, see the [Azure AI Search documentation](https://learn.microsoft.com/azure/search/search-create-service-portal). @@ -170,7 +170,7 @@ Follow the instructions in the [Azure portal](https://portal.azure.com/) to crea Note that if you previously used the same Azure AI Search resource with different model using this project name, you may need to delete your `data-ChatWithCustomData-CSharp.Web-chunks` and `data-ChatWithCustomData-CSharp.Web-documents` indexes using the [Azure portal](https://portal.azure.com/) first before continuing; otherwise, data ingestion may fail due to a vector dimension mismatch. -#### ---#if (UseManagedIdentity) +#### ---#if (IsManagedIdentity) ### 2. Configure Azure AI Search for Keyless Authentication This template is configured to use keyless authentication (also known as Managed Identity, with Entra ID). Before continuing, you'll need to configure your Azure AI Search resource to support this. [Learn more](https://learn.microsoft.com/azure/search/keyless-connections). After creation, ensure that you have selected Role-Based Access Control (RBAC) under Settings > Keys, as this is not the default. Assign yourself the roles called out for local development. [Learn more](https://learn.microsoft.com/azure/search/keyless-connections#roles-for-local-development). diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedChunk.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedChunk.cs index c1369e1bf65..deff2580f52 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedChunk.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedChunk.cs @@ -9,14 +9,14 @@ public class IngestedChunk #else private const int VectorDimensions = 1536; // 1536 is the default vector size for the OpenAI text-embedding-3-small model #endif -#if (UseAzureAISearch || UseQdrant) +#if (IsAzureAISearch || IsQdrant) private const string VectorDistanceFunction = DistanceFunction.CosineSimilarity; #else private const string VectorDistanceFunction = DistanceFunction.CosineDistance; #endif [VectorStoreKey] -#if (UseQdrant) +#if (IsQdrant) public required Guid Key { get; set; } #else public required string Key { get; set; } diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedDocument.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedDocument.cs index 339a7479217..27ea85df7b8 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedDocument.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/IngestedDocument.cs @@ -5,14 +5,14 @@ namespace ChatWithCustomData_CSharp.Web.Services; public class IngestedDocument { private const int VectorDimensions = 2; -#if (UseAzureAISearch || UseQdrant) +#if (IsAzureAISearch || IsQdrant) private const string VectorDistanceFunction = DistanceFunction.CosineSimilarity; #else private const string VectorDistanceFunction = DistanceFunction.CosineDistance; #endif [VectorStoreKey] -#if (UseQdrant) +#if (IsQdrant) public required Guid Key { get; set; } #else public required string Key { get; set; } diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/DataIngestor.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/DataIngestor.cs index 175cc505670..5440772df42 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/DataIngestor.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/DataIngestor.cs @@ -5,7 +5,7 @@ namespace ChatWithCustomData_CSharp.Web.Services.Ingestion; public class DataIngestor( ILogger logger, -#if (UseQdrant) +#if (IsQdrant) VectorStoreCollection chunksCollection, VectorStoreCollection documentsCollection) #else diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/PDFDirectorySource.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/PDFDirectorySource.cs index fdbe058556e..0ea678d888b 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/PDFDirectorySource.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/Ingestion/PDFDirectorySource.cs @@ -26,7 +26,7 @@ public Task> GetNewOrModifiedDocumentsAsync(IReadO var existingDocumentVersion = existingDocumentsById.TryGetValue(sourceFileId, out var existingDocument) ? existingDocument.DocumentVersion : null; if (existingDocumentVersion != sourceFileVersion) { -#if (UseQdrant) +#if (IsQdrant) results.Add(new() { Key = Guid.CreateVersion7(), SourceId = SourceId, DocumentId = sourceFileId, DocumentVersion = sourceFileVersion }); #else results.Add(new() { Key = Guid.CreateVersion7().ToString(), SourceId = SourceId, DocumentId = sourceFileId, DocumentVersion = sourceFileVersion }); @@ -52,7 +52,7 @@ public Task> CreateChunksForDocumentAsync(IngestedDoc return Task.FromResult(paragraphs.Select(p => new IngestedChunk { -#if (UseQdrant) +#if (IsQdrant) Key = Guid.CreateVersion7(), #else Key = Guid.CreateVersion7().ToString(), diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/SemanticSearch.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/SemanticSearch.cs index 44cfcc18fc4..42abf3151fc 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/SemanticSearch.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Services/SemanticSearch.cs @@ -3,7 +3,7 @@ namespace ChatWithCustomData_CSharp.Web.Services; public class SemanticSearch( -#if (UseQdrant) +#if (IsQdrant) VectorStoreCollection vectorCollection) #else VectorStoreCollection vectorCollection) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/README.Aspire.md b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/README.Aspire.md index 14aa513acae..f30fcbfcf2e 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/README.Aspire.md +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/README.Aspire.md @@ -5,7 +5,7 @@ This project is an AI chat application that demonstrates how to chat with custom >[!NOTE] > Before running this project you need to configure the API keys or endpoints for the providers you have chosen. See below for details specific to your choices. -#### ---#if (UseAzure) +#### ---#if (IsAzure) ### Prerequisites To use Azure OpenAI or Azure AI Search, you need an Azure account. If you don't already have one, [create an Azure account](https://azure.microsoft.com/free/). @@ -81,13 +81,13 @@ Download, install, and run Docker Desktop from the [official website](https://ww Note: Ollama and Docker are excellent open source products, but are not maintained by Microsoft. #### ---#endif -#### ---#if (IsAzureOpenAI || UseAzureAISearch) +#### ---#if (IsAzureOpenAI || IsAzureAISearch) ## Using Azure Provisioning The project is set up to automatically provision Azure resources. When running the app for the first time, you will be prompted to provide Azure configuration values. For detailed instructions, see the [Local Provisioning documentation](https://learn.microsoft.com/dotnet/aspire/azure/local-provisioning#configuration). #### ---#endif -#### ---#if (UseQdrant) +#### ---#if (IsQdrant) ## Setting up a local environment for Qdrant This project is configured to run Qdrant in a Docker container. Docker Desktop must be installed and running for the project to run successfully. A Qdrant container will automatically start when running the application. diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/README.md b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/README.md index a2f61924c32..7bebc5d7595 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/README.md +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/README.md @@ -5,6 +5,9 @@ This project is an AI chat application that demonstrates how to chat with custom >[!NOTE] > Before running this project you need to configure the API keys or endpoints for the providers you have chosen. See below for details specific to your choices. +### Prerequisites +To use Azure OpenAI or Azure AI Search, you need an Azure account. If you don't already have one, [create an Azure account](https://azure.microsoft.com/free/). + ### Known Issues #### Errors running Ollama or Docker diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Program.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Program.cs index 0e97b8efffe..1ff3845eb08 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Program.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Program.cs @@ -1,9 +1,9 @@ -using Microsoft.Extensions.AI; +using System.ClientModel; +using Microsoft.Extensions.AI; +using OpenAI; using aichatweb.Components; using aichatweb.Services; using aichatweb.Services.Ingestion; -using OpenAI; -using System.ClientModel; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Program.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Program.cs index a98905903b3..6d23308d93a 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Program.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Program.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.AI; +using OpenAI; using aichatweb.Web.Components; using aichatweb.Web.Services; using aichatweb.Web.Services.Ingestion; -using OpenAI; var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Program.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Program.cs index d469d9c43db..17fe3c62b18 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Program.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Program.cs @@ -1,11 +1,11 @@ -using Microsoft.Extensions.AI; +using System.ClientModel; +using Azure.Core; +using Azure.Identity; +using Microsoft.Extensions.AI; +using OpenAI; using aichatweb.Components; using aichatweb.Services; using aichatweb.Services.Ingestion; -using Azure; -using Azure.Identity; -using OpenAI; -using System.ClientModel; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/README.md b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/README.md index 3f50502fb9f..73375f14cfa 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/README.md +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/README.md @@ -5,6 +5,9 @@ This project is an AI chat application that demonstrates how to chat with custom >[!NOTE] > Before running this project you need to configure the API keys or endpoints for the providers you have chosen. See below for details specific to your choices. +### Prerequisites +To use Azure OpenAI or Azure AI Search, you need an Azure account. If you don't already have one, [create an Azure account](https://azure.microsoft.com/free/). + # Configure the AI Model Provider ## Using OpenAI From b8f6134c94a930539d19bcce268156c3f646592d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 1 Oct 2025 15:53:48 -0400 Subject: [PATCH 7/8] Update to use BearerTokenPolicy --- eng/packages/General.props | 4 +-- .../ChatWithCustomData-CSharp.Web/Program.cs | 7 ++--- ...ons.AI.Evaluation.Integration.Tests.csproj | 1 + .../Setup.cs | 29 ++++++++----------- .../IntegrationTestHelpers.cs | 25 +++++----------- 5 files changed, 25 insertions(+), 41 deletions(-) diff --git a/eng/packages/General.props b/eng/packages/General.props index 7a5bd0d46a0..b66f1a4ffa8 100644 --- a/eng/packages/General.props +++ b/eng/packages/General.props @@ -1,8 +1,8 @@ - - + + diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs index d15fb0b0b4b..981edecbea2 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs @@ -62,10 +62,9 @@ #endif var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Endpoint. See the README for details.")), "/openai/v1"); #if (IsManagedIdentity) -var tokenCredential = new DefaultAzureCredential(); -var token = tokenCredential.GetToken(new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), default); -var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; -var azureOpenAi = new OpenAIClient(new ApiKeyCredential(token.Token), openAIOptions); +var azureOpenAi = new OpenAIClient( + new BearerTokenPolicy(new DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"), + new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }); #else var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; var azureOpenAi = new OpenAIClient(new ApiKeyCredential(builder.Configuration["AzureOpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Key. See the README for details.")), openAIOptions); diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj index 579debd599f..8ee7f39ee1c 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj @@ -4,6 +4,7 @@ $(LatestTargetFramework) Microsoft.Extensions.AI Integration tests for Microsoft.Extensions.AI.Evaluation. + $(NoWarn);OPENAI001 diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs index 3221de32067..0ef84f4230f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs @@ -3,6 +3,7 @@ using System; using System.ClientModel; +using System.ClientModel.Primitives; using Azure.Core; using Azure.Identity; using OpenAI; @@ -24,24 +25,18 @@ internal static ChatConfiguration CreateChatConfiguration() private static OpenAI.Chat.ChatClient GetOpenAIClient() { // Use Azure endpoint with /openai/v1 suffix - var endpoint = new Uri(new Uri(Settings.Current.Endpoint), "/openai/v1"); - var credential = new ChainedTokenCredential(new AzureCliCredential(), new DefaultAzureCredential()); - - OpenAIClient client; - if (OfflineOnly) - { - var options = new OpenAIClientOptions { Endpoint = endpoint }; - client = new OpenAIClient(new ApiKeyCredential("Bogus"), options); - } - else + var options = new OpenAIClientOptions { - // Get Azure token and use as API key - var token = credential.GetToken( - new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), - default); - var options = new OpenAIClientOptions { Endpoint = endpoint }; - client = new OpenAIClient(new ApiKeyCredential(token.Token), options); - } + Endpoint = new Uri(new Uri(Settings.Current.Endpoint), "/openai/v1") + }; + + OpenAIClient client = OfflineOnly ? + new OpenAIClient(new ApiKeyCredential("Bogus"), options) : + new OpenAIClient( + new BearerTokenPolicy( + new ChainedTokenCredential(new AzureCliCredential(), new DefaultAzureCredential()), + "https://cognitiveservices.azure.com/.default"), + options); return client.GetChatClient(Settings.Current.DeploymentName); } diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs index 9d72def30e3..4a7e7e4d2c7 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs @@ -3,7 +3,7 @@ using System; using System.ClientModel; -using Azure.Core; +using System.ClientModel.Primitives; using Azure.Identity; using OpenAI; @@ -26,23 +26,12 @@ internal static class IntegrationTestHelpers ?? throw new InvalidOperationException("To use AzureOpenAI, set a value for OpenAI:Endpoint"); // Use Azure endpoint with /openai/v1 suffix - var azureEndpointUri = new Uri(new Uri(endpoint), "/openai/v1"); - - if (apiKey is not null) - { - var options = new OpenAIClientOptions { Endpoint = azureEndpointUri }; - return new OpenAIClient(new ApiKeyCredential(apiKey), options); - } - else - { - // Use Azure Identity authentication - get token and use as API key - var tokenCredential = new DefaultAzureCredential(); - var token = tokenCredential.GetToken( - new TokenRequestContext(["https://cognitiveservices.azure.com/.default"]), - default); - var options = new OpenAIClientOptions { Endpoint = azureEndpointUri }; - return new OpenAIClient(new ApiKeyCredential(token.Token), options); - } + var options = new OpenAIClientOptions { Endpoint = new Uri(new Uri(endpoint), "/openai/v1") }; + return apiKey is not null ? + new OpenAIClient(new ApiKeyCredential(apiKey), options) : + new OpenAIClient( + new BearerTokenPolicy(new DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"), + options); } else if (apiKey is not null) { From 4b0880d6f21b88962ae5f900bdcff8219ebdac38 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 3 Oct 2025 20:57:47 -0400 Subject: [PATCH 8/8] Change scope --- .../ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs | 2 +- .../Setup.cs | 2 +- .../IntegrationTestHelpers.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs index 981edecbea2..06599d601f2 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Program.cs @@ -63,7 +63,7 @@ var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Endpoint. See the README for details.")), "/openai/v1"); #if (IsManagedIdentity) var azureOpenAi = new OpenAIClient( - new BearerTokenPolicy(new DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"), + new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }); #else var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs index 0ef84f4230f..314e17810e1 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Setup.cs @@ -35,7 +35,7 @@ private static OpenAI.Chat.ChatClient GetOpenAIClient() new OpenAIClient( new BearerTokenPolicy( new ChainedTokenCredential(new AzureCliCredential(), new DefaultAzureCredential()), - "https://cognitiveservices.azure.com/.default"), + "https://ai.azure.com/.default"), options); return client.GetChatClient(Settings.Current.DeploymentName); diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs index 4a7e7e4d2c7..d06f20c3f74 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs @@ -30,7 +30,7 @@ internal static class IntegrationTestHelpers return apiKey is not null ? new OpenAIClient(new ApiKeyCredential(apiKey), options) : new OpenAIClient( - new BearerTokenPolicy(new DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"), + new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), options); } else if (apiKey is not null)