From 40b9e132c5fa69e5234f3e7351092fa8a9863ed3 Mon Sep 17 00:00:00 2001 From: Kevin Guo <105208143+kevinguo-ed@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:30:49 +0800 Subject: [PATCH 01/11] Added article: SignalR with Open AI --- aspnetcore/tutorials/ai-powered-group-chat.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 aspnetcore/tutorials/ai-powered-group-chat.md diff --git a/aspnetcore/tutorials/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat.md new file mode 100644 index 000000000000..21c21d6d13e7 --- /dev/null +++ b/aspnetcore/tutorials/ai-powered-group-chat.md @@ -0,0 +1,89 @@ +--- +title: Building AI-Powered Group Chat with SignalR and OpenAI +author: kevinguo-ed +description: ... +ms.author: ... +ms.date: 08/27/2024 +--- + +## Overview + +The integration of AI into applications is rapidly becoming a must-have for developers looking to help their users be more creative, productive and achieve their health goals. AI-powered features, such as intelligent chatbots, personalized recommendations, and contextual responses, add significant value to modern apps. The AI-powered apps that came out since Chat GPT captured our imagination are primarily between one user and one AI assistant. As developers get more comfortable with the capabilities of AI, they are exploring AI-powered apps in a team's context. They ask "what value can AI add to a team of collaborators"? + +This tutorial guides you through building a real-time group chat application. Among a group of human collaborators in a chat, there's an AI assistant which has access to the chat history and can be invited to help out by any collaborator when they start the message with `@gpt`. The finished app looks like this. + +{{Chat interface missing...}} + +We use Open AI for generating intelligent, context-aware responses and SignalR for delivering the response to users in a group. You can find the complete code [in this repo](https://github.com/microsoft/SignalR-Samples-AI/tree/main/AIStreaming). + +## Dependencies +You can use either Azure Open AI or Open AI for this project. Make sure to update the `endpoint` and `key` in `appsetting.json`. `OpenAIExtensions` reads the configuration when the app starts and they are required to authenticate and use either service. + +# [Open AI](#tab/open-ai) +To build this application, you will need the following: +* ASP.NET Core: To create the web application and host the SignalR hub. +* [SignalR](https://www.nuget.org/packages/Microsoft.AspNetCore.SignalR.Client): For real-time communication between clients and the server. +* [OpenAI Client](https://www.nuget.org/packages/OpenAI/2.0.0-beta.10): To interact with OpenAI's API for generating AI responses. + +# [Azure Open AI](#tab/azure-open-ai) +To build this application, you will need the following: +* ASP.NET Core: To create the web application and host the SignalR hub. +* [SignalR](https://www.nuget.org/packages/Microsoft.AspNetCore.SignalR.Client): For real-time communication between clients and the server. +* [Azure Open AI](https://www.nuget.org/packages/Azure.AI.OpenAI/2.0.0-beta.3): Azure.AI.OpenAI +--- + +## Implementation + +In this section, we'll walk through the key parts of the code that integrate SignalR with OpenAI to create an AI-enhanced group chat experience. + +### SignalR Hub integration** + +The `GroupChatHub` class manages user connections, message broadcasting, and AI interactions. When a user sends a message starting with `@gpt`, the hub forwards it to OpenAI, which generates a response. The AI's response is streamed back to the group in real-time. +```csharp +var chatClient = _openAI.GetChatClient(_options.Model); +await foreach (var completion in chatClient.CompleteChatStreamingAsync(messagesInludeHistory)) +{ + // ... + // Buffering and sending the AI's response in chunks + await Clients.Group(groupName).SendAsync("newMessageWithId", "ChatGPT", id, totalCompletion.ToString()); + // ... +} +``` + +The chat history is managed by `GroupHistoryStore`, which stores messages for context. It includes both user and assistant messages to maintain conversation continuity. +```csharp +public void UpdateGroupHistoryForAssistant(string groupName, string message) { ... } +``` + +### Streaming AI responses + +The `CompleteChatStreamingAsync()` method streams responses from OpenAI incrementally, which allows the application to send partial responses to the client as they are generated. + +The code uses a `StringBuilder` to accumulate the AI's response. It checks the length of the buffered content and sends it to the clients when it exceeds a certain threshold (e.g., 20 characters). This approach ensures that users see the AI’s response as it forms, mimicking a human-like typing effect. +```csharp +totalCompletion.Append(content); +if (totalCompletion.Length - lastSentTokenLength > 20) +{ + await Clients.Group(groupName).SendAsync("newMessageWithId", "ChatGPT", id, totalCompletion.ToString()); + lastSentTokenLength = totalCompletion.Length; +} +``` + +### Maintaining context with history + +The `GroupHistoryStore` class manages the chat history for each group. It stores both user and AI messages, ensuring that the conversation context is preserved across interactions. This context is crucial for generating coherent AI responses. +```csharp +public void UpdateGroupHistoryForAssistant(string groupName, string message) +{ + var chatMessages = _store.GetOrAdd(groupName, _ => InitiateChatMessages()); + chatMessages.Add(new AssistantChatMessage(message)); +} +``` + +### Explore further + +This project opens up exciting possibilities for further enhancement: +1. **Advanced AI features**: Leverage other OpenAI capabilities like sentiment analysis, translation, or summarization. +2. **Incorporating multiple AI agents**: You can introduce multiple AI agents with distinct roles or expertise areas within the same chat. For example, one agent might focus on text generation, the other provides image or audio generation. This can create a richer and more dynamic user experience where different AI agents interact seamlessly with users and each other. +3. **Share chat history between server instances**: Implement a database layer to persist chat history across sessions, allowing conversations to resume even after a disconnect. Beyond SQL or NO SQL based solutions, you can also explore using a caching service like Redis. It can significantly improve performance by storing frequently accessed data, such as chat history or AI responses, in memory. This reduces latency and offloads database operations, leading to faster response times, particularly in high-traffic scenarios. +4. **Leveraging Azure SignalR Service**: [Azure SignalR Service](https://learn.microsoft.com/azure/azure-signalr/signalr-overview) provides scalable and reliable real-time messaging for your application. By offloading the SignalR backplane to Azure, you can scale out the chat application easily to support thousands of concurrent users across multiple servers. Azure SignalR also simplifies management and provides built-in features like automatic reconnections. From 9a5ed0ccaccbb9550543b10407844dda27d75f48 Mon Sep 17 00:00:00 2001 From: Kevin Guo <105208143+kevinguo-ed@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:53:11 +0800 Subject: [PATCH 02/11] Added missing images and fixed formatting issues --- aspnetcore/toc.yml | 2 ++ .../ai-powered-group-chat.jpg | Bin 0 -> 47882 bytes .../ai-powered-group-chat.md | 29 ++++++++++-------- ...sequence-diagram-ai-powered-group-chat.png | Bin 0 -> 43246 bytes 4 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.jpg rename aspnetcore/tutorials/{ => ai-powered-group-chat}/ai-powered-group-chat.md (77%) create mode 100644 aspnetcore/tutorials/ai-powered-group-chat/sequence-diagram-ai-powered-group-chat.png diff --git a/aspnetcore/toc.yml b/aspnetcore/toc.yml index f501a843b47d..5bdd4b3cd689 100644 --- a/aspnetcore/toc.yml +++ b/aspnetcore/toc.yml @@ -874,6 +874,8 @@ items: displayName: signalr - name: Tutorials items: + - name: AI-powered group chat + uid: tutorials/ai-powered-group-chat - name: SignalR with JavaScript uid: tutorials/signalr - name: SignalR with TypeScript diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.jpg b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a9d78f70f19ea1685bf713f9735617abfc89c06 GIT binary patch literal 47882 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 JrpmhKy3fLsuePfvx)F)ChrY$FoHZOl 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≠ihzJs+avYo=KiO@F!a_;3e#zvU((w+6mOCu5mpT=^(3PpcTJM0s7XYE*BInT4#X#9FZkELqe2R$6LH3QLDbh!EpWBXGI4E(u1p zyY3|m!@EPhT4CT)l!}u4R?SgvOvqSf04gdq<_%|LF?2FNHe0m9plCYoRM);6eG{bKG>$#RJDJ(wyLi4OTN0dsL> zKQXtRd3FWzXLLUg)yuv~I+O+uplCX$2S?`D!Mo}AlVhMCr`fRIyvfO(NHOiAu(WZr z*J=MAzk56^VRLefLLN+ta);)OVP3ePgg-o_zO-NHD%7lacxm0f#2X;4l!eo?GG-QN zNgKS~-q174JbvFOZV@&&lSoO@FBwQtD>n zsG5nU!;&Wc)2+*xpaDah=9K0q#Mv+F_9P7nS{}6fE1o^sTofR2xBP>(Ve$vh_Wh9> zm!$qiY=K78oi6{dt62wrq9FIByf>SpCmk;L_k|NWmAxT0KcbqP|0+#si%ZORx)14c zNlxcC7l&MCHzSjh9_y!T1xPrP-oBk0n{End-=%+$6EanymP?x{PUX&SEaCETeE!UY zFQS3>x@d45t|(+IJyZ3K8NHLK(R+K@ZDju>sTpga0hx8<*|L0ZqznmmX@9m#W(F*1 zoK5&Bt4+RGXlKMGI$N;uJQD~I8@b=7*G_TWR`?m~uEzxCAwG#o`8}6)vR7}GgVZ#H zaf0#tPVo@8t7v^jvgSB72WjPghL;z(%qn}nE2%9f^ZnyL_fyxD zlCBhf>bWyqPTQGF-rM!D$p0i?mI9r-Y~PwSKhhM)7Uw@v5?QAU7W%;}VVv<#Z`S{` z8>$4_Fy(Zg*Wq#IgvG46Z0ZhWY)toK)t`=HCSFQ)c8R|*Bj+^>pI^PAapQIUE(iE+ zFt>!vgErMd*;{$Kt_4A-M6q&tyWOyGeB6;CQQZ7v#1JT-JJ#fk6gDAJ&_4FfjhX~* zH%?|}R(;Ht={B&`>IbtIe+igcv;+zp#U(A-ucIY>66NobKY6BMlUQw$sL7MQ*h

