From 7ad7e6bfbf3d4fd1ae5715933d1511db89ae69e5 Mon Sep 17 00:00:00 2001 From: Kavin <115390646+singhk97@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:56:12 -0800 Subject: [PATCH] [C#] bump: dotnet to 1.10.0 (#2295) ## Linked issues closes: #minor ## Details - Bump `Microsoft.Teams.AI` to version 1.10.0 - Update samples to point to this version. **New Changes** https://github.com/microsoft/teams-ai/pull/2235 https://github.com/microsoft/teams-ai/pull/2250 https://github.com/microsoft/teams-ai/pull/2278 https://github.com/microsoft/teams-ai/pull/2232 ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --------- Signed-off-by: dependabot[bot] Co-authored-by: Lily Du Co-authored-by: lilydu Co-authored-by: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com> Co-authored-by: Steven Ickman Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Corina <14900841+corinagum@users.noreply.github.com> Co-authored-by: Yiqing Zhao Co-authored-by: Yiqing Zhao Co-authored-by: Alex Acebo Co-authored-by: Eric Zhu Co-authored-by: Peter --- .../Application/ApplicationRouteTests.cs | 53 +++++++ .../Application/StreamingResponseTests.cs | 7 +- .../Microsoft.Teams.AI.Tests.csproj | 74 +++++----- .../Microsoft.TeamsAI/AI/AI.cs | 2 +- .../Microsoft.TeamsAI/AI/AIOptions.cs | 6 + .../Microsoft.TeamsAI/AI/Action/AIEntity.cs | 130 +++++++++++++++++- .../AI/Action/DefaultActions.cs | 30 +++- .../Microsoft.TeamsAI/AI/Clients/LLMClient.cs | 17 ++- .../AI/Clients/LLMClientOptions.cs | 5 + .../AI/Planners/ActionPlanner.cs | 7 + .../Application/Application.cs | 45 ++++++ .../Application/MessageFetchTaskHandler.cs | 33 +++++ .../Application/StreamingChannelData.cs | 6 + .../Application/StreamingResponse.cs | 103 +++++++++----- .../Microsoft.Teams.AI.csproj | 114 +++++++-------- .../Microsoft.TeamsAI/TypingTimer.cs | 17 ++- .../01.messaging.echoBot/EchoBot.csproj | 4 +- .../SearchCommand.csproj | 4 +- .../TypeAheadBot.csproj | 6 +- .../04.ai.a.teamsChefBot/TeamsChefBot.csproj | 8 +- .../GPT.csproj | 4 +- .../LightBot.csproj | 4 +- .../ListBot.csproj | 4 +- .../DevOpsBot.csproj | 4 +- .../CardGazer.csproj | 4 +- .../TeamsChefBot.csproj | 8 +- .../TwentyQuestions.csproj | 4 +- .../05.chatModeration/ChatModeration.csproj | 8 +- .../06.assistants.a.mathBot/MathBot.csproj | 4 +- .../06.assistants.b.orderBot/OrderBot.csproj | 4 +- .../samples/06.auth.oauth.bot/BotAuth.csproj | 4 +- .../MessageExtensionAuth.csproj | 4 +- .../06.auth.teamsSSO.bot/BotAuth.csproj | 4 +- .../MessageExtensionAuth.csproj | 4 +- .../AzureAISearchBot/AzureAISearchBot.csproj | 8 +- .../AzureAISearchIndexer.csproj | 12 -- .../AzureOpenAIBot.csproj | 8 +- 37 files changed, 553 insertions(+), 210 deletions(-) create mode 100644 dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/MessageFetchTaskHandler.cs diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationRouteTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationRouteTests.cs index bfda685523..1dec60ccac 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationRouteTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationRouteTests.cs @@ -2,6 +2,7 @@ using Microsoft.Bot.Connector; using Microsoft.Bot.Schema; using Microsoft.Bot.Schema.Teams; +using Microsoft.Teams.AI.Application; using Microsoft.Teams.AI.State; using Microsoft.Teams.AI.Tests.TestUtils; using Moq; @@ -1946,6 +1947,58 @@ void CaptureSend(Activity[] arg) Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } + [Fact] + public async Task Test_OnMessageFetchTask() + { + // Arrange + Activity[]? activitiesToSend = null; + void CaptureSend(Activity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "message/fetchTask", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var turnContext1 = new TurnContext(adapter, activity1); + var messageFetchTaskResponse = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = messageFetchTaskResponse.Object + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnMessageFetchTask((turnContext, _, _, _) => + { + names.Add(turnContext.Activity.Name); + return Task.FromResult(messageFetchTaskResponse.Object); + }); + + // Act + await app.OnTurnAsync(turnContext1); + + // Assert + Assert.Single(names); + Assert.Equal("message/fetchTask", names[0]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + [Fact] public async Task Test_OnConfigSubmit() { diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs index d75139e13e..3cf1c340a6 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs @@ -220,7 +220,8 @@ void CaptureSend(Activity[] arg) StreamingResponse streamer = new(turnContext); List citations = new List(); citations.Add(new Citation(content: "test-content", title: "test", url: "https://example.com")); - streamer.QueueTextChunk("first", citations); + streamer.SetCitations(citations); + streamer.QueueTextChunk("first"); await streamer.WaitForQueue(); streamer.QueueTextChunk("second"); await streamer.WaitForQueue(); @@ -229,6 +230,10 @@ void CaptureSend(Activity[] arg) streamer.SensitivityLabel = new SensitivityUsageInfo() { Name= "Sensitivity"}; await streamer.EndStream(); Assert.Equal(2, streamer.UpdatesSent()); + if (streamer.Citations != null) + { + Assert.Single(streamer.Citations); + } } [Fact] diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj index ebc4d6bd05..302ddc7693 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj @@ -1,37 +1,37 @@ - - - - net6.0 - enable - enable - - false - true - x64 - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - + + + + net6.0 + enable + enable + + false + true + x64 + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs index 6a3057a774..05e63e9b91 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs @@ -41,7 +41,7 @@ public AI(AIOptions options, ILoggerFactory? loggerFactory = null) _actions = new ActionCollection(); // Import default actions - ImportActions(new DefaultActions(options.EnableFeedbackLoop, loggerFactory)); + ImportActions(new DefaultActions(options.EnableFeedbackLoop, options.FeedbackLoopType, loggerFactory)); } /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AIOptions.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AIOptions.cs index 4017df5eb3..6d5796d557 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AIOptions.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AIOptions.cs @@ -62,6 +62,12 @@ public sealed class AIOptions where TState : TurnState /// public bool EnableFeedbackLoop { get; set; } = false; + /// + /// Represents the type of feedback loop. Set to "default" by default. It can be set to one of "default" or "custom". + /// The property should be set to true to use this property. + /// + public string FeedbackLoopType { get; set; } = "default"; + /// /// Initializes a new instance of the class. /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs index 12f7c597d1..a94cc36110 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs @@ -66,7 +66,7 @@ public class ClientCitation /// Required. Number and position of the citation. /// [JsonProperty(PropertyName = "position")] - public string Position { get; set; } = string.Empty; + public int Position { get; set; } /// /// The citation's appearance. @@ -88,7 +88,7 @@ public class ClientCitationAppearance public string AtType = "DigitalDocument"; /// - /// Name of the document. + /// Name of the document (max length 80). /// [JsonProperty(PropertyName = "name")] public string Name { get; set; } = string.Empty; @@ -106,13 +106,14 @@ public class ClientCitationAppearance public string? Url { get; set; } /// - /// Content of the citation. Must be clipped if longer than 480 characters. + /// Extract of the referenced content (max length 160). /// [JsonProperty(PropertyName = "abstract")] public string Abstract { get; set; } = string.Empty; /// - /// Optional. Encoding format of the `citation.appearance.text` field. + /// Optional. Encoding format of the `citation.appearance.text` field. + /// It should be one of "text/html" or "application/vnd.microsoft.card.adaptive". /// [JsonProperty(PropertyName = "encodingFormat")] public string? EncodingFormat { get; set; } @@ -121,7 +122,7 @@ public class ClientCitationAppearance /// The icon provided in the citation ui. /// [JsonProperty(PropertyName = "image")] - public string? Image { get; set; } + public AppearanceImage? Image { get; set; } /// /// Optional. Set the keywords. @@ -207,4 +208,123 @@ public class SensitivityUsageInfoPattern [JsonProperty(PropertyName = "termCode")] public string? TermCode { get; set; } } + + /// + /// Represents how the citation will be rendered. + /// + public class AppearanceImage + { + /// + /// Required. Must be "ImageObject". + /// + [JsonProperty(PropertyName = "@type")] + public string Type { get; set; } = "ImageObject"; + + /// + /// The image/icon name. It should be one of + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } = string.Empty; + } + + /// + /// Represents the different possible values for the client citation icon name. + /// + public static class ClientCitationIconName + { + /// + /// Represents the Microsoft Word icon name. + /// + public static readonly string MicrosoftWord = "Microsoft Word"; + + /// + /// Represents the Microsoft Excel icon name. + /// + public static readonly string MicrosoftExcel = "Microsoft Excel"; + + /// + /// Represents the Microsoft PowerPoint icon name. + /// + public static readonly string MicrosoftPowerPoint = "Microsoft PowerPoint"; + + /// + /// Represents the Microsoft Visio icon name. + /// + public static readonly string MicrosoftVisio = "Microsoft Visio"; + + /// + /// Represents the Microsoft Loop icon name. + /// + public static readonly string MicrosoftLoop = "Microsoft Loop"; + + /// + /// Represents the Microsoft Whiteboard icon name. + /// + public static readonly string MicrosoftWhiteboard = "Microsoft Whiteboard"; + + /// + /// Represents the Adobe Illustrator icon name. + /// + public static readonly string AdobeIllustrator = "Adobe Illustrator"; + + /// + /// Represents the Adobe Photoshop icon name. + /// + public static readonly string AdobePhotoshop = "Adobe Photoshop"; + + /// + /// Represents the Adobe InDesign icon name. + /// + public static readonly string AdobeInDesign = "Adobe InDesign"; + + /// + /// Represents the Adobe Flash icon name. + /// + public static readonly string AdobeFlash = "Adobe Flash"; + + /// + /// Represents the Sketch icon name. + /// + public static readonly string Sketch = "Sketch"; + + /// + /// Represents the Source Code icon name. + /// + public static readonly string SourceCode = "Source Code"; + + /// + /// Represents the Image icon name. + /// + public static readonly string Image = "Image"; + + /// + /// Represents the GIF icon name. + /// + public static readonly string GIF = "GIF"; + + /// + /// Represents the Video icon name. + /// + public static readonly string Video = "Video"; + + /// + /// Represents the Sound icon name. + /// + public static readonly string Sound = "Sound"; + + /// + /// Represents the ZIP icon name. + /// + public static readonly string ZIP = "ZIP"; + + /// + /// Represents the Text icon name. + /// + public static readonly string Text = "Text"; + + /// + /// Represents the PDF icon name. + /// + public static readonly string PDF = "PDF"; + } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/DefaultActions.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/DefaultActions.cs index 76a878aca4..c56f81d5b3 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/DefaultActions.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/DefaultActions.cs @@ -15,9 +15,11 @@ internal class DefaultActions where TState : TurnState { private readonly ILogger _logger; private readonly bool _enableFeedbackLoop; + private readonly string _feedbackLoopType; - public DefaultActions(bool enableFeedbackLoop = false, ILoggerFactory? loggerFactory = null) + public DefaultActions(bool enableFeedbackLoop = false, string feedbackLoopType = "default", ILoggerFactory? loggerFactory = null) { + _feedbackLoopType = feedbackLoopType; _enableFeedbackLoop = enableFeedbackLoop; _logger = loggerFactory is null ? NullLogger.Instance : loggerFactory.CreateLogger(typeof(DefaultActions)); } @@ -113,7 +115,7 @@ public async Task SayCommandAsync([ActionTurnContext] ITurnContext turnC citations.Add(new ClientCitation() { - Position = $"{i + 1}", + Position = i + 1, Appearance = new ClientCitationAppearance() { Name = citation.Title, @@ -130,10 +132,28 @@ public async Task SayCommandAsync([ActionTurnContext] ITurnContext turnC // If there are citations, filter out the citations unused in content. List? referencedCitations = citations.Count > 0 ? CitationUtils.GetUsedCitations(contentText, citations) : new List(); - object? channelData = isTeamsChannel ? new + object? channelData = null; + if (isTeamsChannel) { - feedbackLoopEnabled = _enableFeedbackLoop - } : null; + if (_enableFeedbackLoop) + { + channelData = new + { + feedbackLoop = new + { + type = _feedbackLoopType + } + }; + } + else + { + channelData = new + { + feedbackLoopEnabled = false + + }; + } + } AIEntity entity = new(); if (referencedCitations != null) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs index 3b07e4a9da..c9e4219750 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs @@ -68,6 +68,7 @@ public class LLMClient private readonly string? _startStreamingMessage; private ResponseReceivedHandler? _endStreamHandler; private bool? _enableFeedbackLoop; + private string? _feedbackLoopType; /// /// Creates a new `LLMClient` instance. @@ -87,6 +88,7 @@ public LLMClient(LLMClientOptions options, ILoggerFactory? loggerFacto this._startStreamingMessage = Options.StartStreamingMessage; this._endStreamHandler = Options.EndStreamHandler; this._enableFeedbackLoop = Options.EnableFeedbackLoop; + this._feedbackLoopType = Options.FeedbackLoopType; } /// @@ -180,6 +182,11 @@ public async Task CompletePromptAsync( if (this._enableFeedbackLoop != null) { streamer.EnableFeedbackLoop = this._enableFeedbackLoop; + + if (streamer.EnableFeedbackLoop == true && this._feedbackLoopType != null) + { + streamer.FeedbackLoopType = this._feedbackLoopType; + } } streamer.EnableGeneratedByAILabel = true; @@ -199,6 +206,12 @@ public async Task CompletePromptAsync( return; } + IList? citations = args.Chunk.delta?.Context?.Citations ?? null; + + if (citations != null) + { + streamer.SetCitations(citations); + } // Ignore content without text // - The chunk is likely for a Tool Call. @@ -210,12 +223,10 @@ public async Task CompletePromptAsync( // Send chunk to client string text = args.Chunk.delta?.GetContent() ?? ""; - IList? citations = args.Chunk.delta?.Context?.Citations ?? null; - if (text.Length > 0) { - streamer.QueueTextChunk(text, citations); + streamer.QueueTextChunk(text); } }); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs index e5e7d7d6a1..5f0a7e5bc1 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs @@ -78,6 +78,11 @@ public class LLMClientOptions /// public bool? EnableFeedbackLoop { get; set; } + /// + /// Optional. Represents the type of feedback loop. It can be set to one of "default" or "custom". + /// + public string? FeedbackLoopType { get; set; } + /// /// Creates an instance of `LLMClientOptions` /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs index e60c1f0bc8..68d8a375e5 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs @@ -40,6 +40,8 @@ public class ActionPlanner : IPlanner where TState : TurnState private bool _enableFeedbackLoop; + private string? _feedbackLoopType; + /// /// Creates a new `ActionPlanner` instance. /// @@ -108,6 +110,10 @@ public async Task ContinueTaskAsync(ITurnContext context, TState state, AI PromptTemplate template = await this.Options.DefaultPrompt(context, state, this); this._enableFeedbackLoop = ai.Options.EnableFeedbackLoop; + if (this._enableFeedbackLoop) + { + this._feedbackLoopType = ai.Options.FeedbackLoopType; + } PromptResponse response = await this.CompletePromptAsync(context, state, template, template.Augmentation, cancellationToken); @@ -182,6 +188,7 @@ public async Task CompletePromptAsync( StartStreamingMessage = this.Options.StartStreamingMessage, EndStreamHandler = this.Options.EndStreamHandler, EnableFeedbackLoop = this._enableFeedbackLoop, + FeedbackLoopType = this._feedbackLoopType }, this._logger); return await client.CompletePromptAsync(context, memory, this.Prompts, cancellationToken); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs index 682d483b6f..305134ca3d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs @@ -31,6 +31,7 @@ public class Application : IBot { private static readonly string CONFIG_FETCH_INVOKE_NAME = "config/fetch"; private static readonly string CONFIG_SUBMIT_INVOKE_NAME = "config/submit"; + private static readonly string MESSAGE_FETCH_TASK_INVOKE_NAME = "message/fetchTask"; private readonly AI? _ai; private readonly BotAdapter? _adapter; @@ -646,6 +647,50 @@ public Application OnConfigFetch(ConfigHandlerAsync handler) return this; } + /// + /// Handles message fetch task events. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnMessageFetchTask(MessageFetchTaskHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, MESSAGE_FETCH_TASK_INVOKE_NAME)); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + MessageFetchTaskResponse response = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(BotAdapter.InvokeResponseKey) != null) + { + return; + } + + TaskModuleResponse result = new TaskModuleResponse(); + if (response.TaskInfo != null) + { + result.Task = new TaskModuleContinueResponse() + { + Value = response.TaskInfo + }; + } else + { + result.Task = new TaskModuleMessageResponse() + { + Value = response.Message ?? string.Empty + }; + } + + + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); + await turnContext.SendActivityAsync(activity, cancellationToken); + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + /// /// Handles config submit events for Microsoft Teams. /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/MessageFetchTaskHandler.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/MessageFetchTaskHandler.cs new file mode 100644 index 0000000000..9fc91d09a9 --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/MessageFetchTaskHandler.cs @@ -0,0 +1,33 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema.Teams; +using Microsoft.Teams.AI.State; + +namespace Microsoft.Teams.AI.Application +{ + /// + /// Function for handling message task fetch events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The data associated with the fetch. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An instance of TaskModuleTaskInfo. + public delegate Task MessageFetchTaskHandler(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Response for the "message/taskFetch" route handler. Only set one or the other, but not both. + /// + public class MessageFetchTaskResponse + { + /// + /// The task module metadata. + /// + public TaskModuleTaskInfo? TaskInfo; + + /// + /// The message to show in the task module. + /// + public string? Message; + } +} \ No newline at end of file diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs index fa675a2808..661eac37d4 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs @@ -41,5 +41,11 @@ public class StreamingChannelData /// [JsonProperty(PropertyName = "feedbackLoopEnabled")] public bool? feedbackLoopEnabled { get; set; } + + /// + /// Represents the type of feedback loop. Set to "default" by default. It can be set to one of "default" or "custom". + /// + [JsonProperty(PropertyName = "feedbackLoopType")] + public string? feedbackLoopType { get; set; } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs index 34d061d850..84e636ba00 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs @@ -41,6 +41,11 @@ public class StreamingResponse /// public bool? EnableFeedbackLoop { get; set; } = false; + /// + /// Represents the type of feedback loop. Set to "default" by default. It can be set to one of "default" or "custom". + /// + public string FeedbackLoopType { get; set; } = "default"; + /// /// Sets the "Generated by AI" label in Teams. /// Defaults to false. @@ -92,6 +97,40 @@ public Task WaitForQueue() return this._queueSync != null ? this._queueSync : Task.CompletedTask; } + /// + /// Sets the citations for the full message. + /// + /// Citations to be included in the message. + public void SetCitations(IList citations) + { + if (citations.Count > 0) + { + if (this.Citations == null) + { + this.Citations = new List(); + } + + int currPos = this.Citations.Count; + + foreach (Citation citation in citations) + { + string abs = CitationUtils.Snippet(citation.Content, 480); + + this.Citations.Add(new ClientCitation() + { + Position = currPos, + Appearance = new ClientCitationAppearance() + { + Name = citation.Title, + Abstract = abs + } + }); + currPos++; + } + + } + } + /// /// Queues an informative update to be sent to the client. /// @@ -131,38 +170,8 @@ public void QueueTextChunk(string text, IList? citations = null) Message += text; - if (citations != null && citations.Count > 0) - { - if (this.Citations == null) - { - this.Citations = new List(); - } - - int currPos = this.Citations.Count; - - foreach (Citation citation in citations) - { - string abs = CitationUtils.Snippet(citation.Content, 480); - - this.Citations.Add(new ClientCitation() - { - Position = $"{currPos}", - Appearance = new ClientCitationAppearance() - { - Name = citation.Title, - Abstract = abs - } - }); - currPos++; - } - - // If there are citations, modify the content so that the sources are numbers instead of [doc1], [doc2], etc. - this.Message = this.Citations.Count == 0 ? this.Message : CitationUtils.FormatCitationsResponse(this.Message); - - // If there are citations, filter out the citations unused in content. - this.Citations = this.Citations.Count > 0 ? CitationUtils.GetUsedCitations(this.Message, this.Citations) : new List(); - - } + // If there are citations, modify the content so that the sources are numbers instead of [doc1], [doc2], etc. + this.Message = CitationUtils.FormatCitationsResponse(this.Message); QueueNextChunk(); } @@ -320,12 +329,34 @@ private async Task SendActivity(Activity activity) } }; + if (this.Citations != null && this.Citations.Count > 0 && !this._ended) + { + // If there are citations, filter out the citations unused in content. + List? currCitations = CitationUtils.GetUsedCitations(this.Message, this.Citations); + AIEntity entity = new AIEntity(); + if (currCitations != null && currCitations.Count > 0) + { + entity.Citation = currCitations; + } + + activity.Entities.Add(entity); + } + // Add in Powered by AI feature flags if (this._ended) { // Add in feedback loop StreamingChannelData currChannelData = activity.GetChannelData(); - currChannelData.feedbackLoopEnabled = EnableFeedbackLoop; + + if (EnableFeedbackLoop == true) + { + currChannelData.feedbackLoopEnabled = true; + currChannelData.feedbackLoopType = FeedbackLoopType; + } + else + { + currChannelData.feedbackLoopEnabled = false; + } activity.ChannelData = currChannelData; // Add in Generated by AI @@ -334,7 +365,11 @@ private async Task SendActivity(Activity activity) AIEntity entity = new AIEntity(); if (this.Citations != null && this.Citations.Count > 0) { - entity.Citation = this.Citations; + List? currCitations = CitationUtils.GetUsedCitations(this.Message, this.Citations); + if (currCitations != null && currCitations.Count > 0) + { + entity.Citation = currCitations; + } } entity.UsageInfo = this.SensitivityLabel; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index 1809bcf6b2..bda0b5c69c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -1,58 +1,58 @@ - - - - netstandard2.0 - latest - enable - enable - Microsoft.Teams.AI - Microsoft Teams AI SDK - 1.9.0 - Microsoft - Microsoft - © Microsoft Corporation. All rights reserved. - SDK focused on building AI based applications for Microsoft Teams. - README.md - https://github.com/microsoft/teams-ai - git - True - - - - - https://github.com/microsoft/teams-ai - https://github-production-user-asset-6210df.s3.amazonaws.com/14900841/240368384-972a9a1b-679a-4725-bfc0-a1e76151a78a.png - MIT - true - bots;ai;teams - - NU5125 - true - true - - - - - - - - - - - - - - - - - - - - - - - + + + + netstandard2.0 + latest + enable + enable + Microsoft.Teams.AI + Microsoft Teams AI SDK + 1.10.0 + Microsoft + Microsoft + © Microsoft Corporation. All rights reserved. + SDK focused on building AI based applications for Microsoft Teams. + README.md + https://github.com/microsoft/teams-ai + git + True + + + + + https://github.com/microsoft/teams-ai + https://github-production-user-asset-6210df.s3.amazonaws.com/14900841/240368384-972a9a1b-679a-4725-bfc0-a1e76151a78a.png + MIT + true + bots;ai;teams + + NU5125 + true + true + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/TypingTimer.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/TypingTimer.cs index 853b73c1bf..6dace6fac9 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/TypingTimer.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/TypingTimer.cs @@ -1,6 +1,8 @@ using Microsoft.Teams.AI.Utilities; using Microsoft.Bot.Schema; using Microsoft.Bot.Builder; +using Microsoft.Identity.Client; +using Microsoft.Teams.AI.Application; namespace Microsoft.Teams.AI { @@ -20,6 +22,11 @@ internal class TypingTimer : IDisposable /// private bool _disposedValue = false; + /// + /// The send "typing" activity task + /// + private Task _lastSend = Task.CompletedTask; + /// /// Constructs a new instance of the class. /// @@ -100,7 +107,8 @@ private async void SendTypingActivity(object state) try { - await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing }); + _lastSend = turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing }); + await _lastSend; if (IsRunning()) { _timer?.Change(_interval, Timeout.Infinite); @@ -115,21 +123,22 @@ private async void SendTypingActivity(object state) } } - private Task StopTimerWhenSendMessageActivityHandlerAsync(ITurnContext turnContext, List activities, Func> next) + private async Task StopTimerWhenSendMessageActivityHandlerAsync(ITurnContext turnContext, List activities, Func> next) { if (_timer != null) { foreach (Activity activity in activities) { - if (activity.Type == ActivityTypes.Message) + if (activity.Type == ActivityTypes.Message || activity.GetChannelData()?.StreamType != null) { + await _lastSend; Dispose(); break; } } } - return next(); + return await next(); } } } diff --git a/dotnet/samples/01.messaging.echoBot/EchoBot.csproj b/dotnet/samples/01.messaging.echoBot/EchoBot.csproj index 8ba0a6dc7d..9844795803 100644 --- a/dotnet/samples/01.messaging.echoBot/EchoBot.csproj +++ b/dotnet/samples/01.messaging.echoBot/EchoBot.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj b/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj index 65eebfb9d9..6f96d79035 100644 --- a/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj +++ b/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj b/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj index 3cab8fadc2..c14631c841 100644 --- a/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj +++ b/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj @@ -11,9 +11,9 @@ - - - + + + diff --git a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj index 5a23842c05..38d432bcd3 100644 --- a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj @@ -11,12 +11,12 @@ - - - + + + - + diff --git a/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj b/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj index 5f07b0e895..28be4ee329 100644 --- a/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj +++ b/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj index 0e58338c3b..d2bb722bfe 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj b/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj index 18cc40f3c7..e794186e86 100644 --- a/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj +++ b/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj index ef01b0fe12..3a153465e4 100644 --- a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj +++ b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj b/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj index 2c53b795b0..3c3cce1269 100644 --- a/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj +++ b/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj index 6287e4799f..cbf1ed91a8 100644 --- a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj @@ -11,12 +11,12 @@ - - - + + + - + diff --git a/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj b/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj index 5e402169ae..f1041051d2 100644 --- a/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj +++ b/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/dotnet/samples/05.chatModeration/ChatModeration.csproj b/dotnet/samples/05.chatModeration/ChatModeration.csproj index 8cc1a1b596..b002ad0deb 100644 --- a/dotnet/samples/05.chatModeration/ChatModeration.csproj +++ b/dotnet/samples/05.chatModeration/ChatModeration.csproj @@ -11,12 +11,12 @@ - - - + + + - + diff --git a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj index a6cbd34522..fa983e8ce7 100644 --- a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj +++ b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj index f0bb03bc97..3c5b0be4d9 100644 --- a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj +++ b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj @@ -13,11 +13,11 @@ - + - + diff --git a/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj b/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj index a5140f9739..9779768686 100644 --- a/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj +++ b/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj @@ -16,9 +16,9 @@ - + - + diff --git a/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj b/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj index a7eb6ec24d..c4da4f41bc 100644 --- a/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj +++ b/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj @@ -13,10 +13,10 @@ - + - + diff --git a/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj b/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj index 25f422de25..ea32fe3069 100644 --- a/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj +++ b/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj @@ -16,9 +16,9 @@ - + - + diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj index 696aa3eba6..2500e9c634 100644 --- a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj @@ -13,10 +13,10 @@ - + - + diff --git a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj index fef679bcfb..053b6fda9b 100644 --- a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj +++ b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj @@ -12,10 +12,10 @@ - - - - + + + + diff --git a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchIndexer/AzureAISearchIndexer.csproj b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchIndexer/AzureAISearchIndexer.csproj index 19c086e725..9e575e6b4a 100644 --- a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchIndexer/AzureAISearchIndexer.csproj +++ b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchIndexer/AzureAISearchIndexer.csproj @@ -7,18 +7,6 @@ enable - - - - - - - PreserveNewest - true - PreserveNewest - - - diff --git a/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj b/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj index 1157e6d6c2..36fc7f8272 100644 --- a/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj +++ b/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj @@ -12,10 +12,10 @@ - - - - + + + +