diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Components/Pages/Chat/Chat.razor b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Components/Pages/Chat/Chat.razor index a7b1502d894..8aa0ec9fd28 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Components/Pages/Chat/Chat.razor +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.Web/Components/Pages/Chat/Chat.razor @@ -40,6 +40,7 @@ Don't refer to the presence of citations; just emit these tags right at the end, with no surrounding text. "; + private int statefulMessageCount; private readonly ChatOptions chatOptions = new(); private readonly List messages = new(); private CancellationTokenSource? currentResponseCancellation; @@ -49,6 +50,7 @@ protected override void OnInitialized() { + statefulMessageCount = 0; messages.Add(new(ChatRole.System, SystemPrompt)); chatOptions.Tools = [AIFunctionFactory.Create(SearchAsync)]; } @@ -66,15 +68,17 @@ var responseText = new TextContent(""); currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]); currentResponseCancellation = new(); - await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token)) + await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token)) { messages.AddMessages(update, filter: c => c is not TextContent); responseText.Text += update.Text; + chatOptions.ConversationId = update.ConversationId; ChatMessageItem.NotifyChanged(currentResponseMessage); } // Store the final response in the conversation, and begin getting suggestions messages.Add(currentResponseMessage!); + statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0; currentResponseMessage = null; chatSuggestions?.Update(messages); } @@ -96,6 +100,8 @@ CancelAnyCurrentResponse(); messages.Clear(); messages.Add(new(ChatRole.System, SystemPrompt)); + chatOptions.ConversationId = null; + statefulMessageCount = 0; chatSuggestions?.Clear(); await chatInput!.FocusAsync(); } 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 f3f5740066f..f8d2826ab9a 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 @@ -47,7 +47,9 @@ // dotnet user-secrets set OpenAI:Key YOUR-API-KEY var openAIClient = new OpenAIClient( new ApiKeyCredential(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: OpenAI:Key. See the README for details."))); -var chatClient = openAIClient.GetChatClient("gpt-4o-mini").AsIChatClient(); +#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 = 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) @@ -66,7 +68,9 @@ #else new ApiKeyCredential(builder.Configuration["AzureOpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Key. See the README for details."))); #endif -var chatClient = azureOpenAi.GetChatClient("gpt-4o-mini").AsIChatClient(); +#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(); +#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 = azureOpenAi.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator(); #endif diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor index a7b1502d894..8aa0ec9fd28 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.AzureOpenAI_Qdrant_Aspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor @@ -40,6 +40,7 @@ Don't refer to the presence of citations; just emit these tags right at the end, with no surrounding text. "; + private int statefulMessageCount; private readonly ChatOptions chatOptions = new(); private readonly List messages = new(); private CancellationTokenSource? currentResponseCancellation; @@ -49,6 +50,7 @@ protected override void OnInitialized() { + statefulMessageCount = 0; messages.Add(new(ChatRole.System, SystemPrompt)); chatOptions.Tools = [AIFunctionFactory.Create(SearchAsync)]; } @@ -66,15 +68,17 @@ var responseText = new TextContent(""); currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]); currentResponseCancellation = new(); - await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token)) + await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token)) { messages.AddMessages(update, filter: c => c is not TextContent); responseText.Text += update.Text; + chatOptions.ConversationId = update.ConversationId; ChatMessageItem.NotifyChanged(currentResponseMessage); } // Store the final response in the conversation, and begin getting suggestions messages.Add(currentResponseMessage!); + statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0; currentResponseMessage = null; chatSuggestions?.Update(messages); } @@ -96,6 +100,8 @@ CancelAnyCurrentResponse(); messages.Clear(); messages.Add(new(ChatRole.System, SystemPrompt)); + chatOptions.ConversationId = null; + statefulMessageCount = 0; chatSuggestions?.Clear(); await chatInput!.FocusAsync(); } diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Components/Pages/Chat/Chat.razor b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Components/Pages/Chat/Chat.razor index a7b1502d894..8aa0ec9fd28 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Components/Pages/Chat/Chat.razor +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Basic.verified/aichatweb/Components/Pages/Chat/Chat.razor @@ -40,6 +40,7 @@ Don't refer to the presence of citations; just emit these tags right at the end, with no surrounding text. "; + private int statefulMessageCount; private readonly ChatOptions chatOptions = new(); private readonly List messages = new(); private CancellationTokenSource? currentResponseCancellation; @@ -49,6 +50,7 @@ protected override void OnInitialized() { + statefulMessageCount = 0; messages.Add(new(ChatRole.System, SystemPrompt)); chatOptions.Tools = [AIFunctionFactory.Create(SearchAsync)]; } @@ -66,15 +68,17 @@ var responseText = new TextContent(""); currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]); currentResponseCancellation = new(); - await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token)) + await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token)) { messages.AddMessages(update, filter: c => c is not TextContent); responseText.Text += update.Text; + chatOptions.ConversationId = update.ConversationId; ChatMessageItem.NotifyChanged(currentResponseMessage); } // Store the final response in the conversation, and begin getting suggestions messages.Add(currentResponseMessage!); + statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0; currentResponseMessage = null; chatSuggestions?.Update(messages); } @@ -96,6 +100,8 @@ CancelAnyCurrentResponse(); messages.Clear(); messages.Add(new(ChatRole.System, SystemPrompt)); + chatOptions.ConversationId = null; + statefulMessageCount = 0; chatSuggestions?.Clear(); await chatInput!.FocusAsync(); } diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor index a7b1502d894..8aa0ec9fd28 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.BasicAspire.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor @@ -40,6 +40,7 @@ Don't refer to the presence of citations; just emit these tags right at the end, with no surrounding text. "; + private int statefulMessageCount; private readonly ChatOptions chatOptions = new(); private readonly List messages = new(); private CancellationTokenSource? currentResponseCancellation; @@ -49,6 +50,7 @@ protected override void OnInitialized() { + statefulMessageCount = 0; messages.Add(new(ChatRole.System, SystemPrompt)); chatOptions.Tools = [AIFunctionFactory.Create(SearchAsync)]; } @@ -66,15 +68,17 @@ var responseText = new TextContent(""); currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]); currentResponseCancellation = new(); - await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token)) + await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token)) { messages.AddMessages(update, filter: c => c is not TextContent); responseText.Text += update.Text; + chatOptions.ConversationId = update.ConversationId; ChatMessageItem.NotifyChanged(currentResponseMessage); } // Store the final response in the conversation, and begin getting suggestions messages.Add(currentResponseMessage!); + statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0; currentResponseMessage = null; chatSuggestions?.Update(messages); } @@ -96,6 +100,8 @@ CancelAnyCurrentResponse(); messages.Clear(); messages.Add(new(ChatRole.System, SystemPrompt)); + chatOptions.ConversationId = null; + statefulMessageCount = 0; chatSuggestions?.Clear(); await chatInput!.FocusAsync(); } diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Ollama_Qdrant.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Ollama_Qdrant.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor index a7b1502d894..8aa0ec9fd28 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Ollama_Qdrant.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.Ollama_Qdrant.verified/aichatweb/aichatweb.Web/Components/Pages/Chat/Chat.razor @@ -40,6 +40,7 @@ Don't refer to the presence of citations; just emit these tags right at the end, with no surrounding text. "; + private int statefulMessageCount; private readonly ChatOptions chatOptions = new(); private readonly List messages = new(); private CancellationTokenSource? currentResponseCancellation; @@ -49,6 +50,7 @@ protected override void OnInitialized() { + statefulMessageCount = 0; messages.Add(new(ChatRole.System, SystemPrompt)); chatOptions.Tools = [AIFunctionFactory.Create(SearchAsync)]; } @@ -66,15 +68,17 @@ var responseText = new TextContent(""); currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]); currentResponseCancellation = new(); - await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token)) + await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token)) { messages.AddMessages(update, filter: c => c is not TextContent); responseText.Text += update.Text; + chatOptions.ConversationId = update.ConversationId; ChatMessageItem.NotifyChanged(currentResponseMessage); } // Store the final response in the conversation, and begin getting suggestions messages.Add(currentResponseMessage!); + statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0; currentResponseMessage = null; chatSuggestions?.Update(messages); } @@ -96,6 +100,8 @@ CancelAnyCurrentResponse(); messages.Clear(); messages.Add(new(ChatRole.System, SystemPrompt)); + chatOptions.ConversationId = null; + statefulMessageCount = 0; chatSuggestions?.Clear(); await chatInput!.FocusAsync(); } diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Components/Pages/Chat/Chat.razor b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Components/Pages/Chat/Chat.razor index a7b1502d894..8aa0ec9fd28 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Components/Pages/Chat/Chat.razor +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb.OpenAI_AzureAISearch.verified/aichatweb/Components/Pages/Chat/Chat.razor @@ -40,6 +40,7 @@ Don't refer to the presence of citations; just emit these tags right at the end, with no surrounding text. "; + private int statefulMessageCount; private readonly ChatOptions chatOptions = new(); private readonly List messages = new(); private CancellationTokenSource? currentResponseCancellation; @@ -49,6 +50,7 @@ protected override void OnInitialized() { + statefulMessageCount = 0; messages.Add(new(ChatRole.System, SystemPrompt)); chatOptions.Tools = [AIFunctionFactory.Create(SearchAsync)]; } @@ -66,15 +68,17 @@ var responseText = new TextContent(""); currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]); currentResponseCancellation = new(); - await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token)) + await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token)) { messages.AddMessages(update, filter: c => c is not TextContent); responseText.Text += update.Text; + chatOptions.ConversationId = update.ConversationId; ChatMessageItem.NotifyChanged(currentResponseMessage); } // Store the final response in the conversation, and begin getting suggestions messages.Add(currentResponseMessage!); + statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0; currentResponseMessage = null; chatSuggestions?.Update(messages); } @@ -96,6 +100,8 @@ CancelAnyCurrentResponse(); messages.Clear(); messages.Add(new(ChatRole.System, SystemPrompt)); + chatOptions.ConversationId = null; + statefulMessageCount = 0; chatSuggestions?.Clear(); await chatInput!.FocusAsync(); } 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 2b9f0790817..d469d9c43db 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 @@ -16,7 +16,9 @@ // dotnet user-secrets set OpenAI:Key YOUR-API-KEY var openAIClient = new OpenAIClient( new ApiKeyCredential(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: OpenAI:Key. See the README for details."))); -var chatClient = openAIClient.GetChatClient("gpt-4o-mini").AsIChatClient(); +#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 = 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(); // You will need to set the endpoint and key to your own values