d5 zYeNKPs*9k4%(bPf@1MHqKGSml?X>%OnUxyXmKBdDmLkA8XXoU$Cr*4HEt}0Vt@NIk`rI zBFogKm9wBZU10vlIRT20(2Hib^R4>MOLSF?S1eV&$^B4x^_iJ^c3hyd^-{WEor&XI6B-3CFF7k111Uq-mbrF*cfeT#*#|>Vn!W2Qruy8D6^K zWDvH%xx504Ne!kcfRDM;;$K^ZCHby4dX}lV%7x=IGoaF&3z_Q%(dw3&>2c^Wch8G= z#7Hj&$*wU?Dfe(cDeWRRd!nng`#{Xe`q!Y(es=!UyKDCfPc~!e0A|qf#Um?|_GI>$ zA6p!-`xVReiLYuT%$X9KH0vOsuF!j!rCQpT~`7<*-PE^WZt-e8 zNWJd66I!CWU!dHql9{rOenTxS=Hv>$!e@%`T=ABty8VIguvV(x zvb_RebH!LUn^sHdm-&m|`uo&@f=nxGmYKK~pysmG;q#AV8$|>fsg5+jE@bzs&*fTWpBVKNtxU9;Pu{uA z)M~=ef{tGz7TS2v6;f}KiXz?ABX29z`jCoM?wFh#lpnFme(0d-UQwyths_s~**Mnq zRUl1N+SeleMx=pX%j7Pp4dN_C?2qzshQ=ZCOE0U;@rw%*r-P z4i<%AHfIwLGV z@(+6qvoGevYlfIBIo%;ATH83Ev`%uTX2}P+>;-U^>C4`@-6K6;^-amVo2Okj`RjIt zS_7eb#Sp*IUkMt5)Nmc z`hqP7u7<7S66t+W^57_a>gMy3M&ZW42ax=h6H7t#u=-H_r#XHk+&zPqs#`f#Ro!2r zhZU$4oll+o{}L`#LGxR-<#X`Q5rUkzN@E2K$i@Mk9NFsWkB*=J1EN zp8o3t7$ZURcvexp`Ij_(7N(h+P(^~N5vy4S8MX&z)UWWhIfLb?Y+(Fo>a^WH{ zRIP~#xE0KWcQV~(`@GKZO|HE?>A;s6^Sa)Dxqo@JZvWM~)atcc%;#VENG^vPbYgPTq%ECKq-Jid>rJ8i=h^ArfEgr|o&R_SlL^(HnUXl6e$_tUp zi`>u80TWf>A zL~e-onPp+P@5hJLFK%u2F3HjlAE{i+6-=iNmrdfDoO`5&Tr=RojtfWgVnSttR z;amTSg{d;#JdyByO|xnKd_7*(9`2VFYeg=7s9nuj(vZFMYL3;1zI*Xj1&_lwzcQ_> zUnyM}bJlF54q_OOk#maD1&icUOS7JrBP9r-4A6-&+zB%w2lar{0Q3xO@ES5${5Ws} zBPW{S^cp0+y}rOScjImLTcnf|1w5zb4pT%LQu@ls0Y>$)y!dS7J$Il}pv4XtL6)RJ zhUg%t2>p&i-ohsaT13S#EfOj5OMz}2-gx`^E%-bJa3UMDsDpLG8OY{NuoExb04BQL zyZf&BA&otPj=j-sNDV^-OXD%n{daG^Z7oCcm@a76f>CEPQeHAK0|ry__Vrt#Q|BNj jj|San(9t^8HU8(9+S;^sMwS5&0}yz+`njxgN@xNAfwCaU literal 0 HcmV?d00001 From cb9ddee10ef443bb19067183e2670de693fb8496 Mon Sep 17 00:00:00 2001 From: Kevin Guo <105208143+kevinguo-ed@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:32:18 +0800 Subject: [PATCH 03/11] Added more details about managing chat history --- .../ai-powered-group-chat.md | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index fbc5b05dc611..53e8a0846f47 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -55,9 +55,24 @@ await foreach (var completion in chatClient.CompleteChatStreamingAsync(messagesI } ``` -The chat history is managed by `GroupHistoryStore`, which stores messages for context. It includes both user and assistant messages to maintain conversation continuity. +### Maintaining context with history + +Every request to [Open AI's Chat Completions API](https://platform.openai.com/docs/guides/chat-completions) is stateless - Open AI doesn't store past interactions. In a chat application, what a user or an assistant has said is important for generating a response that's contextually relevant. We can achieve this by including chat history in every request to the Completions API. + +The `GroupHistoryStore` class manages chat history for each group. It stores messages posted by both the users and AI assistants, ensuring that the conversation context is preserved across interactions. This context is crucial for generating coherent AI responses. + +```csharp +// Store message generated by AI-assistant in memory +public void UpdateGroupHistoryForAssistant(string groupName, string message) +{ + var chatMessages = _store.GetOrAdd(groupName, _ => InitiateChatMessages()); + chatMessages.Add(new AssistantChatMessage(message)); +} +``` + ```csharp -public void UpdateGroupHistoryForAssistant(string groupName, string message) { ... } +// Store message generated by users in memory +_history.GetOrAddGroupHistory(groupName, userName, message); ``` ### Streaming AI responses @@ -74,17 +89,6 @@ if (totalCompletion.Length - lastSentTokenLength > 20) } ``` -### Maintaining context with history - -The `GroupHistoryStore` class manages the chat history for each group. It stores both user and AI messages, ensuring that the conversation context is preserved across interactions. This context is crucial for generating coherent AI responses. -```csharp -public void UpdateGroupHistoryForAssistant(string groupName, string message) -{ - var chatMessages = _store.GetOrAdd(groupName, _ => InitiateChatMessages()); - chatMessages.Add(new AssistantChatMessage(message)); -} -``` - ## Explore further This project opens up exciting possibilities for further enhancement: From e77a97323573929b15eef86483df601010a073a3 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:36:29 -0800 Subject: [PATCH 04/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Will be pulling all commits into a new PR. Commiting suggestions here before that transfer starts. Co-authored-by: Luke Latham <1622880+guardrex@users.noreply.github.com> --- .../tutorials/ai-powered-group-chat/ai-powered-group-chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index 53e8a0846f47..b68c99299403 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -18,7 +18,7 @@ This tutorial guides you through building a real-time group chat application. Am We use OpenAI for generating intelligent, context-aware responses and SignalR for delivering the response to users in a group. You can find the complete code [in this repo](https://github.com/microsoft/SignalR-Samples-AI/tree/main/AIStreaming). ## Dependencies -You can use either Azure OpenAI or OpenAI for this project. Make sure to update the `endpoint` and `key` in `appsetting.json`. `OpenAIExtensions` reads the configuration when the app starts and they are required to authenticate and use either service. +You can use either Azure OpenAI or OpenAI for this project. Make sure to update the `endpoint` and `key` in `appsettings.json`. `OpenAIExtensions` reads the configuration when the app starts, and the configuration values for `endpoint` and `key` are required to authenticate and use either service. # [OpenAI](#tab/open-ai) To build this application, you will need the following: From 82f01f59aa8d007e3211d61466dd9f9d79d021f1 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:40:12 -0800 Subject: [PATCH 05/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Co-authored-by: David Pine --- .../ai-powered-group-chat/ai-powered-group-chat.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index b68c99299403..0986c9d080e0 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -93,6 +93,6 @@ if (totalCompletion.Length - lastSentTokenLength > 20) This project opens up exciting possibilities for further enhancement: 1. **Advanced AI features**: Leverage other OpenAI capabilities like sentiment analysis, translation, or summarization. -2. **Incorporating multiple AI agents**: You can introduce multiple AI agents with distinct roles or expertise areas within the same chat. For example, one agent might focus on text generation, the other provides image or audio generation. This can create a richer and more dynamic user experience where different AI agents interact seamlessly with users and each other. -3. **Share chat history between server instances**: Implement a database layer to persist chat history across sessions, allowing conversations to resume even after a disconnect. Beyond SQL or NO SQL based solutions, you can also explore using a caching service like Redis. It can significantly improve performance by storing frequently accessed data, such as chat history or AI responses, in memory. This reduces latency and offloads database operations, leading to faster response times, particularly in high-traffic scenarios. -4. **Leveraging Azure SignalR Service**: [Azure SignalR Service](https://learn.microsoft.com/azure/azure-signalr/signalr-overview) provides scalable and reliable real-time messaging for your application. By offloading the SignalR backplane to Azure, you can scale out the chat application easily to support thousands of concurrent users across multiple servers. Azure SignalR also simplifies management and provides built-in features like automatic reconnections. +1. **Incorporating multiple AI agents**: You can introduce multiple AI agents with distinct roles or expertise areas within the same chat. For example, one agent might focus on text generation, the other provides image or audio generation. This can create a richer and more dynamic user experience where different AI agents interact seamlessly with users and each other. +1. **Share chat history between server instances**: Implement a database layer to persist chat history across sessions, allowing conversations to resume even after a disconnect. Beyond SQL or NO SQL based solutions, you can also explore using a caching service like Redis. It can significantly improve performance by storing frequently accessed data, such as chat history or AI responses, in memory. This reduces latency and offloads database operations, leading to faster response times, particularly in high-traffic scenarios. +1. **Leveraging Azure SignalR Service**: [Azure SignalR Service](/azure/azure-signalr/signalr-overview) provides scalable and reliable real-time messaging for your application. By offloading the SignalR backplane to Azure, you can scale out the chat application easily to support thousands of concurrent users across multiple servers. Azure SignalR also simplifies management and provides built-in features like automatic reconnections. From dabdfd6c4c8a70032d419f6582647b0139478df0 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:44:21 -0800 Subject: [PATCH 06/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Co-authored-by: David Pine --- .../tutorials/ai-powered-group-chat/ai-powered-group-chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index 0986c9d080e0..003ce0cd1b04 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -57,7 +57,7 @@ await foreach (var completion in chatClient.CompleteChatStreamingAsync(messagesI ### Maintaining context with history -Every request to [Open AI's Chat Completions API](https://platform.openai.com/docs/guides/chat-completions) is stateless - Open AI doesn't store past interactions. In a chat application, what a user or an assistant has said is important for generating a response that's contextually relevant. We can achieve this by including chat history in every request to the Completions API. +Every request to [Open AI's Chat Completions API](https://platform.openai.com/docs/guides/chat-completions) is stateless—Open AI doesn't store past interactions. In a chat app, what a user or an assistant has said is important for generating a response that's contextually relevant. To achieve this, include chat history in every request to the Completions API. The `GroupHistoryStore` class manages chat history for each group. It stores messages posted by both the users and AI assistants, ensuring that the conversation context is preserved across interactions. This context is crucial for generating coherent AI responses. From c43892e8f5415350ba4b986fefde93268ded009e Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:44:45 -0800 Subject: [PATCH 07/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Co-authored-by: David Pine --- .../ai-powered-group-chat/ai-powered-group-chat.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index 003ce0cd1b04..5f7a37f11636 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -82,9 +82,15 @@ The `CompleteChatStreamingAsync()` method streams responses from OpenAI incremen The code uses a `StringBuilder` to accumulate the AI's response. It checks the length of the buffered content and sends it to the clients when it exceeds a certain threshold (e.g., 20 characters). This approach ensures that users see the AI’s response as it forms, mimicking a human-like typing effect. ```csharp totalCompletion.Append(content); + if (totalCompletion.Length - lastSentTokenLength > 20) { - await Clients.Group(groupName).SendAsync("newMessageWithId", "ChatGPT", id, totalCompletion.ToString()); + await Clients.Group(groupName).SendAsync( + "newMessageWithId", + "ChatGPT", + id, + totalCompletion.ToString()); + lastSentTokenLength = totalCompletion.Length; } ``` From d9db27513d9c0025fb86431a578ecbe0010c23d4 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:46:04 -0800 Subject: [PATCH 08/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Co-authored-by: David Pine --- .../tutorials/ai-powered-group-chat/ai-powered-group-chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index 5f7a37f11636..1ee7f86053b2 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -77,7 +77,7 @@ _history.GetOrAddGroupHistory(groupName, userName, message); ### Streaming AI responses -The `CompleteChatStreamingAsync()` method streams responses from OpenAI incrementally, which allows the application to send partial responses to the client as they are generated. +The `CompleteChatStreamingAsync()` method streams responses from OpenAI incrementally, which allows the app to send partial responses to the client as they are generated. The code uses a `StringBuilder` to accumulate the AI's response. It checks the length of the buffered content and sends it to the clients when it exceeds a certain threshold (e.g., 20 characters). This approach ensures that users see the AI’s response as it forms, mimicking a human-like typing effect. ```csharp From ab6d9ad49bbb18061dd65abfcdca15248aa3a982 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:46:29 -0800 Subject: [PATCH 09/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Co-authored-by: David Pine --- .../tutorials/ai-powered-group-chat/ai-powered-group-chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index 1ee7f86053b2..ade536f5e807 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -75,7 +75,7 @@ public void UpdateGroupHistoryForAssistant(string groupName, string message) _history.GetOrAddGroupHistory(groupName, userName, message); ``` -### Streaming AI responses +### Stream AI responses The `CompleteChatStreamingAsync()` method streams responses from OpenAI incrementally, which allows the app to send partial responses to the client as they are generated. From cb2b2917fcffda48bd52a2b5e64206bfcc40d756 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:47:00 -0800 Subject: [PATCH 10/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Co-authored-by: David Pine --- .../tutorials/ai-powered-group-chat/ai-powered-group-chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index ade536f5e807..583bd1b0ba15 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -55,7 +55,7 @@ await foreach (var completion in chatClient.CompleteChatStreamingAsync(messagesI } ``` -### Maintaining context with history +### Maintain context with history Every request to [Open AI's Chat Completions API](https://platform.openai.com/docs/guides/chat-completions) is stateless—Open AI doesn't store past interactions. In a chat app, what a user or an assistant has said is important for generating a response that's contextually relevant. To achieve this, include chat history in every request to the Completions API. From 6d533d529e708aebe2c8d820fe1204f0073bbd97 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 25 Feb 2025 12:47:24 -0800 Subject: [PATCH 11/11] Update aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md Co-authored-by: David Pine --- .../ai-powered-group-chat/ai-powered-group-chat.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md index 583bd1b0ba15..614ee7a0c427 100644 --- a/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md +++ b/aspnetcore/tutorials/ai-powered-group-chat/ai-powered-group-chat.md @@ -46,11 +46,17 @@ In this section, we'll walk through the key parts of the code that integrate Sig The `GroupChatHub` class manages user connections, message broadcasting, and AI interactions. When a user sends a message starting with `@gpt`, the hub forwards it to OpenAI, which generates a response. The AI's response is streamed back to the group in real-time. ```csharp var chatClient = _openAI.GetChatClient(_options.Model); -await foreach (var completion in chatClient.CompleteChatStreamingAsync(messagesInludeHistory)) + +await foreach (var completion in + chatClient.CompleteChatStreamingAsync(messagesInludeHistory)) { // ... // Buffering and sending the AI's response in chunks - await Clients.Group(groupName).SendAsync("newMessageWithId", "ChatGPT", id, totalCompletion.ToString()); + await Clients.Group(groupName).SendAsync( + "newMessageWithId", + "ChatGPT", + id, + totalCompletion.ToString()); // ... } ```