diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTest.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTest.cs new file mode 100644 index 000000000..23fc9715d --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTest.cs @@ -0,0 +1,29 @@ +using Microsoft.Teams.AI.AI.Models; +using Microsoft.Teams.AI.Tests.TestUtils; +using OpenAI.Assistants; + +namespace Microsoft.Teams.AI.Tests.AITests +{ + public class AssistantsMessageTest + { + [Fact] + public void Test_Constructor() + { + // Arrange + MessageContent content = OpenAIModelFactory.CreateMessageContent("message", "fileId"); + + // Act + AssistantsMessage assistantMessage = new AssistantsMessage(content); + + // Assert + Assert.Equal(assistantMessage.MessageContent, content); + + ChatMessage chatMessage = assistantMessage; + Assert.NotNull(chatMessage); + Assert.Equal(chatMessage.Content, "message"); + Assert.Equal(chatMessage.Context!.Citations[0].Url, "fileId"); + Assert.Equal(chatMessage.Context.Citations[0].Title, ""); + Assert.Equal(chatMessage.Context.Citations[0].Content, ""); + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs index 09cd6ba2d..1c5e6c423 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs @@ -52,6 +52,36 @@ public static ThreadMessage CreateThreadMessage(string threadId, string message) return ModelReaderWriter.Read(BinaryData.FromString(json))!; } + public static MessageContent CreateMessageContent(string message, string fileId) + { + var json = @$"{{ + ""id"": ""test"", + ""thread_id"": ""test"", + ""created_at"": 0, + ""content"": [ + {{ + ""type"": ""text"", + ""text"": {{ + ""value"": ""{message}"", + ""annotations"": [ + {{ + ""type"": ""file_citation"", + ""file_citation"": {{ + ""file_id"": ""{fileId}"" + }} + }} + ] + }} + }} + ] + }}"; + + // Unable to directly read `MessageContent`. + var threadMessage = ModelReaderWriter.Read(BinaryData.FromString(json))!; + + return threadMessage.Content[0]; + } + public static ThreadRun CreateThreadRun(string threadId, string runStatus, string? runId = null, IList requiredActions = null!) { var raJson = "{}"; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/AssistantsMessage.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/AssistantsMessage.cs new file mode 100644 index 000000000..9cc0d8c4a --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/AssistantsMessage.cs @@ -0,0 +1,58 @@ +using OpenAI.Assistants; + +namespace Microsoft.Teams.AI.AI.Models +{ + /// + /// Represents a message returned by the OpenAI Assistants API. + /// + public class AssistantsMessage : ChatMessage + { + /// + /// The message contents from an assistants api response. + /// + public MessageContent MessageContent; + + /// + /// Creates an AssistantMessage. + /// + /// The Assistants API thread message. + public AssistantsMessage(MessageContent content) : base(ChatRole.Assistant) + { + this.MessageContent = content; + + if (content != null) + { + string? textContent = content.Text; + if (content.Text != null && content.Text != string.Empty) + { + this.Content = content.Text; + } + + MessageContext context = new(); + for (int i = 0; i < content.TextAnnotations.Count; i++) + { + TextAnnotation annotation = content.TextAnnotations[i]; + if (annotation?.TextToReplace != null) + { + textContent.Replace(annotation.TextToReplace, $"[{i}]"); + } + + if (annotation?.InputFileId != null) + { + // Retrieve file info object + // Neither `content` or `title` is provided in the annotations + context.Citations.Add(new("", "", annotation.InputFileId)); + } + + if (annotation?.OutputFileId != null) + { + // TODO: Download files or provide link to end user. + // Files were generated by code interpretor tool. + } + } + + Context = context; + } + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs index 1aedb8fa4..708243bb3 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs @@ -2,6 +2,7 @@ using Microsoft.Bot.Builder; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Teams.AI.AI.Models; using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.State; using Microsoft.Teams.AI.Utilities; @@ -52,7 +53,6 @@ public AssistantsPlanner(AssistantsPlannerOptions options, ILoggerFactory? logge }; _logger = loggerFactory == null ? NullLogger.Instance : loggerFactory.CreateLogger>(); _client = _CreateClient(options.ApiKey, options.Endpoint); - } /// @@ -196,10 +196,8 @@ private async Task _GeneratePlanFromMessagesAsync(string threadId, string { foreach (MessageContent content in message.Content) { - if (content.Text != null) - { - plan.Commands.Add(new PredictedSayCommand(content.Text ?? string.Empty)); - } + ChatMessage chatMessage = new AssistantsMessage(content); + plan.Commands.Add(new PredictedSayCommand(chatMessage)); } } return plan;