microsoft/teams.net
Publicmirrored fromhttps://github.com/microsoft/teams.netAvailable
Samples/Samples.AI/Program.cs
162lines · modecode
| 1 | using System.ClientModel; |
| 2 | using System.Text.RegularExpressions; |
| 3 | |
| 4 | using Azure.AI.OpenAI; |
| 5 | |
| 6 | using Microsoft.Teams.AI.Models.OpenAI; |
| 7 | using Microsoft.Teams.AI.Prompts; |
| 8 | using Microsoft.Teams.AI.Templates; |
| 9 | using Microsoft.Teams.Api.Activities; |
| 10 | using Microsoft.Teams.Apps.Activities; |
| 11 | using Microsoft.Teams.Apps.Activities.Invokes; |
| 12 | using Microsoft.Teams.Apps.Extensions; |
| 13 | using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions; |
| 14 | using Microsoft.Teams.Plugins.AspNetCore.Extensions; |
| 15 | |
| 16 | using Samples.AI.Handlers; |
| 17 | |
| 18 | var builder = WebApplication.CreateBuilder(args); |
| 19 | |
| 20 | // Configuration |
| 21 | var azureOpenAIModel = builder.Configuration["AzureOpenAIModel"] ?? throw new InvalidOperationException("AzureOpenAIModel not configured"); |
| 22 | var azureOpenAIEndpoint = builder.Configuration["AzureOpenAIEndpoint"] ?? throw new InvalidOperationException("AzureOpenAIEndpoint not configured"); |
| 23 | var azureOpenAIKey = builder.Configuration["AzureOpenAIKey"] ?? throw new InvalidOperationException("AzureOpenAIKey not configured"); |
| 24 | |
| 25 | var azureOpenAI = new AzureOpenAIClient( |
| 26 | new Uri(azureOpenAIEndpoint), |
| 27 | new ApiKeyCredential(azureOpenAIKey) |
| 28 | ); |
| 29 | |
| 30 | // Register AI Model as singleton |
| 31 | var aiModel = new OpenAIChatModel(azureOpenAIModel, azureOpenAI); |
| 32 | |
| 33 | builder.AddTeams().AddTeamsDevTools(); |
| 34 | var app = builder.Build(); |
| 35 | |
| 36 | var teamsApp = app.UseTeams(); |
| 37 | |
| 38 | // Simple chat handler - "hi" command |
| 39 | teamsApp.OnMessage(@"^hi$", async (context, cancellationToken) => |
| 40 | { |
| 41 | context.Log.Info($"[COMMAND] 'hi' command invoked by user: {context.Activity.From.Name}"); |
| 42 | |
| 43 | var prompt = new OpenAIChatPrompt(aiModel, new ChatPromptOptions |
| 44 | { |
| 45 | Instructions = new StringTemplate("You are a friendly assistant who talks like a pirate") |
| 46 | }); |
| 47 | |
| 48 | context.Log.Info("[COMMAND] Sending 'hi' message to AI with pirate personality..."); |
| 49 | var result = await prompt.Send(context.Activity.Text, cancellationToken); |
| 50 | if (result.Content != null) |
| 51 | { |
| 52 | context.Log.Info($"[COMMAND] AI response: {result.Content}"); |
| 53 | var messageActivity = new MessageActivity |
| 54 | { |
| 55 | Text = result.Content, |
| 56 | }.AddAIGenerated(); |
| 57 | await context.Send(messageActivity, cancellationToken); |
| 58 | } |
| 59 | }); |
| 60 | |
| 61 | // Pokemon command handler |
| 62 | teamsApp.OnMessage(@"^pokemon\s+(.+)", async (context, cancellationToken) => |
| 63 | { |
| 64 | context.Log.Info($"[COMMAND] 'pokemon' command invoked: {context.Activity.Text}"); |
| 65 | var match = Regex.Match(context.Activity.Text ?? "", @"^pokemon\s+(.+)", RegexOptions.IgnoreCase); |
| 66 | if (match.Success) |
| 67 | { |
| 68 | var pokemonName = match.Groups[1].Value.Trim(); |
| 69 | context.Log.Info($"[COMMAND] Extracted pokemon name: '{pokemonName}'"); |
| 70 | context.Activity.Text = pokemonName; |
| 71 | await FunctionCallingHandler.HandlePokemonSearch(aiModel, context, cancellationToken); |
| 72 | } |
| 73 | }); |
| 74 | |
| 75 | |
| 76 | // Streaming handler |
| 77 | teamsApp.OnMessage(@"^stream\s+(.+)", async (context, cancellationToken) => |
| 78 | { |
| 79 | context.Log.Info($"[COMMAND] 'stream' command invoked: {context.Activity.Text}"); |
| 80 | var match = Regex.Match(context.Activity.Text ?? "", @"^stream\s+(.+)", RegexOptions.IgnoreCase); |
| 81 | if (match.Success) |
| 82 | { |
| 83 | var query = match.Groups[1].Value.Trim(); |
| 84 | context.Log.Info($"[COMMAND] Extracted query for streaming: '{query}'"); |
| 85 | var prompt = new OpenAIChatPrompt(aiModel, new ChatPromptOptions |
| 86 | { |
| 87 | Instructions = new StringTemplate("You are a friendly assistant who responds in extremely verbose language") |
| 88 | }); |
| 89 | |
| 90 | context.Log.Info("[COMMAND] Sending streaming request to AI..."); |
| 91 | var result = await prompt.Send(query, (chunk) => |
| 92 | { |
| 93 | context.Log.Info($"[STREAM] Chunk received: {chunk}"); |
| 94 | context.Stream.Emit(chunk); |
| 95 | return Task.CompletedTask; |
| 96 | }, cancellationToken); |
| 97 | } |
| 98 | }); |
| 99 | |
| 100 | // Citations handler |
| 101 | teamsApp.OnMessage(@"^citations?\b", async (context, cancellationToken) => |
| 102 | { |
| 103 | context.Log.Info($"[COMMAND] 'citations' command invoked: {context.Activity.Text}"); |
| 104 | await CitationsHandler.HandleCitationsDemo(context, cancellationToken); |
| 105 | }); |
| 106 | |
| 107 | // Feedback loop handler |
| 108 | teamsApp.OnMessage(@"^feedback\s+(.+)", async (context, cancellationToken) => |
| 109 | { |
| 110 | context.Log.Info($"[COMMAND] 'feedback' command invoked: {context.Activity.Text}"); |
| 111 | var match = Regex.Match(context.Activity.Text ?? "", @"^feedback\s+(.+)", RegexOptions.IgnoreCase); |
| 112 | if (match.Success) |
| 113 | { |
| 114 | var query = match.Groups[1].Value.Trim(); |
| 115 | context.Log.Info($"[COMMAND] Extracted query for feedback: '{query}'"); |
| 116 | context.Activity.Text = query; |
| 117 | await FeedbackHandler.HandleFeedbackLoop(aiModel, context, cancellationToken); |
| 118 | } |
| 119 | }); |
| 120 | |
| 121 | // Memory clear handler |
| 122 | teamsApp.OnMessage(@"^memory\s+clear\b", async (context, cancellationToken) => |
| 123 | { |
| 124 | context.Log.Info($"[COMMAND] 'memory clear' command invoked for conversation: {context.Activity.Conversation.Id}"); |
| 125 | await MemoryManagementHandler.ClearConversationMemory(context.Activity.Conversation.Id); |
| 126 | await context.Reply("🧠 Memory cleared!", cancellationToken); |
| 127 | }); |
| 128 | |
| 129 | // Prompt-based handler (declarative style) |
| 130 | teamsApp.OnMessage(@"^/weather\b", async (context, cancellationToken) => |
| 131 | { |
| 132 | context.Log.Info($"[COMMAND] '/weather' command invoked: {context.Activity.Text}"); |
| 133 | var prompt = OpenAIChatPrompt.From(aiModel, new Samples.AI.Prompts.WeatherPrompt(context.ToActivityType())); |
| 134 | var result = await prompt.Send(context.Activity.Text, cancellationToken); |
| 135 | if (!string.IsNullOrEmpty(result.Content)) |
| 136 | { |
| 137 | context.Log.Info($"[COMMAND] AI response: {result.Content}"); |
| 138 | var messageActivity = new MessageActivity { Text = result.Content }.AddAIGenerated(); |
| 139 | await context.Send(messageActivity, cancellationToken); |
| 140 | } |
| 141 | else |
| 142 | { |
| 143 | await context.Reply("Sorry I could not figure it out", cancellationToken); |
| 144 | } |
| 145 | }); |
| 146 | |
| 147 | // Feedback submission handler |
| 148 | teamsApp.OnFeedback((context, cancellationToken) => |
| 149 | { |
| 150 | context.Log.Info($"[HANDLER] Feedback submission received"); |
| 151 | FeedbackHandler.HandleFeedbackSubmission(context); |
| 152 | return Task.CompletedTask; |
| 153 | }); |
| 154 | |
| 155 | // Fallback stateful conversation handler |
| 156 | teamsApp.OnMessage(async (context, cancellationToken) => |
| 157 | { |
| 158 | context.Log.Info($"[FALLBACK] Fallback handler invoked (no command matched): {context.Activity.Text}"); |
| 159 | await MemoryManagementHandler.HandleStatefulConversation(aiModel, context, cancellationToken); |
| 160 | }); |
| 161 | |
| 162 | app.Run(); |