diff --git a/.gitignore b/.gitignore index f251ef3cc..1f5ef5b87 100644 --- a/.gitignore +++ b/.gitignore @@ -106,4 +106,4 @@ dist .tern-port .DS_Store -**/.vs/ \ No newline at end of file +**/.vs/ diff --git a/dotnet/packages/Microsoft.TeamsAI/.editorconfig b/dotnet/packages/Microsoft.TeamsAI/.editorconfig new file mode 100644 index 000000000..9803dd9aa --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/.editorconfig @@ -0,0 +1,235 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +# OPENAI001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +dotnet_diagnostic.OPENAI001.severity = silent diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.Teams.AI.sln b/dotnet/packages/Microsoft.TeamsAI/Microsoft.Teams.AI.sln index 45114a944..b38b17fc9 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.Teams.AI.sln +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.Teams.AI.sln @@ -7,6 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Teams.AI", "Micro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Teams.AI.Tests", "Microsoft.TeamsAI.Tests\Microsoft.Teams.AI.Tests.csproj", "{7A24063E-21E1-4F01-B849-AD1C5B0C442D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{84A36874-2D2A-4018-96F0-F698F347AD19}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/.editorconfig b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/.editorconfig index 4b6145b1b..403d1b065 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/.editorconfig +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/.editorconfig @@ -13,3 +13,6 @@ dotnet_diagnostic.CS0618.severity = none # IDE0090: Use 'new(...)' dotnet_diagnostic.IDE0090.severity = none + +# OPENAI001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +dotnet_diagnostic.OPENAI001.severity = silent \ No newline at end of file diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AITests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AITests.cs index e3b9e0cae..778de36af 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AITests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AITests.cs @@ -40,7 +40,7 @@ public void Test_RegisterAction_RegisterSameActionTwice() } [Fact] - public async void Test_RegisterAction_OverrideDefaultAction() + public async Task Test_RegisterAction_OverrideDefaultAction() { // Arrange var planner = new TestPlanner(); @@ -68,7 +68,7 @@ public async void Test_RegisterAction_OverrideDefaultAction() } [Fact] - public async void Test_RunAsync() + public async Task Test_RunAsync() { // Arrange var planner = new TurnStatePlanner(); @@ -88,12 +88,12 @@ public async void Test_RunAsync() From = new() { Id = "fromId" }, ChannelId = "channelId" }); - var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContextMock); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContextMock); var actions = new TestActions(); ai.ImportActions(actions); // Act - var result = await ai.RunAsync(turnContextMock, turnState.Result); + var result = await ai.RunAsync(turnContextMock, turnState); // Assert Assert.True(result); @@ -104,7 +104,7 @@ public async void Test_RunAsync() } [Fact] - public async void Test_RunAsync_ExceedStepLimit() + public async Task Test_RunAsync_ExceedStepLimit() { var planner = new TurnStatePlanner(); var moderator = new TurnStateModerator(); @@ -123,14 +123,14 @@ public async void Test_RunAsync_ExceedStepLimit() From = new() { Id = "fromId" }, ChannelId = "channelId" }); - var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContextMock); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContextMock); var actions = new TestActions(); ai.ImportActions(actions); var actionHandler = new TurnStateActionHandler(); ai.RegisterAction(AIConstants.TooManyStepsActionName, actionHandler); // Act - var result = await ai.RunAsync(turnContextMock, turnState.Result, stepCount: 30); + var result = await ai.RunAsync(turnContextMock, turnState, stepCount: 30); // Assert Assert.False(result); @@ -141,7 +141,7 @@ public async void Test_RunAsync_ExceedStepLimit() } [Fact] - public async void Test_RunAsync_ExceedTimeLimit() + public async Task Test_RunAsync_ExceedTimeLimit() { // Arrange var planner = new TurnStatePlanner(); @@ -162,13 +162,13 @@ public async void Test_RunAsync_ExceedTimeLimit() From = new() { Id = "fromId" }, ChannelId = "channelId" }); - var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContextMock); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContextMock); var actions = new TestActions(); ai.ImportActions(actions); ai.RegisterAction(AIConstants.TooManyStepsActionName, new TurnStateActionHandler()); // Act - var result = await ai.RunAsync(turnContextMock, turnState.Result); + var result = await ai.RunAsync(turnContextMock, turnState); // Assert Assert.False(result); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionHandlerTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionHandlerTests.cs index 09f0c509e..3bd9cee75 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionHandlerTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionHandlerTests.cs @@ -103,7 +103,7 @@ private static IActionCollection ImportActions(object instance) return (IActionCollection)actionsField!.GetValue(ai)!; } - private static IEnumerable ParameterAssignTestData() + public static IEnumerable ParameterAssignTestData() { yield return new object[] { diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs index d9efa9da1..a67966f0d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.Teams.AI.Tests.AITests public class ActionPlannerTests { [Fact] - public async void Test_CompletePromptAsync_HasPrompt() + public async Task Test_CompletePromptAsync_HasPrompt() { // Arrange var modelMock = new Mock(); @@ -50,7 +50,7 @@ public async void Test_CompletePromptAsync_HasPrompt() } [Fact] - public async void Test_CompletePromptAsync_DoesNotHavePrompt() + public async Task Test_CompletePromptAsync_DoesNotHavePrompt() { // Arrange var modelMock = new Mock(); @@ -85,7 +85,7 @@ public async void Test_CompletePromptAsync_DoesNotHavePrompt() } [Fact] - public async void Test_ContinueTaskAsync_PromptResponseStatusError() + public async Task Test_ContinueTaskAsync_PromptResponseStatusError() { // Arrange var modelMock = new Mock(); @@ -127,7 +127,7 @@ public async void Test_ContinueTaskAsync_PromptResponseStatusError() } [Fact] - public async void Test_ContinueTaskAsync_PromptResponseStatusError_ErrorNull() + public async Task Test_ContinueTaskAsync_PromptResponseStatusError_ErrorNull() { // Arrange var modelMock = new Mock(); @@ -168,7 +168,7 @@ public async void Test_ContinueTaskAsync_PromptResponseStatusError_ErrorNull() } [Fact] - public async void Test_ContinueTaskAsync_PlanNull() + public async Task Test_ContinueTaskAsync_PlanNull() { // Arrange var modelMock = new Mock(); @@ -224,7 +224,7 @@ public async void Test_ContinueTaskAsync_PlanNull() } [Fact] - public async void Test_ContinueTaskAsync() + public async Task Test_ContinueTaskAsync() { // Arrange var modelMock = new Mock(); @@ -280,7 +280,7 @@ public async void Test_ContinueTaskAsync() } [Fact] - public async void Test_BeginTaskAsync_PromptResponseStatusError() + public async Task Test_BeginTaskAsync_PromptResponseStatusError() { // Arrange var modelMock = new Mock(); @@ -322,7 +322,7 @@ public async void Test_BeginTaskAsync_PromptResponseStatusError() } [Fact] - public async void Test_BeginTaskAsync_PromptResponseStatusError_ErrorNull() + public async Task Test_BeginTaskAsync_PromptResponseStatusError_ErrorNull() { // Arrange var modelMock = new Mock(); @@ -363,7 +363,7 @@ public async void Test_BeginTaskAsync_PromptResponseStatusError_ErrorNull() } [Fact] - public async void Test_BeginTaskAsync_PlanNull() + public async Task Test_BeginTaskAsync_PlanNull() { // Arrange var modelMock = new Mock(); @@ -419,7 +419,7 @@ public async void Test_BeginTaskAsync_PlanNull() } [Fact] - public async void Test_BeginTaskAsync() + public async Task Test_BeginTaskAsync() { // Arrange var modelMock = new Mock(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTest.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTests.cs similarity index 94% rename from dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTest.cs rename to dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTests.cs index 5eb3075dc..9d2cb6595 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTest.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTests.cs @@ -31,8 +31,8 @@ public void Test_Constructor() // Assert Assert.Equal(content, assistantMessage.MessageContent); Assert.Equal("message", assistantMessage.Content); - Assert.Equal(1, assistantMessage.AttachedFiles!.Count); - Assert.Equal("fileId", assistantMessage.AttachedFiles[0].FileInfo.Id); + Assert.Single(assistantMessage.AttachedFiles!); + Assert.Equal("fileId", assistantMessage.AttachedFiles![0].FileInfo.Id); ChatMessage chatMessage = assistantMessage; Assert.NotNull(chatMessage); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/MonologueAugmentationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/MonologueAugmentationTests.cs index 390b510be..9459cc88b 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/MonologueAugmentationTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/MonologueAugmentationTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.Augmentations public class MonologueAugmentationTests { [Fact] - public async void Test_ValidateResponseAsync_ShouldSucceed() + public async Task Test_ValidateResponseAsync_ShouldSucceed() { Mock context = new(); MemoryFork memory = new(); @@ -58,7 +58,7 @@ public async void Test_ValidateResponseAsync_ShouldSucceed() } [Fact] - public async void Test_ValidateResponseAsync_InvalidThoughts_ShouldFail() + public async Task Test_ValidateResponseAsync_InvalidThoughts_ShouldFail() { Mock context = new(); MemoryFork memory = new(); @@ -126,7 +126,7 @@ public async void Test_ValidateResponseAsync_InvalidThoughts_ShouldFail() } [Fact] - public async void Test_ValidateResponseAsync_InvalidAction_ShouldFail() + public async Task Test_ValidateResponseAsync_InvalidAction_ShouldFail() { Mock context = new(); MemoryFork memory = new(); @@ -195,7 +195,7 @@ public async void Test_ValidateResponseAsync_InvalidAction_ShouldFail() } [Fact] - public async void Test_CreatePlanFromResponseAsync_SayCommand_ShouldSucceed() + public async Task Test_CreatePlanFromResponseAsync_SayCommand_ShouldSucceed() { Mock context = new(); MemoryFork memory = new(); @@ -273,7 +273,7 @@ public async void Test_CreatePlanFromResponseAsync_SayCommand_ShouldSucceed() var plan = await augmentation.CreatePlanFromResponseAsync(context.Object, memory, promptResponse); Assert.NotNull(plan); - Assert.Equal(1, plan.Commands.Count); + Assert.Single(plan.Commands); Assert.Equal("SAY", plan.Commands[0].Type); Assert.Equal("hello world", (plan.Commands[0] as PredictedSayCommand)?.Response.Content); Assert.Equal("test intent", (plan.Commands[0] as PredictedSayCommand)?.Response.Context?.Intent); @@ -283,7 +283,7 @@ public async void Test_CreatePlanFromResponseAsync_SayCommand_ShouldSucceed() } [Fact] - public async void Test_CreatePlanFromResponseAsync_DoCommand_ShouldSucceed() + public async Task Test_CreatePlanFromResponseAsync_DoCommand_ShouldSucceed() { Mock context = new(); MemoryFork memory = new(); @@ -353,7 +353,7 @@ public async void Test_CreatePlanFromResponseAsync_DoCommand_ShouldSucceed() var plan = await augmentation.CreatePlanFromResponseAsync(context.Object, memory, promptResponse); Assert.NotNull(plan); - Assert.Equal(1, plan.Commands.Count); + Assert.Single(plan.Commands); Assert.Equal("DO", plan.Commands[0].Type); Assert.Equal("test", (plan.Commands[0] as PredictedDoCommand)?.Action); } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/SequenceAugmentationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/SequenceAugmentationTests.cs index 7ff3eef23..995084c3f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/SequenceAugmentationTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/SequenceAugmentationTests.cs @@ -38,18 +38,18 @@ public async Task Test_CreatePlanFromResponseAsync_ValidPlan_ShouldSucceed() Message = new(ChatRole.Assistant) { Content = @"{ - ""type"": ""plan"", - ""commands"": [ - { - ""type"": ""DO"", - ""action"": ""test"" - }, - { - ""type"": ""SAY"", - ""response"": ""hello"" - } - ] -}", + ""type"": ""plan"", + ""commands"": [ + { + ""type"": ""DO"", + ""action"": ""test"" + }, + { + ""type"": ""SAY"", + ""response"": ""hello"" + } + ] + }", Context = new() { Intent = "test intent", diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/ToolsAugmentationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/ToolsAugmentationTests.cs new file mode 100644 index 000000000..14b02e32d --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Augmentations/ToolsAugmentationTests.cs @@ -0,0 +1,100 @@ +using Microsoft.Bot.Builder; +using Microsoft.Teams.AI.AI.Augmentations; +using Microsoft.Teams.AI.AI.Models; +using Microsoft.Teams.AI.AI.Planners; +using Microsoft.Teams.AI.AI.Prompts; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Tests.TestUtils; + +namespace Microsoft.Teams.AI.Tests.AITests.Augmentations +{ + public class ToolsAugmentationTests + { + [Fact] + public async Task Test_CreatePlanFromResponse_NoActionCalls_CreateSayCommand() + { + // Arrange + ToolsAugmentation augmentation = new ToolsAugmentation(); + TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); + TurnState state = await TurnStateConfig.GetTurnStateWithConversationStateAsync(context); + PromptResponse response = new PromptResponse(); + response.Message = new ChatMessage(ChatRole.Assistant) { Content = "testMessage" }; + + // Act + Plan? plan = await augmentation.CreatePlanFromResponseAsync(context, state, response); + + // Assert + Assert.NotNull(plan); + Assert.Single(plan.Commands); + + var sayCommand = plan.Commands[0] as PredictedSayCommand; + Assert.NotNull(sayCommand); + Assert.Equal("testMessage", sayCommand.Response.Content); + } + + [Fact] + public async Task Test_CreatePlanFromResponse_OneActionCall() + { + // Arrange + ToolsAugmentation augmentation = new ToolsAugmentation(); + TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); + TurnState state = await TurnStateConfig.GetTurnStateWithConversationStateAsync(context); + PromptResponse response = new PromptResponse(); + response.Message = new ChatMessage(ChatRole.Assistant) { + Content = "testMessage", + ActionCalls = new List() { + new ActionCall("id", new ActionFunction("testFunction", "{ \"key\": \"value\" }")) + } + }; + + + // Act + Plan? plan = await augmentation.CreatePlanFromResponseAsync(context, state, response); + + // Assert + Assert.NotNull(plan); + Assert.Single(plan.Commands); + + var doCommand = plan.Commands[0] as PredictedDoCommand; + Assert.NotNull(doCommand); + Assert.Equal("testFunction", doCommand.Action); + Assert.Equal("value", doCommand.Parameters!["key"]); + } + + [Fact] + public async Task Test_CreatePlanFromTesponse_MultipleActionCalls() + { + // Arrange + ToolsAugmentation augmentation = new ToolsAugmentation(); + TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); + TurnState state = await TurnStateConfig.GetTurnStateWithConversationStateAsync(context); + PromptResponse response = new PromptResponse(); + response.Message = new ChatMessage(ChatRole.Assistant) + { + Content = "testMessage", + ActionCalls = new List() { + new ActionCall("id1", new ActionFunction("testFunction1", "{ \"key1\": \"value1\" }")), + new ActionCall("id2", new ActionFunction("testFunction2", "{ \"key2\": \"value2\" }")), + } + }; + + + // Act + Plan? plan = await augmentation.CreatePlanFromResponseAsync(context, state, response); + + // Assert + Assert.NotNull(plan); + Assert.Equal(2, plan.Commands.Count); + + var doCommand1 = plan.Commands[0] as PredictedDoCommand; + Assert.NotNull(doCommand1); + Assert.Equal("testFunction1", doCommand1.Action); + Assert.Equal("value1", doCommand1.Parameters!["key1"]); + + var doCommand2 = plan.Commands[1] as PredictedDoCommand; + Assert.NotNull(doCommand2); + Assert.Equal("testFunction2", doCommand2.Action); + Assert.Equal("value2", doCommand2.Parameters!["key2"]); + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs index 8523cd46b..ed9b794f9 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.Teams.AI.Tests.AITests public class AzureContentSafetyModeratorTests { [Fact] - public async void Test_ReviewPrompt_ThrowsException() + public async Task Test_ReviewPrompt_ThrowsException() { // Arrange var apiKey = "randomApiKey"; @@ -66,7 +66,7 @@ public async void Test_ReviewPrompt_ThrowsException() [InlineData(ModerationType.Input)] [InlineData(ModerationType.Output)] [InlineData(ModerationType.Both)] - public async void Test_ReviewPrompt_Flagged(ModerationType moderate) + public async Task Test_ReviewPrompt_Flagged(ModerationType moderate) { // Arrange var apiKey = "randomApiKey"; @@ -79,7 +79,7 @@ public async void Test_ReviewPrompt_Flagged(ModerationType moderate) Text = "input", }; var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); - var turnStateMock = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var turnStateMock = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); var promptTemplate = new PromptTemplate( "prompt", new(new() { }) @@ -121,7 +121,7 @@ public async void Test_ReviewPrompt_Flagged(ModerationType moderate) }; // Act - var result = await moderator.ReviewInputAsync(turnContext, turnStateMock.Result); + var result = await moderator.ReviewInputAsync(turnContext, turnStateMock); // Assert if (moderate == ModerationType.Input || moderate == ModerationType.Both) @@ -143,20 +143,19 @@ public async void Test_ReviewPrompt_Flagged(ModerationType moderate) [InlineData(ModerationType.Input)] [InlineData(ModerationType.Output)] [InlineData(ModerationType.Both)] - public async void Test_ReviewPrompt_NotFlagged(ModerationType moderate) + public async Task Test_ReviewPrompt_NotFlagged(ModerationType moderate) { // Arrange var apiKey = "randomApiKey"; var endpoint = "https://test.cognitiveservices.azure.com"; var botAdapterMock = new Mock(); - // TODO: when TurnState is implemented, get the user input var activity = new Activity() { Text = "input", }; var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); - var turnStateMock = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var turnStateMock = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); var promptTemplate = new PromptTemplate( "prompt", new(new() { }) @@ -183,14 +182,14 @@ public async void Test_ReviewPrompt_NotFlagged(ModerationType moderate) moderator.GetType().GetField("_client", BindingFlags.Instance | BindingFlags.NonPublic)!.SetValue(moderator, clientMock.Object); // Act - var result = await moderator.ReviewInputAsync(turnContext, turnStateMock.Result); + var result = await moderator.ReviewInputAsync(turnContext, turnStateMock); // Assert Assert.Null(result); } [Fact] - public async void Test_ReviewPlan_ThrowsException() + public async Task Test_ReviewPlan_ThrowsException() { // Arrange var apiKey = "randomApiKey"; @@ -223,14 +222,14 @@ public async void Test_ReviewPlan_ThrowsException() [InlineData(ModerationType.Input)] [InlineData(ModerationType.Output)] [InlineData(ModerationType.Both)] - public async void Test_ReviewPlan_Flagged(ModerationType moderate) + public async Task Test_ReviewPlan_Flagged(ModerationType moderate) { // Arrange var apiKey = "randomApiKey"; var endpoint = "https://test.cognitiveservices.azure.com"; var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); - var turnStateMock = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var turnStateMock = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); var plan = new Plan(new List() { new PredictedDoCommand("action"), @@ -262,7 +261,7 @@ public async void Test_ReviewPlan_Flagged(ModerationType moderate) }; // Act - var result = await moderator.ReviewOutputAsync(turnContext, turnStateMock.Result, plan); + var result = await moderator.ReviewOutputAsync(turnContext, turnStateMock, plan); // Assert if (moderate == ModerationType.Output || moderate == ModerationType.Both) @@ -284,14 +283,14 @@ public async void Test_ReviewPlan_Flagged(ModerationType moderate) [InlineData(ModerationType.Input)] [InlineData(ModerationType.Output)] [InlineData(ModerationType.Both)] - public async void Test_ReviewPlan_NotFlagged(ModerationType moderate) + public async Task Test_ReviewPlan_NotFlagged(ModerationType moderate) { // Arrange var apiKey = "randomApiKey"; var endpoint = "https://test.cognitiveservices.azure.com"; var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); - var turnStateMock = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var turnStateMock = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); var plan = new Plan(new List() { new PredictedDoCommand("action"), @@ -308,7 +307,7 @@ public async void Test_ReviewPlan_NotFlagged(ModerationType moderate) moderator.GetType().GetField("_client", BindingFlags.Instance | BindingFlags.NonPublic)!.SetValue(moderator, clientMock.Object); // Act - var result = await moderator.ReviewOutputAsync(turnContext, turnStateMock.Result, plan); + var result = await moderator.ReviewOutputAsync(turnContext, turnStateMock, plan); // Assert Assert.StrictEqual(plan, result); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs index c0a14397d..5fc35139f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs @@ -67,7 +67,7 @@ public void Test_Initialization_From_OpenAISdk_ChatMessage() var context = message.Context; Assert.NotNull(context); - Assert.Equal(1, context.Citations.Count); + Assert.Single(context.Citations); Assert.Equal("test-title", context.Citations[0].Title); Assert.Equal("test-url", context.Citations[0].Url); Assert.Equal("test-content", context.Citations[0].Content); @@ -160,16 +160,16 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_FunctionCall() } [Fact] - public void Test_AssistantRole_ToOpenAISdkChatMessage_ToolCall() + public void Test_AssistantRole_ToOpenAISdkChatMessage_ActionCall() { // Arrange var chatMessage = new ChatMessage(ChatRole.Assistant) { Content = "test-content", Name = "test-name", - ToolCalls = new List() + ActionCalls = new List() { - new ChatCompletionsFunctionToolCall("test-id", "test-tool-name", "test-tool-arg1") + new ActionCall("test-id", new ActionFunction("test-tool-name", "test-tool-arg1")) } }; @@ -183,7 +183,7 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_ToolCall() // TODO: Uncomment when participant name issue is resolved. //Assert.Equal("test-name", assistantMessage.ParticipantName); - Assert.Equal(1, assistantMessage.ToolCalls.Count); + Assert.Single(assistantMessage.ToolCalls); ChatToolCall toolCall = assistantMessage.ToolCalls[0]; Assert.NotNull(toolCall); Assert.Equal("test-id", toolCall.Id); @@ -239,7 +239,7 @@ public void Test_ToolRole_ToOpenAISdkChatMessage() { Content = "test-content", Name = "tool-name", - ToolCallId = "tool-call-id" + ActionCallId = "action-call-id" }; // Act @@ -249,7 +249,7 @@ public void Test_ToolRole_ToOpenAISdkChatMessage() var toolMessage = result as ToolChatMessage; Assert.NotNull(toolMessage); Assert.Equal("test-content", toolMessage.Content[0].Text); - Assert.Equal("tool-call-id", toolMessage.ToolCallId); + Assert.Equal("action-call-id", toolMessage.ToolCallId); } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/DefaultActionsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/DefaultActionsTests.cs index 9746eb9da..ade32c885 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/DefaultActionsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/DefaultActionsTests.cs @@ -48,7 +48,7 @@ public async Task Test_Execute_UnknownAction() // Assert Assert.Equal(AIConstants.StopCommand, result); - Assert.Equal(1, logs.Count); + Assert.Single(logs); Assert.Equal("An AI action named \"test-action\" was predicted but no handler was registered", logs[0]); } @@ -68,7 +68,7 @@ public async Task Test_Execute_FlaggedInputAction() // Assert Assert.Equal(AIConstants.StopCommand, result); - Assert.Equal(1, logs.Count); + Assert.Single(logs); Assert.Equal("The users input has been moderated but no handler was registered for ___FlaggedInput___", logs[0]); } @@ -88,7 +88,7 @@ public async Task Test_Execute_FlaggedOutputAction() // Assert Assert.Equal(AIConstants.StopCommand, result); - Assert.Equal(1, logs.Count); + Assert.Single(logs); Assert.Equal("The bots output has been moderated but no handler was registered for ___FlaggedOutput___", logs[0]); } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs index ea089bd77..ecb4d2340 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs @@ -52,7 +52,7 @@ public void Test_AddFunctionResultToHistory_MemoryUpdated() Assert.NotNull(history); Assert.Single(history); Assert.Equal(history.First().Role, ChatRole.Function); - Assert.Equal(history.First().Name, "function"); + Assert.Equal("function", history.First().Name); Assert.Equal(history.First().Content, "results"); } @@ -78,7 +78,7 @@ public void Test_AddFunctionResultToHistory_ExceedMaxHistoryMessages() Assert.NotNull(history); Assert.Single(history); Assert.Equal(history.First().Role, ChatRole.Function); - Assert.Equal(history.First().Name, "function-1"); + Assert.Equal("function-1", history.First().Name); Assert.Equal(history.First().Content, "results-1"); } @@ -108,7 +108,7 @@ public async Task Test_CompletePromptAsync_PromptResponse_NotSuccess() Assert.Equal(PromptResponseStatus.Error, response.Status); Assert.NotNull(response.Error); Assert.Equal("test", response.Error.Message); - Assert.Equal(0, memory.Values.Count); + Assert.Empty(memory.Values); } [Fact] @@ -291,7 +291,7 @@ public async Task Test_CompletePromptAsync_PromptResponse_RepairNotSuccess() Assert.Equal(PromptResponseStatus.Error, response.Status); Assert.NotNull(response.Error); Assert.Equal("test", response.Error.Message); - Assert.Equal(1, memory.Values.Count); + Assert.Single(memory.Values); Assert.Equal("hello", memory.Values[options.InputVariable]); } @@ -360,7 +360,7 @@ public async Task Test_CompletePromptAsync_PromptResponse_Repair_ExceedMaxRepair Assert.Equal(PromptResponseStatus.InvalidResponse, response.Status); Assert.NotNull(response.Error); Assert.Equal("Reached max model response repair attempts. Last feedback given to model: \"The response was invalid. Try another strategy.\"", response.Error.Message); - Assert.Equal(1, memory.Values.Count); + Assert.Single(memory.Values); Assert.Equal("hello", memory.Values[options.InputVariable]); } @@ -398,7 +398,7 @@ public async Task Test_CompletePromptAsync_PromptResponse_DisableHistory() Assert.NotNull(response.Message); Assert.Equal(ChatRole.Assistant, response.Message.Role); Assert.Equal("welcome", response.Message.Content); - Assert.Equal(1, memory.Values.Count); + Assert.Empty(memory.Values); } [Fact] @@ -444,7 +444,7 @@ public async Task Test_CompletePromptAsync_PromptResponse_DisableRepair() Assert.NotNull(response.Message); Assert.Equal(ChatRole.Assistant, response.Message.Role); Assert.Equal("welcome", response.Message.Content); - Assert.Equal(1, memory.Values.Count); + Assert.Single(memory.Values); Assert.Equal("hello", memory.Values[options.InputVariable]); } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs index c57957f7e..0455a759e 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs @@ -4,9 +4,10 @@ namespace Microsoft.Teams.AI.Tests.AITests.Models { - internal sealed class ChatCompletionToolCallTests + public sealed class ChatCompletionToolCallTests { [Fact] + [Obsolete] public void Test_ChatCompletionsToolCall_ToFunctionToolCall() { // Arrange @@ -24,6 +25,7 @@ public void Test_ChatCompletionsToolCall_ToFunctionToolCall() } [Fact] + [Obsolete] public void Test_ChatCompletionsToolCall_InvalidToolType() { // Arrange @@ -36,6 +38,7 @@ public void Test_ChatCompletionsToolCall_InvalidToolType() Assert.Equal("Invalid tool type: invalidToolType", ex.Message); } + [Obsolete] private sealed class InvalidToolCall : ChatCompletionsToolCall { public InvalidToolCall() : base("invalidToolType", "test-id") diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs index add2c5288..ac7618e2c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs @@ -101,9 +101,9 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_ToolCall() { Content = "test-content", Name = "test-name", - ToolCalls = new List() + ActionCalls = new List() { - new ChatCompletionsFunctionToolCall("test-id", "test-tool-name", "test-tool-arg1") + new ActionCall("test-id", new ActionFunction("test-tool-name", "test-tool-arg1")) } }; @@ -114,10 +114,8 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_ToolCall() var assistantMessage = result as AssistantChatMessage; Assert.NotNull(assistantMessage); Assert.Equal("test-content", assistantMessage.Content[0].Text); - // TODO: Uncomment when participant name issue is resolved. - //Assert.Equal("test-name", assistantMessage.ParticipantName); - Assert.Equal(1, assistantMessage.ToolCalls.Count); + Assert.Single(assistantMessage.ToolCalls); ChatToolCall toolCall = assistantMessage.ToolCalls[0]; Assert.NotNull(toolCall); Assert.Equal("test-id", toolCall.Id); @@ -173,7 +171,7 @@ public void Test_ToolRole_ToOpenAISdkChatMessage() { Content = "test-content", Name = "tool-name", - ToolCallId = "tool-call-id" + ActionCallId = "tool-call-id" }; // Act @@ -187,6 +185,7 @@ public void Test_ToolRole_ToOpenAISdkChatMessage() } [Fact] + [Obsolete] public void Test_ChatCompletionsToolCall_ToFunctionToolCall() { // Arrange @@ -203,6 +202,7 @@ public void Test_ChatCompletionsToolCall_ToFunctionToolCall() } [Fact] + [Obsolete] public void Test_ChatCompletionsToolCall_InvalidToolType() { // Arrange @@ -215,6 +215,7 @@ public void Test_ChatCompletionsToolCall_InvalidToolType() Assert.Equal("Invalid tool type: invalidToolType", ex.Message); } + [Obsolete] private sealed class InvalidToolCall : ChatCompletionsToolCall { public InvalidToolCall() : base("invalidToolType", "test-id") diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs index dbc104c2c..62c0a8e10 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs @@ -15,6 +15,7 @@ using ChatMessage = Microsoft.Teams.AI.AI.Models.ChatMessage; using ChatRole = Microsoft.Teams.AI.AI.Models.ChatRole; using Azure.Identity; +using Microsoft.Teams.AI.AI.Augmentations; namespace Microsoft.Teams.AI.Tests.AITests.Models { @@ -64,7 +65,7 @@ public void Test_Constructor_AzureOpenAI_ManagedIdentityAuth() } [Fact] - public async void Test_CompletePromptAsync_AzureOpenAI_Chat_PromptTooLong() + public async Task Test_CompletePromptAsync_AzureOpenAI_Chat_PromptTooLong() { // Arrange var turnContextMock = new Mock(); @@ -92,7 +93,7 @@ public async void Test_CompletePromptAsync_AzureOpenAI_Chat_PromptTooLong() } [Fact] - public async void Test_CompletePromptAsync_AzureOpenAI_Chat_RateLimited() + public async Task Test_CompletePromptAsync_AzureOpenAI_Chat_RateLimited() { // Arrange var turnContextMock = new Mock(); @@ -125,7 +126,7 @@ public async void Test_CompletePromptAsync_AzureOpenAI_Chat_RateLimited() } [Fact] - public async void Test_CompletePromptAsync_AzureOpenAI_Chat_RequestFailed() + public async Task Test_CompletePromptAsync_AzureOpenAI_Chat_RequestFailed() { // Arrange var turnContextMock = new Mock(); @@ -154,11 +155,11 @@ public async void Test_CompletePromptAsync_AzureOpenAI_Chat_RequestFailed() // Assert Assert.Equal(PromptResponseStatus.Error, result.Status); Assert.NotNull(result.Error); - Assert.True(result.Error.Message.StartsWith("The chat completion API returned an error status of InternalServerError: Service request failed.\r\nStatus: 500 (exception)")); + Assert.StartsWith("The chat completion API returned an error status of InternalServerError: Service request failed.\r\nStatus: 500 (exception)", result.Error.Message); } [Fact] - public async void Test_CompletePromptAsync_AzureOpenAI_Chat() + public async Task Test_CompletePromptAsync_AzureOpenAI_Chat() { // Arrange var turnContextMock = new Mock(); @@ -207,5 +208,79 @@ public async void Test_CompletePromptAsync_AzureOpenAI_Chat() Assert.Equal("test-choice", result.Message.Content); } + [Fact] + public async Task Test_CompletePromptAsync_AzureOpenAI_Chat_WithTools() + { + // Arrange + var turnContextMock = new Mock(); + var turnStateMock = new Mock(); + var renderedPrompt = new RenderedPromptSection>(new List(), length: 256, tooLong: false); + var promptMock = new Mock(new List(), -1, true, "\n\n"); + promptMock.Setup((prompt) => prompt.RenderAsMessagesAsync( + It.IsAny(), It.IsAny(), It.IsAny>>(), + It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(renderedPrompt); + var promptTemplate = new PromptTemplate("test-prompt", promptMock.Object) + { + Actions = new List() { new ChatCompletionAction() { Name = "testAction" } }, + Augmentation = new ToolsAugmentation(), + Configuration = new PromptTemplateConfiguration() + { + Augmentation = new AugmentationConfiguration() { + Type = AugmentationType.Tools + } + } + }; + var options = new AzureOpenAIModelOptions("test-key", "test-deployment", "https://test.openai.azure.com/") + { + CompletionType = CompletionConfiguration.CompletionType.Chat, + LogRequests = true, + }; + var clientMock = new Mock(); + var chatCompletion = ModelReaderWriter.Read(BinaryData.FromString(@$"{{ + ""choices"": [ + {{ + ""finish_reason"": ""stop"", + ""message"": {{ + ""role"": ""assistant"", + ""content"": null, + ""tool_calls"": [ + {{ + ""id"": ""call_abc123"", + ""type"": ""function"", + ""function"": {{ + ""name"": ""testAction"", + ""arguments"": ""{{}}"" + }} + }} + ] + }} + }} + ] + }}")); + var response = new TestResponse(200, string.Empty); + clientMock.Setup((client) => + client + .GetChatClient(It.IsAny()) + .CompleteChatAsync(It.IsAny>(), It.IsAny(), It.IsAny()) + ).ReturnsAsync(ClientResult.FromValue(chatCompletion!, response)); + + var openAIModel = new OpenAIModel(options, loggerFactory: new TestLoggerFactory()); + openAIModel.GetType().GetField("_openAIClient", BindingFlags.Instance | BindingFlags.NonPublic)!.SetValue(openAIModel, clientMock.Object); + + // Act + var result = await openAIModel.CompletePromptAsync(turnContextMock.Object, turnStateMock.Object, new PromptManager(), new GPTTokenizer(), promptTemplate); + + // Assert + Assert.Equal(PromptResponseStatus.Success, result.Status); + Assert.NotNull(result.Message); + + Assert.NotNull(result.Message.ActionCalls); + Assert.Single(result.Message.ActionCalls); + Assert.Equal("testAction", result.Message.ActionCalls[0].Function.Name); + + Assert.Null(result.Error); + Assert.Equal(ChatRole.Assistant, result.Message.Role); + Assert.Null(result.Message.Content); + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs index 1675cfaae..b7d84bc51 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs @@ -25,7 +25,7 @@ public void Test_Constructor_AzureOpenAI_ManagedIdentityAuth() } [Fact] - public async void Test_OpenAI_CreateEmbeddings_ReturnEmbeddings() + public async Task Test_OpenAI_CreateEmbeddings_ReturnEmbeddings() { // Arrange var apiKey = "randomApiKey"; @@ -59,11 +59,11 @@ public async void Test_OpenAI_CreateEmbeddings_ReturnEmbeddings() // Assert Assert.NotNull(result); Assert.NotNull(result.Output); - Assert.Equal(result.Status, EmbeddingsResponseStatus.Success); + Assert.Equal(EmbeddingsResponseStatus.Success, result.Status); } [Fact] - public async void Test_AzureOpenAI_CreateEmbeddings_ReturnEmbeddings() + public async Task Test_AzureOpenAI_CreateEmbeddings_ReturnEmbeddings() { // Arrange var apiKey = "randomApiKey"; @@ -98,13 +98,13 @@ public async void Test_AzureOpenAI_CreateEmbeddings_ReturnEmbeddings() // Assert Assert.NotNull(result); Assert.NotNull(result.Output); - Assert.Equal(result.Status, EmbeddingsResponseStatus.Success); + Assert.Equal(EmbeddingsResponseStatus.Success, result.Status); } [Theory] [InlineData(429, "too many requests", EmbeddingsResponseStatus.RateLimited)] [InlineData(502, "service error", EmbeddingsResponseStatus.Failure)] - public async void Test_CreateEmbeddings_ThrowClientResultException(int statusCode, string errorMsg, EmbeddingsResponseStatus responseStatus) + public async Task Test_CreateEmbeddings_ThrowClientResultException(int statusCode, string errorMsg, EmbeddingsResponseStatus responseStatus) { // Arrange var apiKey = "randomApiKey"; @@ -133,7 +133,7 @@ public async void Test_CreateEmbeddings_ThrowClientResultException(int statusCod [Fact] [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "")] - public async void Test_CreateEmbeddings_ThrowException() + public async Task Test_CreateEmbeddings_ThrowException() { // Arrange var apiKey = "randomApiKey"; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIModeratorTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIModeratorTests.cs index 92c674b83..279a0e6bf 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIModeratorTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIModeratorTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.Teams.AI.Tests.AITests public class OpenAIModeratorTests { [Fact] - public async void Test_ReviewPrompt_ThrowsException() + public async Task Test_ReviewPrompt_ThrowsException() { // Arrange var apiKey = "randomApiKey"; @@ -60,12 +60,12 @@ public async void Test_ReviewPrompt_ThrowsException() [InlineData(ModerationType.Input)] [InlineData(ModerationType.Output)] [InlineData(ModerationType.Both)] - public async void Test_ReviewPrompt_Flagged(ModerationType moderate) + public async Task Test_ReviewPrompt_Flagged(ModerationType moderate) { // Arrange var apiKey = "randomApiKey"; var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); - var turnStateMock = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var turnStateMock = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); var promptTemplate = new PromptTemplate( "prompt", new(new() { }) @@ -122,7 +122,7 @@ public async void Test_ReviewPrompt_Flagged(ModerationType moderate) moderator.GetType().GetField("_client", BindingFlags.Instance | BindingFlags.NonPublic)!.SetValue(moderator, clientMock.Object); // Act - var result = await moderator.ReviewInputAsync(turnContext, turnStateMock.Result); + var result = await moderator.ReviewInputAsync(turnContext, turnStateMock); // Assert if (moderate == ModerationType.Input || moderate == ModerationType.Both) @@ -141,7 +141,7 @@ public async void Test_ReviewPrompt_Flagged(ModerationType moderate) } [Fact] - public async void Test_ReviewPlan_ThrowsException() + public async Task Test_ReviewPlan_ThrowsException() { // Arrange var apiKey = "randomApiKey"; @@ -173,13 +173,13 @@ public async void Test_ReviewPlan_ThrowsException() [InlineData(ModerationType.Input)] [InlineData(ModerationType.Output)] [InlineData(ModerationType.Both)] - public async void Test_ReviewPlan_Flagged(ModerationType moderate) + public async Task Test_ReviewPlan_Flagged(ModerationType moderate) { // Arrange var apiKey = "randomApiKey"; var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); - var turnStateMock = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var turnStateMock = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); var plan = new Plan(new List() { new PredictedDoCommand("action"), @@ -226,7 +226,7 @@ public async void Test_ReviewPlan_Flagged(ModerationType moderate) moderator.GetType().GetField("_client", BindingFlags.Instance | BindingFlags.NonPublic)!.SetValue(moderator, clientMock.Object); // Act - var result = await moderator.ReviewOutputAsync(turnContext, turnStateMock.Result, plan); + var result = await moderator.ReviewOutputAsync(turnContext, turnStateMock, plan); // Assert if (moderate == ModerationType.Output || moderate == ModerationType.Both) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PlanTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PlanTests.cs index 96c46f9e5..d7ef2e332 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PlanTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PlanTests.cs @@ -30,7 +30,7 @@ public void Test_ToJsonString_Complex() // Arrange Plan plan = new(); plan.Commands.Add(new PredictedSayCommand("Hello")); - plan.Commands.Add(new PredictedDoCommand("DoSomething", new() { { "prop", "value" } })); + plan.Commands.Add(new PredictedDoCommand("DoSomething", new Dictionary() { { "prop", "value" } })); // Note: This is not a formatting error. It is formatted this way to match the expected string. string expectedPlanJson = @"{ diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/PromptManagerTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/PromptManagerTests.cs index a7740282e..8b21366ec 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/PromptManagerTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/PromptManagerTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests public class PromptManagerTests { [Fact] - public async void Test_Functions() + public async Task Test_Functions() { PromptManager manager = new(); Mock context = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/ActionOutputMessageSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/ActionOutputMessageSectionTests.cs new file mode 100644 index 000000000..29191fa3d --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/ActionOutputMessageSectionTests.cs @@ -0,0 +1,103 @@ +using Microsoft.Teams.AI.AI.Models; +using Microsoft.Teams.AI.AI.Prompts.Sections; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Tests.TestUtils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests +{ + public class ActionOutputMessageSectionTests + { + [Fact] + public async Task Test_RenderAsMessages_NoHistory_ReturnsEmptyList() + { + // Arrange + var historyVariable = "temp.history"; + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + turnState.SetValue(historyVariable, new List() { }); + turnState.SetValue(TempState.ActionOutputsKey, new Dictionary()); + var section = new ActionOutputMessageSection(historyVariable); + + // Act + var sections = await section.RenderAsMessagesAsync(turnContext, turnState, null!, null!, 0); + + // Assert + Assert.Empty(sections.Output); + } + + [Fact] + public async Task Test_RenderAsMessages_HistoryWithoutActionCalls_ReturnsEmptyList() + { + // Arrange + var historyVariable = "temp.history"; + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + turnState.SetValue(historyVariable, new List() { new ChatMessage(ChatRole.Assistant) }); + turnState.SetValue(TempState.ActionOutputsKey, new Dictionary()); + var section = new ActionOutputMessageSection(historyVariable); + + // Act + var sections = await section.RenderAsMessagesAsync(turnContext, turnState, null!, null!, 0); + + // Assert + Assert.Empty(sections.Output); + } + + [Fact] + public async Task Test_RenderAsMessages_WithActionCalls_AddsToolMessage() + { + // Arrange + var historyVariable = "temp.history"; + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + turnState.SetValue(historyVariable, new List() { + new ChatMessage(ChatRole.Assistant) { + ActionCalls = new List { new ActionCall("testId", new ActionFunction("testName", "{}")) } + } + }); + turnState.SetValue(TempState.ActionOutputsKey, new Dictionary() + { + { "testId", "testOutput" } + }); + var section = new ActionOutputMessageSection(historyVariable); + + // Act + var sections = await section.RenderAsMessagesAsync(turnContext, turnState, null!, null!, 0); + + // Assert + Assert.Single(sections.Output); + Assert.Equal("testOutput", sections.Output[0].Content); + } + + [Fact] + public async Task Test_RenderAsMessages_WithInvalidActionCalls_AddsToolMessage_WithEmptyStringOutputContent() + { + // Arrange + var historyVariable = "temp.history"; + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + turnState.SetValue(historyVariable, new List() { + new ChatMessage(ChatRole.Assistant) { + ActionCalls = new List { new ActionCall("testId", new ActionFunction("testName", "{}")) } + } + }); + turnState.SetValue(TempState.ActionOutputsKey, new Dictionary() + { + { "InvalidTestId", "testOutput" } + }); + var section = new ActionOutputMessageSection(historyVariable); + + // Act + var sections = await section.RenderAsMessagesAsync(turnContext, turnState, null!, null!, 0); + + // Assert + Assert.Single(sections.Output); + Assert.Equal("", sections.Output[0].Content); + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/ConversationHistorySectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/ConversationHistorySectionTests.cs index a5cd93625..211b7884c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/ConversationHistorySectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/ConversationHistorySectionTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class ConversationHistorySectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRender() + public async Task Test_RenderAsTextAsync_ShouldRender() { // Arrange ConversationHistorySection section = new("history"); @@ -36,7 +36,7 @@ public async void Test_RenderAsTextAsync_ShouldRender() } [Fact] - public async void Test_RenderAsTextAsync_ShouldRenderEmpty() + public async Task Test_RenderAsTextAsync_ShouldRenderEmpty() { // Arrange ConversationHistorySection section = new("history"); @@ -55,7 +55,7 @@ public async void Test_RenderAsTextAsync_ShouldRenderEmpty() [Fact] - public async void Test_RenderAsMessagesAsync_ShoulderRender() + public async Task Test_RenderAsMessagesAsync_ShoulderRender() { // Arrange ConversationHistorySection section = new("history"); @@ -74,9 +74,9 @@ public async void Test_RenderAsMessagesAsync_ShoulderRender() // Assert RenderedPromptSection> rendered = await section.RenderAsMessagesAsync(context.Object, memory, manager, tokenizer, 50); - Assert.Equal("you are a unit test bot", rendered.Output[2].GetContent()); + Assert.Equal("you are a unit test bot", rendered.Output[0].GetContent()); Assert.Equal("hi", rendered.Output[1].GetContent()); - Assert.Equal("hi, how may I assist you?", rendered.Output[0].GetContent()); + Assert.Equal("hi, how may I assist you?", rendered.Output[2].GetContent()); Assert.Equal(15, rendered.Length); } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/DataSourceSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/DataSourceSectionTests.cs index dd202d183..c53811612 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/DataSourceSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/DataSourceSectionTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class DataSourceSectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRender() + public async Task Test_RenderAsTextAsync_ShouldRender() { TextDataSource dataSource = new("test", "my text to use"); DataSourceSection section = new(dataSource); @@ -26,7 +26,7 @@ public async void Test_RenderAsTextAsync_ShouldRender() } [Fact] - public async void Test_RenderAsTextAsync_ShouldTruncate() + public async Task Test_RenderAsTextAsync_ShouldTruncate() { TextDataSource dataSource = new("test", "my text to use"); DataSourceSection section = new(dataSource); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionCallMessageSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionCallMessageSectionTests.cs index 84512206e..0f12a97d0 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionCallMessageSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionCallMessageSectionTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class FunctionCallMessageSectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRender() + public async Task Test_RenderAsTextAsync_ShouldRender() { FunctionCallMessageSection section = new(new("MyFunction", "")); Mock context = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionResponseMessageSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionResponseMessageSectionTests.cs index a6d3ace05..454d77632 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionResponseMessageSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/FunctionResponseMessageSectionTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class FunctionResponseMessageSectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRender() + public async Task Test_RenderAsTextAsync_ShouldRender() { FunctionResponseMessageSection section = new("MyFunction", 27); Mock context = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/GroupSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/GroupSectionTests.cs index 83cc2d1aa..63aa6cf01 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/GroupSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/GroupSectionTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class GroupSectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRender() + public async Task Test_RenderAsTextAsync_ShouldRender() { GroupSection section = new(ChatRole.User, new() { @@ -33,7 +33,7 @@ public async void Test_RenderAsTextAsync_ShouldRender() } [Fact] - public async void Test_RenderAsTextAsync_ShouldTruncate() + public async Task Test_RenderAsTextAsync_ShouldTruncate() { GroupSection section = new(ChatRole.User, new() { diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/PromptSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/PromptSectionTests.cs index f8703e65f..ca2e267e7 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/PromptSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/PromptSectionTests.cs @@ -32,7 +32,7 @@ public string GetMessage(ChatMessage message) public class PromptSectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRender() + public async Task Test_RenderAsTextAsync_ShouldRender() { TestSection section = new(); Mock context = new(); @@ -46,7 +46,7 @@ public async void Test_RenderAsTextAsync_ShouldRender() } [Fact] - public async void Test_RenderAsTextAsync_ShouldTruncate() + public async Task Test_RenderAsTextAsync_ShouldTruncate() { TestSection section = new(8); Mock context = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TemplateSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TemplateSectionTests.cs index 96bf968fd..ee40812b7 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TemplateSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TemplateSectionTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class TemplateSectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRenderWithFunction() + public async Task Test_RenderAsTextAsync_ShouldRenderWithFunction() { TemplateSection section = new("this is a test message: {{getMessage}}", ChatRole.User); Mock context = new(); @@ -31,7 +31,7 @@ public async void Test_RenderAsTextAsync_ShouldRenderWithFunction() } [Fact] - public async void Test_RenderAsTextAsync_ShouldRenderWithFunction_WithWhiteSpace() + public async Task Test_RenderAsTextAsync_ShouldRenderWithFunction_WithWhiteSpace() { TemplateSection section = new("this is a test message: {{ getMessage }}", ChatRole.User); Mock context = new(); @@ -51,7 +51,7 @@ public async void Test_RenderAsTextAsync_ShouldRenderWithFunction_WithWhiteSpace } [Fact] - public async void Test_RenderAsTextAsync_ShouldRenderWithFunctionArgs() + public async Task Test_RenderAsTextAsync_ShouldRenderWithFunctionArgs() { TemplateSection section = new("this is a test message: {{getMessage 'my param'}}", ChatRole.User); Mock context = new(); @@ -71,7 +71,7 @@ public async void Test_RenderAsTextAsync_ShouldRenderWithFunctionArgs() } [Fact] - public async void Test_RenderAsTextAsync_ShouldRenderWithVariable() + public async Task Test_RenderAsTextAsync_ShouldRenderWithVariable() { TemplateSection section = new("this is a test message: {{$message}}", ChatRole.User); Mock context = new(); @@ -88,7 +88,7 @@ public async void Test_RenderAsTextAsync_ShouldRenderWithVariable() } [Fact] - public async void Test_RenderAsTextAsync_ShouldRenderWithVariable_WithWhitespace() + public async Task Test_RenderAsTextAsync_ShouldRenderWithVariable_WithWhitespace() { TemplateSection section = new("this is a test message: {{ $message }}", ChatRole.User); Mock context = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TextSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TextSectionTests.cs index c9ca668ab..2c819c0b7 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TextSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/TextSectionTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class TextSectionTests { [Fact] - public async void Test_RenderAsTextAsync_ShouldRender() + public async Task Test_RenderAsTextAsync_ShouldRender() { TextSection section = new("this is a test section", ChatRole.User); Mock context = new(); @@ -25,7 +25,7 @@ public async void Test_RenderAsTextAsync_ShouldRender() } [Fact] - public async void Test_RenderAsTextAsync_ShouldTruncate() + public async Task Test_RenderAsTextAsync_ShouldTruncate() { TextSection section = new("this is a test section", ChatRole.User); Mock context = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/UserInputMessageSectionTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/UserInputMessageSectionTests.cs index fbe00b619..0a7c59bed 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/UserInputMessageSectionTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/PromptsTests/SectionsTests/UserInputMessageSectionTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.PromptsTests.SectionsTests public class UserInputMessageSectionTest { [Fact] - public async void Test_RenderAsMessagesAsync_ShoulderRender() + public async Task Test_RenderAsMessagesAsync_ShoulderRender() { // Arrange UserInputMessageSection section = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/ActionResponseValidatorTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/ActionResponseValidatorTests.cs index 5e00a3844..23764d3ca 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/ActionResponseValidatorTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/ActionResponseValidatorTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.ValidatorsTests public class ActionResponseValidatorTests { [Fact] - public async void Test_FunctionWithParams_ShouldSucceed() + public async Task Test_FunctionWithParams_ShouldSucceed() { Mock context = new(); MemoryFork memory = new(); @@ -48,7 +48,7 @@ public async void Test_FunctionWithParams_ShouldSucceed() } [Fact] - public async void Test_FunctionWithMissingParams_ShouldFail() + public async Task Test_FunctionWithMissingParams_ShouldFail() { Mock context = new(); MemoryFork memory = new(); @@ -84,7 +84,7 @@ public async void Test_FunctionWithMissingParams_ShouldFail() } [Fact] - public async void Test_FunctionWithInvalidParams_ShouldFail() + public async Task Test_FunctionWithInvalidParams_ShouldFail() { Mock context = new(); MemoryFork memory = new(); @@ -120,7 +120,7 @@ public async void Test_FunctionWithInvalidParams_ShouldFail() } [Fact] - public async void Test_MissingAction_ShouldFail() + public async Task Test_MissingAction_ShouldFail() { Mock context = new(); MemoryFork memory = new(); @@ -156,7 +156,7 @@ public async void Test_MissingAction_ShouldFail() } [Fact] - public async void Test_TextMessageRequired_ShouldFail() + public async Task Test_TextMessageRequired_ShouldFail() { Mock context = new(); MemoryFork memory = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/DefaultResponseValidatorTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/DefaultResponseValidatorTests.cs index 0825046cb..698920164 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/DefaultResponseValidatorTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/DefaultResponseValidatorTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.ValidatorsTests public class DefaultResponseValidatorTests { [Fact] - public async void Test_ShouldSucceed() + public async Task Test_ShouldSucceed() { Mock context = new(); MemoryFork memory = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/JsonResponseValidatorTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/JsonResponseValidatorTests.cs index b70f2e66b..53396bf92 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/JsonResponseValidatorTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ValidatorsTests/JsonResponseValidatorTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Teams.AI.Tests.AITests.ValidatorsTests public class JsonResponseValidatorTests { [Fact] - public async void Test_NoSchema_ShouldSucceed() + public async Task Test_NoSchema_ShouldSucceed() { Mock context = new(); MemoryFork memory = new(); @@ -30,7 +30,7 @@ public async void Test_NoSchema_ShouldSucceed() } [Fact] - public async void Test_WithSchema_ShouldSucceed() + public async Task Test_WithSchema_ShouldSucceed() { JsonSchemaBuilder schema = new JsonSchemaBuilder() .Type(SchemaValueType.Object) @@ -58,7 +58,7 @@ public async void Test_WithSchema_ShouldSucceed() } [Fact] - public async void Test_WithSchema_ShouldFailRequired() + public async Task Test_WithSchema_ShouldFailRequired() { JsonSchemaBuilder schema = new JsonSchemaBuilder() .Type(SchemaValueType.Object) @@ -86,7 +86,7 @@ public async void Test_WithSchema_ShouldFailRequired() } [Fact] - public async void Test_WithSchema_ShouldFailType() + public async Task Test_WithSchema_ShouldFailType() { JsonSchemaBuilder schema = new JsonSchemaBuilder() .Type(SchemaValueType.Object) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs index 70d6a66a5..2f7c097f4 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Teams.AI.Tests.Application public class AdaptiveCardsTests { [Fact] - public async void Test_OnActionExecute_Verb() + public async Task Test_OnActionExecute_Verb() { // Arrange Activity[]? activitiesToSend = null; @@ -65,13 +65,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnActionExecute_Verb_NotHit() + public async Task Test_OnActionExecute_Verb_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -125,7 +125,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnActionExecute_RouteSelector_ActivityNotMatched() + public async Task Test_OnActionExecute_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -163,7 +163,7 @@ public async void Test_OnActionExecute_RouteSelector_ActivityNotMatched() } [Fact] - public async void Test_OnActionSubmit_Verb() + public async Task Test_OnActionSubmit_Verb() { // Arrange var adapter = new SimpleAdapter(); @@ -206,7 +206,7 @@ public async void Test_OnActionSubmit_Verb() } [Fact] - public async void Test_OnActionSubmit_Verb_NotHit() + public async Task Test_OnActionSubmit_Verb_NotHit() { // Arrange var adapter = new SimpleAdapter(); @@ -248,7 +248,7 @@ public async void Test_OnActionSubmit_Verb_NotHit() } [Fact] - public async void Test_OnActionSubmit_RouteSelector_ActivityNotMatched() + public async Task Test_OnActionSubmit_RouteSelector_ActivityNotMatched() { // Arrange var adapter = new SimpleAdapter(); @@ -286,7 +286,7 @@ public async void Test_OnActionSubmit_RouteSelector_ActivityNotMatched() } [Fact] - public async void Test_OnSearch_Dataset() + public async Task Test_OnSearch_Dataset() { // Arrange Activity[]? activitiesToSend = null; @@ -352,13 +352,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnSearch_Dataset_NotHit() + public async Task Test_OnSearch_Dataset_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -414,7 +414,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnSearch_RouteSelector_ActivityNotMatched() + public async Task Test_OnSearch_RouteSelector_ActivityNotMatched() { // Arrange var adapter = new SimpleAdapter(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationBuilderTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationBuilderTests.cs index 85d4fc2fb..c33630a6f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationBuilderTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationBuilderTests.cs @@ -15,19 +15,19 @@ public void Test_ApplicationBuilder_DefaultSetup() var app = new ApplicationBuilder().Build(); // Assert - Assert.NotEqual(null, app.Options); + Assert.NotNull(app.Options); Assert.Null(app.Options.Adapter); Assert.Null(app.Options.BotAppId); Assert.Null(app.Options.Storage); Assert.Null(app.Options.AI); - Assert.NotEqual(null, app.Options.TurnStateFactory); + Assert.NotNull(app.Options.TurnStateFactory); Assert.Null(app.Options.AdaptiveCards); Assert.Null(app.Options.TaskModules); Assert.Null(app.Options.LoggerFactory); Assert.Null(app.Options.Authentication); - Assert.Equal(true, app.Options.RemoveRecipientMention); - Assert.Equal(true, app.Options.StartTypingTimer); - Assert.Equal(false, app.Options.LongRunningMessages); + Assert.True(app.Options.RemoveRecipientMention); + Assert.True(app.Options.StartTypingTimer); + Assert.False(app.Options.LongRunningMessages); } [Fact] 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 944483a48..bfda68552 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationRouteTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationRouteTests.cs @@ -687,7 +687,7 @@ public async Task Test_OnConversationUpdate_ChannelCreated() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -731,7 +731,7 @@ public async Task Test_OnConversationUpdate_ChannelRenamed() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -775,7 +775,7 @@ public async Task Test_OnConversationUpdate_ChannelDeleted() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -820,7 +820,7 @@ public async Task Test_OnConversationUpdate_ChannelRestored() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -863,7 +863,7 @@ public async Task Test_OnConversationUpdate_TeamRenamed() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -906,7 +906,7 @@ public async Task Test_OnConversationUpdate_TeamDeleted() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -949,7 +949,7 @@ public async Task Test_OnConversationUpdate_TeamHardDeleted() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -992,7 +992,7 @@ public async Task Test_OnConversationUpdate_TeamArchived() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -1035,7 +1035,7 @@ public async Task Test_OnConversationUpdate_TeamUnarchived() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -1078,7 +1078,7 @@ public async Task Test_OnConversationUpdate_TeamRestored() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -1120,7 +1120,7 @@ public async Task Test_OnConversationUpdate_UnknownEventName() await app.OnTurnAsync(turnContext); // Assert - Assert.Equal(1, names.Count); + Assert.Single(names); Assert.Equal("1", names[0]); } @@ -1941,7 +1941,7 @@ void CaptureSend(Activity[] arg) Assert.Single(names); Assert.Equal("config/fetch", names[0]); Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } @@ -2033,7 +2033,7 @@ void CaptureSend(Activity[] arg) Assert.Single(names); Assert.Equal("config/submit", names[0]); Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } @@ -2114,7 +2114,7 @@ void CaptureSend(Activity[] arg) Assert.Single(ids); Assert.Equal("test", ids[0]); Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } @@ -2195,7 +2195,7 @@ void CaptureSend(Activity[] arg) Assert.Single(ids); Assert.Equal("test", ids[0]); Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } @@ -2269,7 +2269,7 @@ void CaptureSend(Activity[] arg) Assert.Single(ids); Assert.Equal("test", ids[0]); Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } @@ -2343,7 +2343,7 @@ void CaptureSend(Activity[] arg) Assert.Single(ids); Assert.Equal("test", ids[0]); Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationTests.cs index d317f54b5..1f0a360ee 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/ApplicationTests.cs @@ -23,18 +23,18 @@ public void Test_Application_DefaultSetup() Application app = new(applicationOptions); // Assert - Assert.NotEqual(null, app.Options); + Assert.NotNull(app.Options); Assert.Null(app.Options.Adapter); Assert.Null(app.Options.BotAppId); Assert.Null(app.Options.Storage); Assert.Null(app.Options.AI); - Assert.NotEqual(null, app.Options.TurnStateFactory); + Assert.NotNull(app.Options.TurnStateFactory); Assert.Null(app.Options.AdaptiveCards); Assert.Null(app.Options.TaskModules); Assert.Null(app.Options.LoggerFactory); - Assert.Equal(true, app.Options.RemoveRecipientMention); - Assert.Equal(true, app.Options.StartTypingTimer); - Assert.Equal(false, app.Options.LongRunningMessages); + Assert.True(app.Options.RemoveRecipientMention); + Assert.True(app.Options.StartTypingTimer); + Assert.False(app.Options.LongRunningMessages); } [Fact] @@ -154,7 +154,7 @@ public void Test_StartTypingTimer_MessageActivityType() var timer = app.GetType().GetField("_typingTimer", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(app); Assert.NotNull(timer); - Assert.Equal(timer.GetType(), typeof(TypingTimer)); + Assert.Equal(typeof(TypingTimer), timer.GetType()); } [Fact] @@ -172,7 +172,7 @@ public void Test_StartTypingTimer_MessageActivityType_DoubleStart_DoesNothing() var timer1 = app.GetType().GetField("_typingTimer", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(app); Assert.NotNull(timer1); - Assert.Equal(timer1.GetType(), typeof(TypingTimer)); + Assert.Equal(typeof(TypingTimer), timer1.GetType()); // Act 2 app.StartTypingTimer(turnContext); @@ -181,7 +181,7 @@ public void Test_StartTypingTimer_MessageActivityType_DoubleStart_DoesNothing() var timer2 = app.GetType().GetField("_typingTimer", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(app); Assert.NotNull(timer2); - Assert.Equal(timer2.GetType(), typeof(TypingTimer)); + Assert.Equal(typeof(TypingTimer), timer2.GetType()); Assert.Equal(timer2, timer2); } @@ -487,7 +487,7 @@ public async Task Test_GetTokenOrStartSignInAsync_Error() // Assert Assert.NotNull(authException); - Assert.True(authException.Message.StartsWith("Error occured while trying to authenticate user:")); + Assert.StartsWith("Error occured while trying to authenticate user:", authException.Message); } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthUtilitiesTest.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthUtilitiesTest.cs index 98c830bb2..b89e89cb5 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthUtilitiesTest.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthUtilitiesTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.Teams.AI.Tests.Application.Authentication public class AuthUtilitiesTest { [Fact] - public async void Test_SetTokenInState() + public async Task Test_SetTokenInState() { // Arrange TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); @@ -23,7 +23,7 @@ public async void Test_SetTokenInState() } [Fact] - public async void Test_DeleteTokenFromState() + public async Task Test_DeleteTokenFromState() { // Arrange TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); @@ -40,7 +40,7 @@ public async void Test_DeleteTokenFromState() } [Fact] - public async void Test_UserInSignInFlow() + public async Task Test_UserInSignInFlow() { // Arrange TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); @@ -57,7 +57,7 @@ public async void Test_UserInSignInFlow() } [Fact] - public async void Test_SetUserInSignInFlow() + public async Task Test_SetUserInSignInFlow() { // Arrange TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); @@ -72,7 +72,7 @@ public async void Test_SetUserInSignInFlow() } [Fact] - public async void Test_DeleteUserInSignInFlow() + public async Task Test_DeleteUserInSignInFlow() { // Arrange TurnContext context = TurnStateConfig.CreateConfiguredTurnContext(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthenticationManagerTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthenticationManagerTests.cs index e983b3084..0f0f0d43d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthenticationManagerTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/AuthenticationManagerTests.cs @@ -8,7 +8,7 @@ namespace Microsoft.Teams.AI.Tests.Application.Authentication public class AuthenticationManagerTests { [Fact] - public async void Test_SignIn_DefaultSetting() + public async Task Test_SignIn_DefaultSetting() { // arrange var graphToken = "graph token"; @@ -33,7 +33,7 @@ public async void Test_SignIn_DefaultSetting() } [Fact] - public async void Test_SignIn_SpecificSetting() + public async Task Test_SignIn_SpecificSetting() { // arrange var sharepointToken = "graph token"; @@ -58,7 +58,7 @@ public async void Test_SignIn_SpecificSetting() } [Fact] - public async void Test_SignIn_Pending() + public async Task Test_SignIn_Pending() { var app = new TestApplication(new TestApplicationOptions()); var options = new AuthenticationOptions(); @@ -79,7 +79,7 @@ public async void Test_SignIn_Pending() } [Fact] - public async void Test_SignOut_DefaultHandler() + public async Task Test_SignOut_DefaultHandler() { // arrange var app = new TestApplication(new TestApplicationOptions()); @@ -107,7 +107,7 @@ public async void Test_SignOut_DefaultHandler() } [Fact] - public async void Test_SignOut_SpecificHandler() + public async Task Test_SignOut_SpecificHandler() { // arrange var app = new TestApplication(new TestApplicationOptions()); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/BotAuthenticationBaseTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/BotAuthenticationBaseTests.cs index 0322ca30b..849dcc806 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/BotAuthenticationBaseTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/BotAuthenticationBaseTests.cs @@ -89,7 +89,7 @@ public void Test_IsValidActivity_With_Invalid_Activity() } [Fact] - public async void Test_Authenticate_Pending() + public async Task Test_Authenticate_Pending() { // arrange var app = new Application(new ApplicationOptions()); @@ -103,14 +103,14 @@ public async void Test_Authenticate_Pending() // assert var stateKey = "__fromId:test:Bot:AuthState__"; var authState = state.Conversation[stateKey] as Dictionary; - Assert.Equal(null, token); + Assert.Null(token); Assert.NotNull(authState); Assert.True(authState.ContainsKey("message")); Assert.Equal("test text", authState["message"]); } [Fact] - public async void Test_Authenticate_Complete() + public async Task Test_Authenticate_Complete() { // arrange var app = new Application(new ApplicationOptions()); @@ -126,7 +126,7 @@ public async void Test_Authenticate_Complete() } [Fact] - public async void Test_Authenticate_CompleteWithoutToken() + public async Task Test_Authenticate_CompleteWithoutToken() { // arrange var app = new Application(new ApplicationOptions()); @@ -138,11 +138,11 @@ public async void Test_Authenticate_CompleteWithoutToken() var token = await botAuth.AuthenticateAsync(context, state); // assert - Assert.Equal(null, token); + Assert.Null(token); } [Fact] - public async void Test_HandleSignInActivity_Complete() + public async Task Test_HandleSignInActivity_Complete() { // arrange var app = new Application(new ApplicationOptions()); @@ -172,7 +172,7 @@ public async void Test_HandleSignInActivity_Complete() } [Fact] - public async void Test_HandleSignInActivity_CompleteWithoutToken() + public async Task Test_HandleSignInActivity_CompleteWithoutToken() { // arrange var app = new Application(new ApplicationOptions()); @@ -192,7 +192,7 @@ public async void Test_HandleSignInActivity_CompleteWithoutToken() } [Fact] - public async void Test_HandleSignInActivity_ThrowException() + public async Task Test_HandleSignInActivity_ThrowException() { // arrange var app = new Application(new ApplicationOptions()); @@ -221,7 +221,7 @@ public void Test_SetSettingNameInContextActivityValue_NullContextActivityValue() // assert Assert.NotNull(context.Activity.Value); - Assert.Equal(((JObject)context.Activity.Value).GetValue("settingName")!.ToString(), "settingNameValue"); + Assert.Equal("settingNameValue", ((JObject)context.Activity.Value).GetValue("settingName")!.ToString()); } @@ -239,8 +239,8 @@ public void Test_SetSettingNameInContextActivityValue_ExistingContextActivityVal Assert.NotNull(context.Activity.Value); var v = (JObject)context.Activity.Value; - Assert.Equal(v.GetValue("settingName")!.ToString(), "settingNameValue"); - Assert.Equal(v.GetValue("testKey")!.ToString(), "testValue"); + Assert.Equal("settingNameValue", v.GetValue("settingName")!.ToString()); + Assert.Equal("testValue", v.GetValue("testKey")!.ToString()); } @@ -283,7 +283,7 @@ public void Test_GetSettingNameFromContextActivityValue_ReturnsSettingName() // assert Assert.NotNull(s); - Assert.Equal(s, "settingNameValue"); + Assert.Equal("settingNameValue", s); } private static TurnContext MockTurnContext(string type = ActivityTypes.Message) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs index 65bbce9ac..68d91b6be 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs @@ -35,7 +35,7 @@ public class OAuthBotAuthenticationTests private const string SETTING_NAME = "settingName"; [Fact] - public async void Test_CreateOAuthCard_WithSSOEnabled() + public async Task Test_CreateOAuthCard_WithSSOEnabled() { // Arrange IActivity sentActivity; @@ -64,17 +64,17 @@ public async void Test_CreateOAuthCard_WithSSOEnabled() Assert.Equal(card.ConnectionName, authSettings.ConnectionName); Assert.Equal(card.Buttons[0].Title, authSettings.Title); Assert.Equal(card.Buttons[0].Text, authSettings.Text); - Assert.Equal(card.Buttons[0].Type, "signin"); - Assert.Equal(card.Buttons[0].Value, "signInLink"); + Assert.Equal("signin", card.Buttons[0].Type); + Assert.Equal("signInLink", card.Buttons[0].Value); Assert.NotNull(card.TokenExchangeResource); - Assert.Equal(card.TokenExchangeResource.Id, "id"); - Assert.Equal(card.TokenExchangeResource.Uri, "uri"); + Assert.Equal("id", card.TokenExchangeResource.Id); + Assert.Equal("uri", card.TokenExchangeResource.Uri); Assert.NotNull(card.TokenPostResource); - Assert.Equal(card.TokenPostResource.SasUrl, "sasUrl"); + Assert.Equal("sasUrl", card.TokenPostResource.SasUrl); } [Fact] - public async void Test_CreateOAuthCard_WithoutSSO() + public async Task Test_CreateOAuthCard_WithoutSSO() { // Arrange IActivity sentActivity; @@ -103,15 +103,15 @@ public async void Test_CreateOAuthCard_WithoutSSO() Assert.Equal(card.ConnectionName, authSettings.ConnectionName); Assert.Equal(card.Buttons[0].Title, authSettings.Title); Assert.Equal(card.Buttons[0].Text, authSettings.Text); - Assert.Equal(card.Buttons[0].Type, "signin"); + Assert.Equal("signin", card.Buttons[0].Type); Assert.Equal(card.Buttons[0].Value, "signInLink"); Assert.Null(card.TokenExchangeResource); Assert.NotNull(card.TokenPostResource); - Assert.Equal(card.TokenPostResource.SasUrl, "sasUrl"); + Assert.Equal("sasUrl", card.TokenPostResource.SasUrl); } [Fact] - public async void Test_VerifyStateRouteSelector_ReturnsTrue() + public async Task Test_VerifyStateRouteSelector_ReturnsTrue() { // Arrange var testAdapter = new SimpleAdapter(); @@ -132,7 +132,7 @@ public async void Test_VerifyStateRouteSelector_ReturnsTrue() } [Fact] - public async void Test_VerifyStateRouteSelector_IncorrectActivity_ReturnsFalse() + public async Task Test_VerifyStateRouteSelector_IncorrectActivity_ReturnsFalse() { // Arrange var testAdapter = new SimpleAdapter(); @@ -153,7 +153,7 @@ public async void Test_VerifyStateRouteSelector_IncorrectActivity_ReturnsFalse() } [Fact] - public async void Test_VerifyStateRouteSelector_IncorrectInvokeName_ReturnsFalse() + public async Task Test_VerifyStateRouteSelector_IncorrectInvokeName_ReturnsFalse() { // Arrange var testAdapter = new SimpleAdapter(); @@ -174,7 +174,7 @@ public async void Test_VerifyStateRouteSelector_IncorrectInvokeName_ReturnsFalse } [Fact] - public async void Test_VerifyStateRouteSelector_IncorrectSettingName_ReturnsFalse() + public async Task Test_VerifyStateRouteSelector_IncorrectSettingName_ReturnsFalse() { // Arrange var testAdapter = new SimpleAdapter(); @@ -195,7 +195,7 @@ public async void Test_VerifyStateRouteSelector_IncorrectSettingName_ReturnsFals } [Fact] - public async void Test_TokenExchangeRouteSelector_ReturnsTrue() + public async Task Test_TokenExchangeRouteSelector_ReturnsTrue() { // Arrange var testAdapter = new SimpleAdapter(); @@ -216,7 +216,7 @@ public async void Test_TokenExchangeRouteSelector_ReturnsTrue() } [Fact] - public async void Test_TokenExchangeRouteSelector_IncorrectActivity_ReturnsFalse() + public async Task Test_TokenExchangeRouteSelector_IncorrectActivity_ReturnsFalse() { // Arrange var testAdapter = new SimpleAdapter(); @@ -237,7 +237,7 @@ public async void Test_TokenExchangeRouteSelector_IncorrectActivity_ReturnsFalse } [Fact] - public async void Test_TokenExchangeRouteSelector_IncorrectInvokeName_ReturnsFalse() + public async Task Test_TokenExchangeRouteSelector_IncorrectInvokeName_ReturnsFalse() { // Arrange var testAdapter = new SimpleAdapter(); @@ -258,7 +258,7 @@ public async void Test_TokenExchangeRouteSelector_IncorrectInvokeName_ReturnsFal } [Fact] - public async void Test_TokenExchangeRouteSelector_IncorrectSettingName_ReturnsFalse() + public async Task Test_TokenExchangeRouteSelector_IncorrectSettingName_ReturnsFalse() { // Arrange var testAdapter = new SimpleAdapter(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoBotAuthenticationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoBotAuthenticationTests.cs index 707a9af79..f40fde96a 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoBotAuthenticationTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoBotAuthenticationTests.cs @@ -30,7 +30,7 @@ public async Task TokenExchangeRouteSelectorPublic(ITurnContext context, C [Fact] - public async void Test_RunDialog_BeginNew() + public async Task Test_RunDialog_BeginNew() { // arrange var app = new Application(new ApplicationOptions()); @@ -49,7 +49,7 @@ public async void Test_RunDialog_BeginNew() } [Fact] - public async void Test_RunDialog_ContinueExisting() + public async Task Test_RunDialog_ContinueExisting() { // arrange var app = new Application(new ApplicationOptions()); @@ -71,7 +71,7 @@ public async void Test_RunDialog_ContinueExisting() [Fact] - public async void Test_ContinueDialog() + public async Task Test_ContinueDialog() { // arrange var app = new Application(new ApplicationOptions()); @@ -92,7 +92,7 @@ public async void Test_ContinueDialog() } [Fact] - public async void Test_TokenExchangeRouteSelector_NameMatched() + public async Task Test_TokenExchangeRouteSelector_NameMatched() { // arrange var app = new Application(new ApplicationOptions()); @@ -110,7 +110,7 @@ public async void Test_TokenExchangeRouteSelector_NameMatched() } [Fact] - public async void Test_TokenExchangeRouteSelector_NameNotMatch() + public async Task Test_TokenExchangeRouteSelector_NameNotMatch() { // arrange var app = new Application(new ApplicationOptions()); @@ -128,7 +128,7 @@ public async void Test_TokenExchangeRouteSelector_NameNotMatch() } [Fact] - public async void Test_Dedupe() + public async Task Test_Dedupe() { // arrange var app = new Application(new ApplicationOptions()); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoPromptTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoPromptTests.cs index 4eff52499..a45ec3e5e 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoPromptTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/TeamsSsoPromptTests.cs @@ -47,11 +47,11 @@ await testFlow }) .AssertReply(activity => { - Assert.Equal(1, ((Activity)activity).Attachments.Count); + Assert.Single(((Activity)activity).Attachments); Assert.Equal(OAuthCard.ContentType, ((Activity)activity).Attachments[0].ContentType); OAuthCard? card = ((Activity)activity).Attachments[0].Content as OAuthCard; Assert.NotNull(card); - Assert.Equal(1, card.Buttons.Count); + Assert.Single(card.Buttons); Assert.Equal(ActionTypes.Signin, card!.Buttons[0].Type); Assert.Equal($"{AuthStartPage}?scope={UserReadScope}&clientId={ClientId}&tenantId={TenantId}", card!.Buttons[0].Value); }) @@ -78,11 +78,11 @@ await testFlow }) .AssertReply(activity => { - Assert.Equal(1, ((Activity)activity).Attachments.Count); + Assert.Single(((Activity)activity).Attachments); Assert.Equal(OAuthCard.ContentType, ((Activity)activity).Attachments[0].ContentType); OAuthCard? card = ((Activity)activity).Attachments[0].Content as OAuthCard; Assert.NotNull(card); - Assert.Equal(1, card.Buttons.Count); + Assert.Single(card.Buttons); Assert.Equal(ActionTypes.Signin, card!.Buttons[0].Type); Assert.Equal($"{AuthStartPage}?scope={UserReadScope}&clientId={ClientId}&tenantId={TenantId}", card!.Buttons[0].Value); }) @@ -133,11 +133,11 @@ await testFlow }) .AssertReply(activity => { - Assert.Equal(1, ((Activity)activity).Attachments.Count); + Assert.Single(((Activity)activity).Attachments); Assert.Equal(OAuthCard.ContentType, ((Activity)activity).Attachments[0].ContentType); OAuthCard? card = ((Activity)activity).Attachments[0].Content as OAuthCard; Assert.NotNull(card); - Assert.Equal(1, card.Buttons.Count); + Assert.Single(card.Buttons); Assert.Equal(ActionTypes.Signin, card!.Buttons[0].Type); Assert.Equal($"{AuthStartPage}?scope={UserReadScope}&clientId={ClientId}&tenantId={TenantId}", card!.Buttons[0].Value); }) @@ -179,11 +179,11 @@ await testFlow }) .AssertReply(activity => { - Assert.Equal(1, ((Activity)activity).Attachments.Count); + Assert.Single(((Activity)activity).Attachments); Assert.Equal(OAuthCard.ContentType, ((Activity)activity).Attachments[0].ContentType); OAuthCard? card = ((Activity)activity).Attachments[0].Content as OAuthCard; Assert.NotNull(card); - Assert.Equal(1, card.Buttons.Count); + Assert.Single(card.Buttons); Assert.Equal(ActionTypes.Signin, card!.Buttons[0].Type); Assert.Equal($"{AuthStartPage}?scope={UserReadScope}&clientId={ClientId}&tenantId={TenantId}", card!.Buttons[0].Value); }) @@ -195,11 +195,11 @@ await testFlow }) .AssertReply(activity => { - Assert.Equal(1, ((Activity)activity).Attachments.Count); + Assert.Single(((Activity)activity).Attachments); Assert.Equal(OAuthCard.ContentType, ((Activity)activity).Attachments[0].ContentType); OAuthCard? card = ((Activity)activity).Attachments[0].Content as OAuthCard; Assert.NotNull(card); - Assert.Equal(1, card.Buttons.Count); + Assert.Single(card.Buttons); Assert.Equal(ActionTypes.Signin, card!.Buttons[0].Type); Assert.Equal($"{AuthStartPage}?scope={UserReadScope}&clientId={ClientId}&tenantId={TenantId}", card!.Buttons[0].Value); }) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs index b68ff544f..e2411a1ea 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs @@ -60,7 +60,7 @@ internal sealed class Authentication public class MessageExtensionsAuthenticationBaseTests { [Fact] - public async void Test_Authenticate_TokenExchange_Success() + public async Task Test_Authenticate_TokenExchange_Success() { // arrange var meAuth = new MockedMessageExtensionsAuthentication(tokenExchangeResponse: new TokenResponse(token: "test token")); @@ -81,7 +81,7 @@ public async void Test_Authenticate_TokenExchange_Success() } [Fact] - public async void Test_Authenticate_TokenExchange_Fail() + public async Task Test_Authenticate_TokenExchange_Fail() { // arrange var meAuth = new MockedMessageExtensionsAuthentication(); @@ -104,7 +104,7 @@ void CaptureSend(Activity[] arg) var token = await meAuth.AuthenticateAsync(context); // assert - Assert.Equal(null, token); + Assert.Null(token); Assert.NotNull(activities); var sentActivity = activities.FirstOrDefault(); Assert.NotNull(sentActivity); @@ -113,7 +113,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_Authenticate_UserSignIn_Success() + public async Task Test_Authenticate_UserSignIn_Success() { // arrange var meAuth = new MockedMessageExtensionsAuthentication(signInResponse: new TokenResponse(token: "test token")); @@ -128,7 +128,7 @@ public async void Test_Authenticate_UserSignIn_Success() } [Fact] - public async void Test_Authenticate_TriggerSignin() + public async Task Test_Authenticate_TriggerSignin() { // arrange var meAuth = new MockedMessageExtensionsAuthentication(); @@ -145,7 +145,7 @@ void CaptureSend(Activity[] arg) var token = await meAuth.AuthenticateAsync(context); // assert - Assert.Equal(null, token); + Assert.Null(token); Assert.NotNull(activities); var sentActivity = activities.FirstOrDefault(); Assert.NotNull(sentActivity); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs index e82d81294..2107c3848 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs @@ -8,7 +8,7 @@ namespace Microsoft.Teams.AI.Tests.Application.Authentication public class OAuthAuthenticationTests { [Fact] - public async void Test_IsUserSignedIn_ReturnsTokenString() + public async Task Test_IsUserSignedIn_ReturnsTokenString() { // Arrange var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); @@ -35,7 +35,7 @@ public async void Test_IsUserSignedIn_ReturnsTokenString() } [Fact] - public async void Test_IsUserSignedIn_ReturnsNull() + public async Task Test_IsUserSignedIn_ReturnsNull() { // Arrange var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MeetingsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MeetingsTests.cs index ada88dd30..8abb58e9d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MeetingsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MeetingsTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.Teams.AI.Tests.Application public class MeetingsTests { [Fact] - public async void Test_OnStart() + public async Task Test_OnStart() { // Arrange var adapter = new NotImplementedAdapter(); @@ -41,7 +41,7 @@ public async void Test_OnStart() } [Fact] - public async void Test_OnEnd() + public async Task Test_OnEnd() { // Arrange var adapter = new NotImplementedAdapter(); @@ -73,7 +73,7 @@ public async void Test_OnEnd() } [Fact] - public async void Test_OnParticipantsJoin() + public async Task Test_OnParticipantsJoin() { // Arrange var adapter = new NotImplementedAdapter(); @@ -105,7 +105,7 @@ public async void Test_OnParticipantsJoin() } [Fact] - public async void Test_OnParticipantsLeave() + public async Task Test_OnParticipantsLeave() { // Arrange var adapter = new NotImplementedAdapter(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MessageExtensionsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MessageExtensionsTests.cs index 83920ce3f..7abb89226 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MessageExtensionsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/MessageExtensionsTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.Teams.AI.Tests.Application public class MessageExtensionsTests { [Fact] - public async void Test_OnSubmitAction_CommandId() + public async Task Test_OnSubmitAction_CommandId() { // Arrange Activity[]? activitiesToSend = null; @@ -66,13 +66,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnSubmitAction_CommandId_NotHit() + public async Task Test_OnSubmitAction_CommandId_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -123,7 +123,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnSubmitAction_RouteSelector_ActivityNotMatched() + public async Task Test_OnSubmitAction_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -160,7 +160,7 @@ public async void Test_OnSubmitAction_RouteSelector_ActivityNotMatched() } [Fact] - public async void Test_OnBotMessagePreviewEdit_CommandId() + public async Task Test_OnBotMessagePreviewEdit_CommandId() { // Arrange Activity[]? activitiesToSend = null; @@ -218,13 +218,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnBotMessagePreviewEdit_CommandId_NotHit() + public async Task Test_OnBotMessagePreviewEdit_CommandId_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -279,7 +279,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnBotMessagePreviewEdit_RouteSelector_ActivityNotMatched() + public async Task Test_OnBotMessagePreviewEdit_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -316,7 +316,7 @@ public async void Test_OnBotMessagePreviewEdit_RouteSelector_ActivityNotMatched( } [Fact] - public async void Test_OnBotMessagePreviewSend_CommandId() + public async Task Test_OnBotMessagePreviewSend_CommandId() { // Arrange Activity[]? activitiesToSend = null; @@ -372,13 +372,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnBotMessagePreviewSend_CommandId_NotHit() + public async Task Test_OnBotMessagePreviewSend_CommandId_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -432,7 +432,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnBotMessagePreviewSend_RouteSelector_ActivityNotMatched() + public async Task Test_OnBotMessagePreviewSend_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -468,7 +468,7 @@ public async void Test_OnBotMessagePreviewSend_RouteSelector_ActivityNotMatched( } [Fact] - public async void Test_OnFetchTask_CommandId() + public async Task Test_OnFetchTask_CommandId() { // Arrange Activity[]? activitiesToSend = null; @@ -513,13 +513,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnFetchTask_CommandId_NotHit() + public async Task Test_OnFetchTask_CommandId_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -562,7 +562,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnFetchTask_RouteSelector_ActivityNotMatched() + public async Task Test_OnFetchTask_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -599,7 +599,7 @@ public async void Test_OnFetchTask_RouteSelector_ActivityNotMatched() } [Fact] - public async void Test_OnQuery_CommandId() + public async Task Test_OnQuery_CommandId() { // Arrange Activity[]? activitiesToSend = null; @@ -647,7 +647,7 @@ void CaptureSend(Activity[] arg) }); QueryHandlerAsync handler = (turnContext, turnState, query, cancellationToken) => { - Assert.Equal(1, query.Parameters.Count); + Assert.Single(query.Parameters); Assert.Equal("test-value", query.Parameters["test-name"]); Assert.Equal(10, query.Count); Assert.Equal(0, query.Skip); @@ -660,13 +660,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnQuery_CommandId_NotHit() + public async Task Test_OnQuery_CommandId_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -706,7 +706,7 @@ void CaptureSend(Activity[] arg) }); QueryHandlerAsync handler = (turnContext, turnState, query, cancellationToken) => { - Assert.Equal(1, query.Parameters.Count); + Assert.Single(query.Parameters); Assert.Equal("test-value", query.Parameters["test-name"]); Assert.Equal(10, query.Count); Assert.Equal(0, query.Skip); @@ -722,7 +722,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnQuery_RouteSelector_NotMatched() + public async Task Test_OnQuery_RouteSelector_NotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -759,7 +759,7 @@ public async void Test_OnQuery_RouteSelector_NotMatched() } [Fact] - public async void Test_SelectItem() + public async Task Test_SelectItem() { // Arrange Activity[]? activitiesToSend = null; @@ -804,13 +804,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_SelectItem_NotHit() + public async Task Test_SelectItem_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -858,7 +858,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnQueryLink() + public async Task Test_OnQueryLink() { // Arrange Activity[]? activitiesToSend = null; @@ -907,13 +907,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnQueryLink_NotHit() + public async Task Test_OnQueryLink_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -952,7 +952,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnAnonymousQueryLink() + public async Task Test_OnAnonymousQueryLink() { // Arrange Activity[]? activitiesToSend = null; @@ -1001,13 +1001,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnAnonymousQueryLink_NotHit() + public async Task Test_OnAnonymousQueryLink_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -1046,7 +1046,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnQueryUrlSetting() + public async Task Test_OnQueryUrlSetting() { // Arrange Activity[]? activitiesToSend = null; @@ -1089,13 +1089,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnQueryUrlSetting_NotHit() + public async Task Test_OnQueryUrlSetting_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -1133,7 +1133,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnConfigureSettings() + public async Task Test_OnConfigureSettings() { // Arrange Activity[]? activitiesToSend = null; @@ -1178,13 +1178,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnConfigureSettings_NotHit() + public async Task Test_OnConfigureSettings_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -1221,7 +1221,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnCardButtonClicked() + public async Task Test_OnCardButtonClicked() { // Arrange Activity[]? activitiesToSend = null; @@ -1259,13 +1259,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnCardButtonClicked_NotHit() + public async Task Test_OnCardButtonClicked_NotHit() { // Arrange Activity[]? activitiesToSend = null; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TaskModulesTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TaskModulesTests.cs index c2309bb22..0d703762a 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TaskModulesTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TaskModulesTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Teams.AI.Tests.Application public class TaskModulesTests { [Fact] - public async void Test_OnFetch_Verb() + public async Task Test_OnFetch_Verb() { // Arrange Activity[]? activitiesToSend = null; @@ -64,13 +64,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnFetch_Verb_NotHit() + public async Task Test_OnFetch_Verb_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -120,7 +120,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnFetch_RouteSelector_ActivityNotMatched() + public async Task Test_OnFetch_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -157,7 +157,7 @@ public async void Test_OnFetch_RouteSelector_ActivityNotMatched() } [Fact] - public async void Test_OnSubmit_Verb() + public async Task Test_OnSubmit_Verb() { // Arrange Activity[]? activitiesToSend = null; @@ -209,13 +209,13 @@ void CaptureSend(Activity[] arg) // Assert Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); + Assert.Single(activitiesToSend); Assert.Equal("invokeResponse", activitiesToSend[0].Type); Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); } [Fact] - public async void Test_OnSubmit_Verb_NotHit() + public async Task Test_OnSubmit_Verb_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -266,7 +266,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnSubmit_RouteSelector_ActivityNotMatched() + public async Task Test_OnSubmit_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TeamsAdapterTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TeamsAdapterTests.cs index 58d9149e0..0c47479d5 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TeamsAdapterTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/TeamsAdapterTests.cs @@ -16,7 +16,7 @@ public void Test_TeamsAdapter_HasDefaultHeaders() HttpClient client = adapter.HttpClientFactory.CreateClient(); Assert.NotNull(client); - Assert.True(client.DefaultRequestHeaders.UserAgent.Contains(productInfo)); + Assert.Contains(productInfo, client.DefaultRequestHeaders.UserAgent); } [Fact] @@ -30,7 +30,7 @@ public void Test_TeamsAdapter_NoDuplicateDefaultHeaders() HttpClient client = adapter.HttpClientFactory.CreateClient(); Assert.NotNull(client); - Assert.True(client.DefaultRequestHeaders.UserAgent.Contains(productInfo)); + Assert.Contains(productInfo, client.DefaultRequestHeaders.UserAgent); Assert.True(client.DefaultRequestHeaders.UserAgent.Count == 1); } } 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 30c9baa5e..5431589b8 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 @@ -11,13 +11,13 @@ - + - + diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/StateTests/TurnStateTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/StateTests/TurnStateTests.cs index 47668e244..854fb930e 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/StateTests/TurnStateTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/StateTests/TurnStateTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Teams.AI.Tests.StateTests public class TurnStateTests { [Fact] - public async void Test_LoadState_NoStorageProvided_ShouldCreateDefaultTurnState() + public async Task Test_LoadState_NoStorageProvided_ShouldCreateDefaultTurnState() { // Arrange var state = new TurnState(); @@ -29,7 +29,7 @@ public async void Test_LoadState_NoStorageProvided_ShouldCreateDefaultTurnState( } [Fact] - public async void Test_LoadState_MockStorageProvided_ShouldPopulateTurnState() + public async Task Test_LoadState_MockStorageProvided_ShouldPopulateTurnState() { // Arrange var state = new TurnState(); @@ -70,7 +70,7 @@ public async void Test_LoadState_MockStorageProvided_ShouldPopulateTurnState() } [Fact] - public async void Test_LoadState_MemoryStorageProvided_ShouldPopulateTurnState() + public async Task Test_LoadState_MemoryStorageProvided_ShouldPopulateTurnState() { // Arrange var state = new TurnState(); @@ -107,7 +107,7 @@ public async void Test_LoadState_MemoryStorageProvided_ShouldPopulateTurnState() } [Fact] - public async void Test_LoadState_CustomTurnState_MemoryStorageProvided_ShouldPopulateTurnState() + public async Task Test_LoadState_CustomTurnState_MemoryStorageProvided_ShouldPopulateTurnState() { // Arrange var state = new CustomTurnState(); @@ -144,7 +144,7 @@ public async void Test_LoadState_CustomTurnState_MemoryStorageProvided_ShouldPop } [Fact] - public async void Test_LoadState_CustomTurnState_EmptyMemoryStorageProvided_ShouldPopulateTurnState() + public async Task Test_LoadState_CustomTurnState_EmptyMemoryStorageProvided_ShouldPopulateTurnState() { // Arrange var state = new CustomTurnState(); @@ -162,7 +162,7 @@ public async void Test_LoadState_CustomTurnState_EmptyMemoryStorageProvided_Shou } [Fact] - public async void Test_SaveState_Existing_Loading_Operation() + public async Task Test_SaveState_Existing_Loading_Operation() { // Arrange var state = new TurnState(); @@ -192,12 +192,12 @@ public async void Test_SaveState_Existing_Loading_Operation() } // Assert - Assert.Equal(true, waitedForTaskToComplete); + Assert.True(waitedForTaskToComplete); Assert.True(task.IsCompleted); } [Fact] - public async void Test_SaveState_IsLoaded() + public async Task Test_SaveState_IsLoaded() { // Arrange var state = new TurnState(); @@ -205,11 +205,11 @@ public async void Test_SaveState_IsLoaded() // Act & Assert var exception = await Assert.ThrowsAsync(async () => await state.SaveStateAsync(turnContext, null)); - Assert.True(exception.Message.Contains("TurnState hasn't been loaded.")); + Assert.Contains("TurnState hasn't been loaded.", exception.Message); } [Fact] - public async void Test_SaveState_Does_Not_Save_TempScope() + public async Task Test_SaveState_Does_Not_Save_TempScope() { // Arrange var state = new TurnState(); @@ -227,7 +227,7 @@ public async void Test_SaveState_Does_Not_Save_TempScope() } [Fact] - public async void Test_SaveState_Conversation_State() + public async Task Test_SaveState_Conversation_State() { // Arrange var state = new CustomTurnState(); @@ -251,7 +251,7 @@ public async void Test_SaveState_Conversation_State() } [Fact] - public async void Test_SaveState_User_State() + public async Task Test_SaveState_User_State() { // Arrange var state = new CustomTurnState(); @@ -275,7 +275,7 @@ public async void Test_SaveState_User_State() } [Fact] - public async void Test_SaveState_Entry_Deleted() + public async Task Test_SaveState_Entry_Deleted() { // Arrange var state = new CustomTurnState(); @@ -297,7 +297,7 @@ public async void Test_SaveState_Entry_Deleted() [Fact] - public async void Test_SetValue() + public async Task Test_SetValue() { // Arrange var state = new TurnState(); @@ -316,7 +316,7 @@ public async void Test_SetValue() } [Fact] - public async void Test_SetValue_InvalidScope() + public async Task Test_SetValue_InvalidScope() { // Arrange var state = new TurnState(); @@ -329,7 +329,7 @@ public async void Test_SetValue_InvalidScope() } [Fact] - public async void Test_SetValue_InvalidPath() + public async Task Test_SetValue_InvalidPath() { // Arrange var state = new TurnState(); @@ -342,7 +342,7 @@ public async void Test_SetValue_InvalidPath() } [Fact] - public async void Test_GetValue() + public async Task Test_GetValue() { // Arrange var state = new TurnState(); @@ -361,7 +361,7 @@ public async void Test_GetValue() } [Fact] - public async void Test_HasValue_Returns_True() + public async Task Test_HasValue_Returns_True() { // Arrange var state = new TurnState(); @@ -377,7 +377,7 @@ public async void Test_HasValue_Returns_True() } [Fact] - public async void Test_HasValue_Returns_False() + public async Task Test_HasValue_Returns_False() { // Arrange var state = new TurnState(); @@ -393,7 +393,7 @@ public async void Test_HasValue_Returns_False() } [Fact] - public async void Test_DeleteValue() + public async Task Test_DeleteValue() { // Arrange var state = new TurnState(); @@ -409,7 +409,7 @@ public async void Test_DeleteValue() } [Fact] - public async void Test_DeleteTempState() + public async Task Test_DeleteTempState() { // Arrange var state = new TurnState(); @@ -437,7 +437,7 @@ public void Test_DeleteTempState_Invalid_Scope() } [Fact] - public async void Test_DeleteUserState() + public async Task Test_DeleteUserState() { // Arrange var state = new TurnState(); @@ -465,7 +465,7 @@ public void Test_DeleteUserState_Invalid_Scope() } [Fact] - public async void Test_DeleteConversationState() + public async Task Test_DeleteConversationState() { // Arrange var state = new TurnState(); @@ -506,7 +506,7 @@ public void Test_GetScope_Before_Loading_State() } [Fact] - public async void Test_GetScope_After_Loading_State() + public async Task Test_GetScope_After_Loading_State() { // Arrange var state = new TurnState(); @@ -557,7 +557,7 @@ public void Test_Temp_Before_Loading_State() } [Fact] - public async void Test_Conversation() + public async Task Test_Conversation() { // Arrange var state = new TurnState(); @@ -572,7 +572,7 @@ public async void Test_Conversation() } [Fact] - public async void Test_Temp() + public async Task Test_Temp() { // Arrange var state = new TurnState(); @@ -587,7 +587,7 @@ public async void Test_Temp() } [Fact] - public async void Test_User() + public async Task Test_User() { // Arrange var state = new TurnState(); @@ -638,7 +638,7 @@ public void Test_Set_Temp_Before_Loading_State() } [Fact] - public async void Test_Set_Conversation() + public async Task Test_Set_Conversation() { // Arrange var state = new TurnState(); @@ -653,7 +653,7 @@ public async void Test_Set_Conversation() } [Fact] - public async void Test_Set_Temp() + public async Task Test_Set_Temp() { // Arrange var state = new TurnState(); @@ -668,7 +668,7 @@ public async void Test_Set_Temp() } [Fact] - public async void Test_Set_User() + public async Task Test_Set_User() { // Arrange var state = new TurnState(); 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 aa31ec804..50f84ab84 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs @@ -161,23 +161,62 @@ public TestRequiredAction(string toolCallId, string functionName, string functio } } - internal sealed class TestAsyncPageableCollection : AsyncPageableCollection where T : class + internal sealed class TestAsyncPageCollection : AsyncPageCollection where T : class { public List Items; - internal PipelineResponse _pipelineResponse; + private IAsyncEnumerator> _enumerator; - public TestAsyncPageableCollection(List items, PipelineResponse response) + public TestAsyncPageCollection(List items, PipelineResponse response) { Items = items; _pipelineResponse = response; + _enumerator = new TestAsyncEnumerator(items, response); + } + + protected override IAsyncEnumerator> GetAsyncEnumeratorCore(CancellationToken cancellationToken = default) + { + return _enumerator; + } + + protected override Task> GetCurrentPageAsyncCore() + { + return Task.FromResult(_enumerator.Current); } + } + + internal sealed class TestAsyncEnumerator : IAsyncEnumerator> where T : class + { + private readonly List _items; + private readonly PipelineResponse _pipelineResponse; + private bool _movedOnToNext; -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public override async IAsyncEnumerable> AsPages(string? continuationToken = null, int? pageSizeHint = null) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + public TestAsyncEnumerator(List items, PipelineResponse response) { - yield return ResultPage.Create(Items, null, _pipelineResponse); + _items = items; + _pipelineResponse = response; + _movedOnToNext = false; + } + + public PageResult Current => PageResult.Create(_items, ContinuationToken.FromBytes(BinaryData.FromString("")), null, _pipelineResponse); + + public ValueTask DisposeAsync() + { + return new ValueTask(); + } + + public ValueTask MoveNextAsync() + { + if (!_movedOnToNext) + { + return new ValueTask(true); + } + else + { + _movedOnToNext = true; + return new ValueTask(false); + } + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs index 7cab001fd..19d53a52e 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs @@ -76,7 +76,7 @@ private ThreadMessage _CreateMessage(string threadId, string message) return newMessage; } - public override AsyncPageableCollection GetMessagesAsync(string threadId, ListOrder? resultOrder = null, CancellationToken cancellationToken = default) + public override AsyncPageCollection GetMessagesAsync(string threadId, MessageCollectionOptions options, CancellationToken cancellationToken = default) { while (RemainingMessages.Count > 0) { @@ -86,12 +86,12 @@ public override AsyncPageableCollection GetMessagesAsync(string t // Sorted by oldest first List messages = Messages[threadId].ToList(); - if (resultOrder != null && resultOrder.Value == ListOrder.NewestFirst) + if (options != null && options.Order != null && options.Order.Value == ListOrder.NewestFirst) { messages.Reverse(); } - return new TestAsyncPageableCollection(messages, Mock.Of()); + return new TestAsyncPageCollection(messages, Mock.Of()); } public override Task> CreateRunAsync(string threadId, string assistantId, RunCreationOptions createRunOptions, CancellationToken cancellationToken = default) @@ -152,14 +152,14 @@ public override Task> GetRunAsync(string threadId, strin return runWithUpdatedStatus; } - public override AsyncPageableCollection GetRunsAsync(string threadId, ListOrder? resultOrder = null, CancellationToken cancellationToken = default) + public override AsyncPageCollection GetRunsAsync(string threadId, RunCollectionOptions? options = null, CancellationToken cancellationToken = default) { - AsyncPageableCollection response; + AsyncPageCollection response; // AssistantsPlanner only needs the get the latest. if (Runs[threadId].Count() == 0) { - response = new TestAsyncPageableCollection(new List(), Mock.Of()); + response = new TestAsyncPageCollection(new List(), Mock.Of()); return response; } @@ -167,7 +167,7 @@ public override AsyncPageableCollection GetRunsAsync(string threadId, ThreadRun run = Runs[threadId][lastIndex]; ThreadRun runWithUpdatedStatus = _GetRun(threadId, run.Id)!; - response = new TestAsyncPageableCollection(new List() { runWithUpdatedStatus }, Mock.Of()); + response = new TestAsyncPageCollection(new List() { runWithUpdatedStatus }, Mock.Of()); return response; } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TypingTimerTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TypingTimerTests.cs index e9e48c1ae..fa5227736 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TypingTimerTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TypingTimerTests.cs @@ -155,7 +155,7 @@ public void Dispose_ShouldResetProperties() Assert.False(typingTimer.IsRunning()); } - private static IEnumerable ExceptionTestData() + public static IEnumerable ExceptionTestData() { yield return new[] { diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/UtilitiesTests/ResponseJsonParsersTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/UtilitiesTests/ResponseJsonParsersTests.cs index 95403328f..21408f39c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/UtilitiesTests/ResponseJsonParsersTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/UtilitiesTests/ResponseJsonParsersTests.cs @@ -10,7 +10,7 @@ public void ParseJson_Simple() { var obj = ResponseJsonParsers.ParseJSON("{ \"foo\": \"bar\" }"); Assert.NotNull(obj); - Assert.Equal(1, obj.Count()); + Assert.Single(obj); Assert.Equal("foo", obj.First().Key); Assert.Equal("bar", obj.First().Value.ToString()); } @@ -20,7 +20,7 @@ public void ParseJson_MultiLineObject_ShouldParse() { var obj = ResponseJsonParsers.ParseJSON("{ \n\"foo\": \"bar\"\n }"); Assert.NotNull(obj); - Assert.Equal(1, obj.Count()); + Assert.Single(obj); Assert.Equal("foo", obj.First().Key); Assert.Equal("bar", obj.First().Value.ToString()); } @@ -30,7 +30,7 @@ public void ParseJson_EscapedQuotes_ShouldParse() { var obj = ResponseJsonParsers.ParseJSON("{ \"foo\": \"bar\\\"baz\" }"); Assert.NotNull(obj); - Assert.Equal(1, obj.Count()); + Assert.Single(obj); Assert.Equal("foo", obj.First().Key); Assert.Equal("bar\"baz", obj.First().Value.ToString()); } @@ -40,7 +40,7 @@ public void ParseJson_EscapedBackSlashes_ShouldParse() { var obj = ResponseJsonParsers.ParseJSON("{ \"foo\": \"bar\\\\\"baz\" }"); Assert.NotNull(obj); - Assert.Equal(1, obj.Count()); + Assert.Single(obj); Assert.Equal("foo", obj.First().Key); Assert.Equal("bar\"baz", obj.First().Value.ToString()); } @@ -50,7 +50,7 @@ public void ParseJson_EscapedSlashes_ShouldParse() { var obj = ResponseJsonParsers.ParseJSON("{ \"foo\": \"bar\\/baz\" }"); Assert.NotNull(obj); - Assert.Equal(1, obj.Count()); + Assert.Single(obj); Assert.Equal("foo", obj.First().Key); Assert.Equal("bar/baz", obj.First().Value.ToString()); } @@ -60,7 +60,7 @@ public void ParseJson_EmbeddedObject_ShouldParse() { var obj = ResponseJsonParsers.ParseJSON("Hello { \"foo\": \"bar\" } World!"); Assert.NotNull(obj); - Assert.Equal(1, obj.Count()); + Assert.Single(obj); Assert.Equal("foo", obj.First().Key); Assert.Equal("bar", obj.First().Value.ToString()); } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig new file mode 100644 index 000000000..5e51edaf1 --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS0618: Type or member is obsolete +dotnet_diagnostic.CS0618.severity = silent diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs index 26b242699..ddd224933 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs @@ -209,10 +209,6 @@ public async Task RunAsync(ITurnContext turnContext, TState turnState, Dat // Initialize start time startTime = startTime ?? DateTime.UtcNow; - - // Populate {{$temp.input}} - _SetTempStateValues(turnState, turnContext); - Plan? plan = null; // Review input on first loop @@ -262,8 +258,10 @@ await _actions[AIConstants.TooManyStepsActionName] } string output; - if (command is PredictedDoCommand doCommand) + PredictedDoCommand? doCommand = null; + if (command is PredictedDoCommand) { + doCommand = (PredictedDoCommand)command; if (_actions.ContainsAction(doCommand.Action)) { DoCommandActionData data = new() @@ -276,10 +274,15 @@ await _actions[AIConstants.TooManyStepsActionName] output = await this._actions[AIConstants.DoCommandActionName] .Handler .PerformActionAsync(turnContext, turnState, data, doCommand.Action, cancellationToken); - shouldLoop = output.Length > 0; - if (turnState.Temp != null) + if (doCommand.ActionId != null) { + shouldLoop = true; + turnState.Temp.ActionOutputs[doCommand.ActionId] = output; + } + else + { + shouldLoop = output.Length > 0; turnState.Temp.ActionOutputs[doCommand.Action] = output; } } @@ -311,8 +314,16 @@ await _actions[AIConstants.TooManyStepsActionName] } // Copy the actions output to the input - turnState.Temp!.Input = output; turnState.Temp.InputFiles = new(); + + if (doCommand != null && doCommand.ActionId != null) + { + turnState.DeleteValue("temp.input"); + } + else + { + turnState.Temp!.Input = output; + } } // Check for looping diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Augmentations/AugmentationType.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Augmentations/AugmentationType.cs index f8d265140..9609c9f52 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Augmentations/AugmentationType.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Augmentations/AugmentationType.cs @@ -18,6 +18,11 @@ public enum AugmentationType /// /// Monologue /// - Monologue + Monologue, + + /// + /// Tools + /// + Tools } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Augmentations/ToolsAugmentation.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Augmentations/ToolsAugmentation.cs new file mode 100644 index 000000000..deb6bf4a1 --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Augmentations/ToolsAugmentation.cs @@ -0,0 +1,75 @@ +using Microsoft.Bot.Builder; +using Microsoft.Teams.AI.AI.Models; +using Microsoft.Teams.AI.AI.Planners; +using Microsoft.Teams.AI.AI.Prompts; +using Microsoft.Teams.AI.AI.Prompts.Sections; +using Microsoft.Teams.AI.AI.Tokenizers; +using Microsoft.Teams.AI.AI.Validators; +using Microsoft.Teams.AI.State; + +namespace Microsoft.Teams.AI.AI.Augmentations +{ + /// + /// A server-side 'tools' augmentation + /// + public class ToolsAugmentation : IAugmentation + { + /// + /// Creates a plan given validated response value. + /// + /// Context for the current turn of conversation. + /// Interface for accessing state variables. + /// Response to validate. + /// The cancellation token. + /// The created plan + public Task CreatePlanFromResponseAsync(ITurnContext context, IMemory memory, PromptResponse response, CancellationToken cancellationToken = default) + { + List commands = new(); + + if (response.Message != null && response.Message.ActionCalls != null && response.Message.ActionCalls.Count() > 0) + { + IList actionCalls = response.Message.ActionCalls; + + foreach (ActionCall actionCall in actionCalls) + { + PredictedDoCommand command = new(actionCall.Function!.Name, actionCall.Function.Arguments) + { + ActionId = actionCall.Id, + }; + commands.Add(command); + } + + return Task.FromResult(new Plan(commands)); + } + + PredictedSayCommand sayCommand = new(response.Message!); + commands.Add(sayCommand); + + return Task.FromResult(new Plan(commands)); + } + + /// + /// Creates an optional prompt section for the augmentation. + /// + /// + public PromptSection? CreatePromptSection() + { + return null; + } + + /// + /// Validates a response to a prompt. + /// + /// Context for the current turn of conversation. + /// Interface for accessing state variables. + /// Tokenizer to use for encoding/decoding text. + /// Response to validate. + /// Number of remaining attempts to validate the response. + /// The cancellation token. + /// A Validation object. + public Task ValidateResponseAsync(ITurnContext context, IMemory memory, ITokenizer tokenizer, PromptResponse response, int remainingAttempts, CancellationToken cancellationToken = default) + { + return Task.FromResult(new Validation() { Valid = true }); + } + } +} 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 58ed3f5d7..8b76b4144 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs @@ -160,14 +160,14 @@ public async Task CompletePromptAsync( return response; } - // Get input message + // Get input message/s string inputVariable = Options.InputVariable; - ChatMessage? inputMsg = response.Input; - if (inputMsg == null) + IList? inputMsgs = response.Input; + if (inputMsgs == null) { object? content = memory.GetValue(inputVariable); - inputMsg = new ChatMessage(ChatRole.User); - inputMsg.Content = content; + inputMsgs = new List() { new ChatMessage(ChatRole.User) }; + inputMsgs[0].Content = content; } Validation validation = await this.Options.Validator.ValidateResponseAsync( @@ -186,11 +186,11 @@ public async Task CompletePromptAsync( response.Message.Content = validation.Value.ToString(); } - this.AddInputToHistory(memory, this.Options.HistoryVariable, inputMsg); + this.AddMessagesToHistory(memory, this.Options.HistoryVariable, inputMsgs); if (response.Message != null) { - this.AddOutputToHistory(memory, this.Options.HistoryVariable, response.Message); + this.AddMessageToHistory(memory, this.Options.HistoryVariable, response.Message); } return response; @@ -226,11 +226,11 @@ public async Task CompletePromptAsync( if (repairResponse.Status == PromptResponseStatus.Success) { - this.AddInputToHistory(memory, this.Options.HistoryVariable, inputMsg); + this.AddMessagesToHistory(memory, this.Options.HistoryVariable, inputMsgs); if (repairResponse.Message != null) { - this.AddOutputToHistory(memory, this.Options.HistoryVariable, repairResponse.Message); + this.AddMessageToHistory(memory, this.Options.HistoryVariable, repairResponse.Message); } } @@ -246,44 +246,6 @@ public async Task CompletePromptAsync( } } - private void AddInputToHistory(IMemory memory, string variable, ChatMessage input) - { - if (variable == null) - { - return; - } - - List history = (List?)memory.GetValue(variable) ?? new() { }; - - history.Insert(0, input); - - if (history.Count > this.Options.MaxHistoryMessages) - { - history.RemoveAt(history.Count - 1); - } - - memory.SetValue(variable, history); - } - - private void AddOutputToHistory(IMemory memory, string variable, ChatMessage message) - { - if (variable == string.Empty) - { - return; - } - - List history = (List?)memory.GetValue(variable) ?? new() { }; - - history.Insert(0, message); - - if (history.Count > this.Options.MaxHistoryMessages) - { - history.RemoveAt(history.Count - 1); - } - - memory.SetValue(variable, history); - } - private async Task RepairResponseAsync( ITurnContext context, MemoryFork fork, @@ -296,11 +258,11 @@ CancellationToken cancellationToken { string feedback = validation.Feedback ?? "The response was invalid. Try another strategy."; - this.AddInputToHistory(fork, $"{this.Options.HistoryVariable}-repair", new(ChatRole.User) { Content = feedback }); + this.AddMessageToHistory(fork, $"{this.Options.HistoryVariable}-repair", new(ChatRole.User) { Content = feedback }); if (response.Message != null) { - this.AddOutputToHistory(fork, $"{this.Options.HistoryVariable}-repair", response.Message); + this.AddMessageToHistory(fork, $"{this.Options.HistoryVariable}-repair", response.Message); } PromptTemplate repairTemplate = new(this.Options.Template); @@ -361,5 +323,38 @@ CancellationToken cancellationToken return await this.RepairResponseAsync(context, fork, functions, repairResponse, repairValidation, remainingAttempts, cancellationToken); } + + private void AddMessagesToHistory(IMemory memory, string variable, IEnumerable messages) + { + if (variable == string.Empty || variable == null) + { + return; + } + + List history = (List?)memory.GetValue(variable) ?? new(); + + history.AddRange(messages); + + if (history.Count > this.Options.MaxHistoryMessages) + { + int overflow = history.Count - this.Options.MaxHistoryMessages; + history.RemoveRange(0, overflow); + } + + // Remove completed partial action outputs + while (history.Count > 0 && history[0].Role == ChatRole.Tool) + { + history.RemoveAt(0); + } + + + memory.SetValue(variable, history); + } + + + private void AddMessageToHistory(IMemory memory, string variable, ChatMessage message) + { + AddMessagesToHistory(memory, variable, new List() { message }); + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionAction.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionAction.cs index a52a466da..35de6b14d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionAction.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionAction.cs @@ -1,4 +1,6 @@ using Json.Schema; +using OpenAI.Chat; +using System.Text.Json; using System.Text.Json.Serialization; namespace Microsoft.Teams.AI.AI.Models @@ -8,6 +10,8 @@ namespace Microsoft.Teams.AI.AI.Models /// public class ChatCompletionAction { + private static JsonSerializerOptions _serializerOptions = new(); + /// /// Name of the action to be called. /// @@ -73,5 +77,19 @@ public ChatCompletionAction(string name, string description, JsonSchema paramete this.Description = description; this.Parameters = parameters; } + + /// + /// Maps to an instance of . + /// + internal ChatTool ToChatTool() + { + // Empty json object string + string parameters = "{}"; + if (Parameters != null) + { + parameters = JsonSerializer.Serialize(this.Parameters, _serializerOptions); + } + return ChatTool.CreateFunctionTool(this.Name, this.Description, BinaryData.FromString(parameters)); + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs index 2bb8ac0eb..80152ef5e 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs @@ -7,6 +7,7 @@ namespace Microsoft.Teams.AI.AI.Models /// /// Abstract class representing a tool call in OpenAI's Chat Completion API. /// + [Obsolete] public abstract class ChatCompletionsToolCall { /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionsFunctionToolCall.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionsFunctionToolCall.cs index 6c80b06e7..35dad331d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionsFunctionToolCall.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionsFunctionToolCall.cs @@ -5,6 +5,7 @@ namespace Microsoft.Teams.AI.AI.Models /// /// Represents a function tool call in a chat message. /// + [Obsolete] public class ChatCompletionsFunctionToolCall : ChatCompletionsToolCall { /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs index b67b10f42..de994d6fa 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs @@ -3,7 +3,10 @@ using Microsoft.Bot.Schema; using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.Utilities; +using Microsoft.Teams.AI.Utilities.JsonConverters; using OpenAI.Chat; +using System.Text; +using System.Text.Json.Serialization; using OAI = OpenAI; namespace Microsoft.Teams.AI.AI.Models @@ -11,6 +14,7 @@ namespace Microsoft.Teams.AI.AI.Models /// /// Represents a message that will be passed to the Chat Completions API /// + [JsonConverter(typeof(ChatMessageJsonConverter))] public class ChatMessage { /// @@ -33,28 +37,45 @@ public class ChatMessage /// /// The name and arguments of a function that should be called, as generated by the model. /// + [Obsolete("Use ActionCalls instead")] public FunctionCall? FunctionCall { get; set; } /// /// The ID of the tool call resolved by the provided content. `toolCallId` is required if role is `tool`. /// + [Obsolete("Use ActionCalldId instead.")] public string? ToolCallId { get; set; } /// /// The context used for this message. /// + [JsonPropertyName("context")] public MessageContext? Context { get; set; } /// /// The tool calls generated by the model, such as function calls. /// + [Obsolete("Use ActionCalls instead")] public IList? ToolCalls { get; set; } /// /// Attachments for the bot to send back. /// + [JsonPropertyName("attachments")] public List? Attachments { get; set; } + /// + /// The tool calls generated by the model, such as function calls. + /// + [JsonPropertyName("actionCalls")] + public List? ActionCalls { get; set; } + + /// + /// The ID of the tool call resolved by the provided content. `toolCallId` is required if role is `tool`. + /// + [JsonPropertyName("actionCallId")] + public string? ActionCallId { get; set; } + /// /// Gets the content with the given type. /// Will throw an exception if the content is not of the given type. @@ -81,7 +102,12 @@ public ChatMessage(ChatRole role) internal ChatMessage(ChatCompletion chatCompletion) { this.Role = ChatRole.Assistant; - this.Content = chatCompletion.Content[0].Text; + + // If finish reason is `toolCall` then there won't be any content. + if (chatCompletion.Content.Count() > 0) + { + this.Content = chatCompletion.Content[0].Text; + } if (chatCompletion.FunctionCall != null && chatCompletion.FunctionCall.FunctionName != string.Empty) { @@ -91,10 +117,11 @@ internal ChatMessage(ChatCompletion chatCompletion) if (chatCompletion.ToolCalls != null && chatCompletion.ToolCalls.Count > 0) { - this.ToolCalls = new List(); + // Note: Replaced `ToolCalls` field. + this.ActionCalls = new List(); foreach (ChatToolCall toolCall in chatCompletion.ToolCalls) { - this.ToolCalls.Add(ChatCompletionsToolCall.FromChatToolCall(toolCall)); + this.ActionCalls.Add(new ActionCall(toolCall)); } } @@ -113,7 +140,6 @@ internal ChatMessage(ChatCompletion chatCompletion) internal OAI.Chat.ChatMessage ToOpenAIChatMessage() { - Verify.NotNull(this.Content); Verify.NotNull(this.Role); ChatRole role = this.Role; @@ -121,11 +147,13 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() string? content = null; List contentItems = new(); + StringBuilder textContentBuilder = new(); // Content is a text - if (this.Content is string textContent) + if (this.Content is string) { - content = textContent; + content = (string)this.Content; + textContentBuilder.AppendLine(content); } else if (this.Content is IEnumerable contentParts) { @@ -135,6 +163,7 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() if (contentPart is TextContentPart textPart) { contentItems.Add(ChatMessageContentPart.CreateTextMessageContentPart(textPart.Text)); + textContentBuilder.AppendLine(textPart.Text); } else if (contentPart is ImageContentPart imagePart) { @@ -142,6 +171,9 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() } } } + // else if content is null, then it must be a tool message. + + string textContent = textContentBuilder.ToString().Trim(); // Different roles map to different classes if (role == ChatRole.User) @@ -159,7 +191,7 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() if (this.Name != null) { // TODO: Currently no way to set `ParticipantName` come and change it eventually. - //userMessage.ParticipantName = this.Name; + // userMessage.ParticipantName = this.Name; } message = userMessage; @@ -172,20 +204,20 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() if (this.FunctionCall != null) { ChatFunctionCall functionCall = new(this.FunctionCall.Name ?? "", this.FunctionCall.Arguments ?? ""); - assistantMessage = new AssistantChatMessage(functionCall, this.GetContent()); + assistantMessage = new AssistantChatMessage(functionCall, textContent); } - else if (this.ToolCalls != null) + else if (this.ActionCalls != null) { List toolCalls = new(); - foreach (ChatCompletionsToolCall toolCall in this.ToolCalls) + foreach (ActionCall actionCall in this.ActionCalls) { - toolCalls.Add(toolCall.ToChatToolCall()); + toolCalls.Add(actionCall.ToChatToolCall()); } - assistantMessage = new AssistantChatMessage(toolCalls, this.GetContent()); + assistantMessage = new AssistantChatMessage(toolCalls, textContent); } else { - assistantMessage = new AssistantChatMessage(this.GetContent()); + assistantMessage = new AssistantChatMessage(textContent); } if (this.Name != null) @@ -199,7 +231,7 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() if (role == ChatRole.System) { - SystemChatMessage systemMessage = new(this.GetContent()); + SystemChatMessage systemMessage = new(textContent); if (this.Name != null) { @@ -214,14 +246,14 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() { // TODO: Clean up #pragma warning disable CS0618 // Type or member is obsolete - message = new FunctionChatMessage(this.Name ?? "", this.GetContent()); + message = new FunctionChatMessage(this.Name ?? "", textContent); #pragma warning restore CS0618 // Type or member is obsolete } if (role == ChatRole.Tool) { - message = new ToolChatMessage(this.ToolCallId ?? "", this.GetContent()); + message = new ToolChatMessage(this.ActionCallId ?? "", textContent); } if (message == null) @@ -236,6 +268,7 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() /// /// The name and arguments of a function that should be called, as generated by the model. /// + [Obsolete("Deprecated for ActionCall")] public class FunctionCall { /// @@ -263,6 +296,119 @@ public FunctionCall(string name, string arguments) } } + /// + /// Action called by the model. + /// + public class ActionCall + { + /// + /// The ID of the action call. + /// + [JsonPropertyName("id")] + public string? Id { get; set; } + + /// + /// The function that the model called. + /// + [JsonPropertyName("function")] + public ActionFunction? Function { get; set; } + + + /// + /// The type of the action. Currently, only "function" is supported. + /// + [JsonPropertyName("type")] + public string Type { get; } = ActionCallType.Function; + + /// + /// Creates an instance of + /// + /// + /// + public ActionCall(string id, ActionFunction function) + { + Id = id; + Function = function; + } + + /// + /// Creates an instance of . + /// + /// Used to create the object when deserializing. + /// + [JsonConstructor] + internal ActionCall() {} + + /// + /// Creates an instance of from + /// + /// + /// Thrown if `toolCall` has an invalid type + public ActionCall(ChatToolCall toolCall) + { + if (toolCall.Kind != ChatToolCallKind.Function) + { + throw new TeamsAIException($"Invalid ActionCall type: {toolCall.GetType().Name}"); + } + + Id = toolCall.Id; + Function = new ActionFunction(toolCall.FunctionName, toolCall.FunctionArguments); + } + + internal ChatToolCall ToChatToolCall() + { + if (this.Type == ActionCallType.Function) + { + return ChatToolCall.CreateFunctionToolCall(Id, Function!.Name, Function.Arguments); + } + + throw new TeamsAIException($"Invalid tool type: {this.Type}"); + } + } + + /// + /// Represents an ActionCall type + /// + public class ActionCallType + { + /// + /// Function action call type + /// + public static string Function { get; } = "function"; + } + + /// + /// Function details associated with an action called by a model. + /// + public class ActionFunction + { + /// + /// The name of the action to call. + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// The arguments to call the function with, as generated by the model in JSON format. + /// Note that the model does not always generate valid JSON, and may hallucinate parameters + /// not defined by your function schema. Validate the arguments in your code before calling + /// your function. + /// + [JsonPropertyName("arguments")] + public string Arguments { get; set; } = string.Empty; + + /// + /// Creates an instance of `ActionFunction`. + /// + /// action name + /// function arguments + public ActionFunction(string name, string arguments) + { + this.Name = name; + this.Arguments = arguments; + } + } + /// /// Represents the ChatMessage content. /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs index 3a3a779ec..43937307f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs @@ -18,6 +18,7 @@ using ServiceVersion = Azure.AI.OpenAI.AzureOpenAIClientOptions.ServiceVersion; using Azure.AI.OpenAI.Chat; using OpenAI.Chat; +using Microsoft.Recognizers.Text.NumberWithUnit.Dutch; namespace Microsoft.Teams.AI.AI.Models { @@ -138,134 +139,176 @@ public OpenAIModel(AzureOpenAIModelOptions options, ILoggerFactory? loggerFactor /// public async Task CompletePromptAsync(ITurnContext turnContext, IMemory memory, IPromptFunctions> promptFunctions, ITokenizer tokenizer, PromptTemplate promptTemplate, CancellationToken cancellationToken = default) { + if (_options.CompletionType != CompletionType.Chat) + { + throw new TeamsAIException("The legacy completion endpoint has been deprecated, please use the chat completions endpoint instead"); + } + DateTime startTime = DateTime.UtcNow; - int maxInputTokens = promptTemplate.Configuration.Completion.MaxInputTokens; + CompletionConfiguration completion = promptTemplate.Configuration.Completion; + int maxInputTokens = completion.MaxInputTokens; + // Setup tools if enabled + bool isToolsAugmentation = promptTemplate.Configuration.Augmentation.Type == Augmentations.AugmentationType.Tools; + List tools = new(); - if (_options.CompletionType == CompletionType.Chat) + // If tools is enabled, reformat actions to schema + if (isToolsAugmentation && promptTemplate.Actions.Count > 0) { - // Render prompt - RenderedPromptSection> prompt = await promptTemplate.Prompt.RenderAsMessagesAsync(turnContext, memory, promptFunctions, tokenizer, maxInputTokens, cancellationToken); - if (prompt.TooLong) - { - return new PromptResponse - { - Status = PromptResponseStatus.TooLong, - Error = new($"The generated chat completion prompt had a length of {prompt.Length} tokens which exceeded the MaxInputTokens of {maxInputTokens}.") - }; - } - if (!_options.UseSystemMessages!.Value && prompt.Output.Count > 0 && prompt.Output[0].Role == ChatRole.System) - { - prompt.Output[0].Role = ChatRole.User; - } - if (_options.LogRequests!.Value) + foreach (ChatCompletionAction action in promptTemplate.Actions) { - // TODO: Colorize - _logger.LogTrace("CHAT PROMPT:"); - _logger.LogTrace(JsonSerializer.Serialize(prompt.Output, _serializerOptions)); + tools.Add(action.ToChatTool()); } + } - // Get input message - // - we're doing this here because the input message can be complex and include images. - ChatMessage? input = null; - int last = prompt.Output.Count - 1; - if (last >= 0 && prompt.Output[last].Role == "user") + // Render prompt + RenderedPromptSection> prompt = await promptTemplate.Prompt.RenderAsMessagesAsync(turnContext, memory, promptFunctions, tokenizer, maxInputTokens, cancellationToken); + if (prompt.TooLong) + { + return new PromptResponse { - input = prompt.Output[last]; - } + Status = PromptResponseStatus.TooLong, + Error = new($"The generated chat completion prompt had a length of {prompt.Length} tokens which exceeded the MaxInputTokens of {maxInputTokens}.") + }; + } - // Call chat completion API - IEnumerable chatMessages = prompt.Output.Select(chatMessage => chatMessage.ToOpenAIChatMessage()); + if (!_options.UseSystemMessages!.Value && prompt.Output.Count > 0 && prompt.Output[0].Role == ChatRole.System) + { + prompt.Output[0].Role = ChatRole.User; + } + + if (_options.LogRequests!.Value) + { + _logger.LogTrace("CHAT PROMPT:"); + _logger.LogTrace(JsonSerializer.Serialize(prompt.Output, _serializerOptions)); + } - ChatCompletionOptions? chatCompletionOptions = ModelReaderWriter.Read(BinaryData.FromString($@"{{ - ""max_tokens"": {promptTemplate.Configuration.Completion.MaxTokens}, - ""temperature"": {(float)promptTemplate.Configuration.Completion.Temperature}, - ""top_p"": {(float)promptTemplate.Configuration.Completion.TopP}, - ""presence_penalty"": {(float)promptTemplate.Configuration.Completion.PresencePenalty}, - ""frequency_penalty"": {(float)promptTemplate.Configuration.Completion.FrequencyPenalty} - }}")); + // Render prompt template + IEnumerable chatMessages = prompt.Output.Select(chatMessage => chatMessage.ToOpenAIChatMessage()); - if (chatCompletionOptions == null) + ChatCompletionOptions chatCompletionOptions = new() + { + MaxTokens = completion.MaxTokens, + Temperature = (float)completion.Temperature, + TopP = (float)completion.TopP, + PresencePenalty = (float)completion.PresencePenalty, + FrequencyPenalty = (float)completion.FrequencyPenalty, + }; + + if (isToolsAugmentation) + { + chatCompletionOptions.ToolChoice = completion.GetOpenAIChatToolChoice(); + chatCompletionOptions.ParallelToolCallsEnabled = completion.ParallelToolCalls; + } + + foreach (ChatTool tool in tools) + { + chatCompletionOptions.Tools.Add(tool); + } + + + if (chatCompletionOptions == null) + { + throw new TeamsAIException("Failed to create chat completions options"); + } + + IDictionary? additionalData = promptTemplate.Configuration.Completion.AdditionalData; + if (_useAzure) + { + AddAzureChatExtensionConfigurations(chatCompletionOptions, additionalData); + } + + string model = promptTemplate.Configuration.Completion.Model ?? _deploymentName; + + PipelineResponse? rawResponse; + ClientResult? chatCompletionsResponse = null; + PromptResponse promptResponse = new(); + try + { + chatCompletionsResponse = await _openAIClient.GetChatClient(model).CompleteChatAsync(chatMessages, chatCompletionOptions, cancellationToken); + rawResponse = chatCompletionsResponse.GetRawResponse(); + promptResponse.Status = PromptResponseStatus.Success; + } + catch (ClientResultException e) + { + rawResponse = e.GetRawResponse(); + HttpOperationException httpOperationException = new(e); + if (httpOperationException.StatusCode == (HttpStatusCode)429) { - throw new TeamsAIException("Failed to create chat completions options"); + promptResponse.Status = PromptResponseStatus.RateLimited; + promptResponse.Error = new("The chat completion API returned a rate limit error."); } - - // TODO: Use this once setters are added for the following fields in `OpenAI` package. - //OAIChat.ChatCompletionOptions chatCompletionsOptions = new() - //{ - // MaxTokens = maxInputTokens, - // Temperature = (float)promptTemplate.Configuration.Completion.Temperature, - // TopP = (float)promptTemplate.Configuration.Completion.TopP, - // PresencePenalty = (float)promptTemplate.Configuration.Completion.PresencePenalty, - // FrequencyPenalty = (float)promptTemplate.Configuration.Completion.FrequencyPenalty, - //}; - - // TODO: Use this once setters are added for the following fields in `OpenAI` package. - //OAIChat.ChatCompletionOptions chatCompletionsOptions = new() - //{ - // MaxTokens = maxInputTokens, - // Temperature = (float)promptTemplate.Configuration.Completion.Temperature, - // TopP = (float)promptTemplate.Configuration.Completion.TopP, - // PresencePenalty = (float)promptTemplate.Configuration.Completion.PresencePenalty, - // FrequencyPenalty = (float)promptTemplate.Configuration.Completion.FrequencyPenalty, - //}; - - IDictionary? additionalData = promptTemplate.Configuration.Completion.AdditionalData; - if (_useAzure) + else { - AddAzureChatExtensionConfigurations(chatCompletionOptions, additionalData); + promptResponse.Status = PromptResponseStatus.Error; + promptResponse.Error = new($"The chat completion API returned an error status of {httpOperationException.StatusCode}: {httpOperationException.Message}"); } + } - PipelineResponse? rawResponse; - ClientResult? chatCompletionsResponse = null; - PromptResponse promptResponse = new(); - try + if (_options.LogRequests!.Value) + { + // TODO: Colorize + _logger.LogTrace("RESPONSE:"); + _logger.LogTrace($"status {rawResponse!.Status}"); + _logger.LogTrace($"duration {(DateTime.UtcNow - startTime).TotalMilliseconds} ms"); + if (promptResponse.Status == PromptResponseStatus.Success) { - chatCompletionsResponse = await _openAIClient.GetChatClient(_deploymentName).CompleteChatAsync(chatMessages, chatCompletionOptions, cancellationToken); - rawResponse = chatCompletionsResponse.GetRawResponse(); - promptResponse.Status = PromptResponseStatus.Success; - promptResponse.Message = new ChatMessage(chatCompletionsResponse.Value); - promptResponse.Input = input; + _logger.LogTrace(JsonSerializer.Serialize(chatCompletionsResponse!.Value, _serializerOptions)); } - catch (ClientResultException e) + if (promptResponse.Status == PromptResponseStatus.RateLimited) { - // TODO: Verify if RequestFailedException is thrown when request fails. - rawResponse = e.GetRawResponse(); - HttpOperationException httpOperationException = new(e); - if (httpOperationException.StatusCode == (HttpStatusCode)429) - { - promptResponse.Status = PromptResponseStatus.RateLimited; - promptResponse.Error = new("The chat completion API returned a rate limit error."); - } - else - { - promptResponse.Status = PromptResponseStatus.Error; - promptResponse.Error = new($"The chat completion API returned an error status of {httpOperationException.StatusCode}: {httpOperationException.Message}"); - } + _logger.LogTrace("HEADERS:"); + _logger.LogTrace(JsonSerializer.Serialize(rawResponse.Headers, _serializerOptions)); } + } + + // Returns if the unsuccessful response + if (promptResponse.Status != PromptResponseStatus.Success || chatCompletionsResponse == null) + { + return promptResponse; + } - if (_options.LogRequests!.Value) + // Process response + ChatCompletion chatCompletion = chatCompletionsResponse.Value; + List actionCalls = new(); + IReadOnlyList toolsCalls = chatCompletion.ToolCalls; + if (isToolsAugmentation && toolsCalls.Count > 0) + { + foreach(ChatToolCall toolCall in toolsCalls) { - // TODO: Colorize - _logger.LogTrace("RESPONSE:"); - _logger.LogTrace($"status {rawResponse!.Status}"); - _logger.LogTrace($"duration {(DateTime.UtcNow - startTime).TotalMilliseconds} ms"); - if (promptResponse.Status == PromptResponseStatus.Success) - { - _logger.LogTrace(JsonSerializer.Serialize(chatCompletionsResponse!.Value, _serializerOptions)); - } - if (promptResponse.Status == PromptResponseStatus.RateLimited) - { - _logger.LogTrace("HEADERS:"); - _logger.LogTrace(JsonSerializer.Serialize(rawResponse.Headers, _serializerOptions)); - } + actionCalls.Add(new ActionCall(toolCall)); } - return promptResponse; } - else + + List? inputs = new(); + int lastMessage = prompt.Output.Count - 1; + + // Skips the first message which is the prompt + if (lastMessage > 0 && prompt.Output[lastMessage].Role != ChatRole.Assistant) { - throw new TeamsAIException("The legacy completion endpoint has been deprecated, please use the chat completions endpoint instead"); + inputs.Add(prompt.Output.ElementAt(lastMessage)); + + // Add remaining parallel tools calls + if (inputs[0].Role == ChatRole.Tool) + { + int i; + for (i = prompt.Output.Count - 1; i >= 0; i--) + { + if (prompt.Output[i].ActionCalls != null && prompt.Output[i].ActionCalls!.Count > 0) + { + break; + } + } + int firstMessage = i+1; + inputs = prompt.Output.GetRange(firstMessage, prompt.Output.Count - firstMessage); + } } + + promptResponse.Input = inputs; + promptResponse.Message = new ChatMessage(chatCompletionsResponse.Value); + + return promptResponse; + } private ServiceVersion? ConvertStringToServiceVersion(string apiVersion) 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 c08696cb8..d352c3c26 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs @@ -189,14 +189,14 @@ private async Task _BlockOnInProgressRunsAsync(string threadId, CancellationToke // Loop until the last run is completed while (true) { - AsyncPageableCollection? runs = _client.GetRunsAsync(threadId, ListOrder.NewestFirst, cancellationToken); + AsyncPageCollection? runs = _client.GetRunsAsync(threadId, new() { Order = ListOrder.NewestFirst }, cancellationToken); if (runs == null) { return; } - ThreadRun? run = runs.GetAsyncEnumerator().Current; + ThreadRun? run = runs.GetAllValuesAsync().GetAsyncEnumerator().Current; if (run == null || _IsRunCompleted(run)) { return; @@ -210,9 +210,9 @@ private async Task _BlockOnInProgressRunsAsync(string threadId, CancellationToke private async Task _GeneratePlanFromMessagesAsync(string threadId, string lastMessageId, CancellationToken cancellationToken) { // Find the new messages - AsyncPageableCollection messages = _client.GetMessagesAsync(threadId, ListOrder.NewestFirst, cancellationToken); + AsyncPageCollection messages = _client.GetMessagesAsync(threadId, new() { Order = ListOrder.NewestFirst }, cancellationToken); List newMessages = new(); - await foreach (ThreadMessage message in messages) + await foreach (ThreadMessage message in messages.GetAllValuesAsync()) { if (string.Equals(message.Id, lastMessageId)) { diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/PredictedDoCommand.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/PredictedDoCommand.cs index c29b1767f..144c41102 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/PredictedDoCommand.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/PredictedDoCommand.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; using Json.Schema; using Microsoft.Teams.AI.Utilities.JsonConverters; @@ -27,6 +28,14 @@ public class PredictedDoCommand : IPredictedCommand [JsonConverter(typeof(DictionaryJsonConverter))] public Dictionary? Parameters { get; set; } + /// + /// The id mapped to the named action that the AI system should perform. + /// + [JsonPropertyName("action_id")] + public string? ActionId { get; set; } + + private static readonly JsonSerializerOptions _serializerOptions = _CreateJsonSerializerOptions(); + /// /// Creates a new instance of the class. /// @@ -38,6 +47,17 @@ public PredictedDoCommand(string action, Dictionary parameters) Parameters = parameters; } + /// + /// Creates a new instance of the class where parameters is a valid JSON string. + /// + /// The action name. + /// Any parameters that the AI system should use to perform the action as a JSON string. + public PredictedDoCommand(string action, string parameters) + { + Action = action; + Parameters = JsonSerializer.Deserialize>(parameters, _serializerOptions)!; + } + /// /// Creates a new instance of the class. /// @@ -78,5 +98,12 @@ public static JsonSchema Schema() .Required(new string[] { "type", "action" }) .Build(); } + + private static JsonSerializerOptions _CreateJsonSerializerOptions() + { + JsonSerializerOptions options = new(); + options.Converters.Add(new DictionaryJsonConverter()); + return options; + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs index f8ef76a77..d7b8a6a8c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs @@ -121,7 +121,15 @@ public PromptTemplate GetPrompt(string name) if (template == null) { - template = this.AppendAugmentations(this._LoadPromptTemplateFromFile(name)); + template = this._LoadPromptTemplateFromFile(name); + template = this.AppendAugmentations(template); + + // Group sections together under one "system" message + template.Prompt.Sections = new List() { + // The "1" place holder is to make this a fixed section so it is rendered in the correct order. + // TODO: When implementing the new layout engine class refactor this. + new GroupSection(ChatRole.System, template.Prompt.Sections, 1) + }; if (template.Configuration.Completion.IncludeHistory) { @@ -131,6 +139,13 @@ public PromptTemplate GetPrompt(string name) )); } + if (template.Configuration.Augmentation != null && template.Configuration.Augmentation.Type == AugmentationType.Tools) + { + bool includeHistory = template.Configuration.Completion.IncludeHistory; + string historyVariable = includeHistory ? $"conversation.{name}_history" : $"temp.{name}_history"; + template.Prompt.AddSection(new ActionOutputMessageSection(historyVariable)); + } + if (template.Configuration.Completion.IncludeImages) { template.Prompt.AddSection(new UserInputMessageSection(Options.MaxInputTokens)); @@ -174,6 +189,9 @@ private PromptTemplate AppendAugmentations(PromptTemplate template) case AugmentationType.Sequence: template.Augmentation = new SequenceAugmentation(template.Actions); break; + case AugmentationType.Tools: + template.Augmentation = new ToolsAugmentation(); + break; } if (template.Augmentation != null) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptResponse.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptResponse.cs index 3b47ea89e..de1573f59 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptResponse.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptResponse.cs @@ -16,7 +16,7 @@ public class PromptResponse /// /// User input message sent to the model. null if no input was sent. /// - public ChatMessage? Input { get; set; } + public IList? Input { get; set; } /// /// Message returned. diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs index 2c77d30fe..8796b651b 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs @@ -3,6 +3,7 @@ using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization; +using OAI = OpenAI; namespace Microsoft.Teams.AI.AI.Prompts { @@ -125,6 +126,8 @@ public class PromptTemplateConfiguration [Obsolete("Use `completion.model` instead.")] public List DefaultBackends { get; set; } = new(); + private static readonly JsonSerializerOptions _serializerOptions = new() { WriteIndented = true }; + /// /// Creates a prompt template configuration from JSON. /// @@ -132,10 +135,7 @@ public class PromptTemplateConfiguration /// Prompt template configuration. internal static PromptTemplateConfiguration FromJson(string json) { - PromptTemplateConfiguration? result = JsonSerializer.Deserialize(json, new JsonSerializerOptions() - { - WriteIndented = true - }); + PromptTemplateConfiguration? result = JsonSerializer.Deserialize(json, _serializerOptions); if (result == null) { @@ -252,6 +252,21 @@ public class CompletionConfiguration [JsonPropertyOrder(12)] public bool IncludeImages { get; set; } = false; + /// + /// Defines function calling behavior. Defaults to "auto". + /// + [JsonPropertyName("tool_choice")] + [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonPropertyOrder(13)] + public ChatToolChoice ToolChoice { get; set; } = ChatToolChoice.Auto; + + /// + /// Configures parallel function calling. Defaults to "true". + /// + [JsonPropertyName("parallel_tool_calls")] + [JsonPropertyOrder(14)] + public bool ParallelToolCalls { get; set; } = true; + /// /// Additional data provided in the completion configuration. /// @@ -273,6 +288,38 @@ public enum CompletionType /// Text } + + /// + /// ChatToolChoice + /// + public enum ChatToolChoice + { + /// + /// None + /// + None, + + /// + /// Auto + /// + Auto, + + /// + /// Required + /// + Required + } + + internal OAI.Chat.ChatToolChoice GetOpenAIChatToolChoice() + { + return ToolChoice switch + { + ChatToolChoice.Auto => OAI.Chat.ChatToolChoice.Auto, + ChatToolChoice.Required => OAI.Chat.ChatToolChoice.Required, + ChatToolChoice.None => OAI.Chat.ChatToolChoice.None, + _ => throw new InvalidOperationException($"Unknown ChatToolChoice: {ToolChoice}"), + }; + } } /// @@ -298,4 +345,4 @@ public class AugmentationConfiguration [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary DataSources { get; set; } = new(); } -} +} \ No newline at end of file diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/ActionOutputMessageSection.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/ActionOutputMessageSection.cs new file mode 100644 index 000000000..ee429548f --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/ActionOutputMessageSection.cs @@ -0,0 +1,60 @@ +using Microsoft.Bot.Builder; +using Microsoft.Teams.AI.AI.Models; +using Microsoft.Teams.AI.AI.Tokenizers; +using Microsoft.Teams.AI.State; + +namespace Microsoft.Teams.AI.AI.Prompts.Sections +{ + /// + /// A section that renders action outputs + /// + public class ActionOutputMessageSection : PromptSection + { + private string _OutputVariable; + + private string _HistoryVariable; + + /// + /// Action output message section + /// + /// The variable to retrieve the conversation history. + /// The variable to retrieve the action outputs from. + public ActionOutputMessageSection(string historyVariable, string outputVariable = TempState.ActionOutputsKey) : base(-1, true, "\n", "action: ") + { + this._OutputVariable = outputVariable; + this._HistoryVariable = historyVariable; + } + + /// + public override Task>> RenderAsMessagesAsync(ITurnContext context, IMemory memory, IPromptFunctions> functions, ITokenizer tokenizer, int maxTokens, CancellationToken cancellationToken = default) + { + List history = memory.GetValue(_HistoryVariable) as List ?? new(); + List messages = new(); + + if (history.Count >= 1) + { + Dictionary actionOutputs = memory.GetValue(_OutputVariable) as Dictionary ?? new(); + List actionCalls = history.Last().ActionCalls ?? new(); + + foreach (ActionCall actionCall in actionCalls) + { + string output = ""; + if (actionOutputs.TryGetValue(actionCall.Id!, out string actionOutput)) + { + output = actionOutput; + } + + ChatMessage message = new(ChatRole.Tool) + { + ActionCallId = actionCall.Id, + Content = output + }; + + messages.Add(message); + } + } + + return Task.FromResult(new RenderedPromptSection>(messages, messages.Count)); + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/ConversationHistorySection.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/ConversationHistorySection.cs index f51c20747..960082db1 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/ConversationHistorySection.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/ConversationHistorySection.cs @@ -95,14 +95,16 @@ public override async Task>> RenderAsMes return new(new() { }, 0, false); } - messages.Reverse(); + // Shallow copy + List history = new(messages); + history.Reverse(); // Populate history and stay under the token budget int tokens = 0; int budget = this.Tokens > 1 ? Math.Min(this.Tokens, maxTokens) : maxTokens; List output = new(); - foreach (ChatMessage message in messages) + foreach (ChatMessage message in history) { int length = tokenizer.Encode(this.GetMessageText(message)).Count; @@ -120,7 +122,13 @@ public override async Task>> RenderAsMes } tokens += length; - output.Add(message); + output.Insert(0, message); + } + + // Remove completed partial action outputs + while (messages.Count > 0 && messages[0].Role == ChatRole.Tool) + { + messages.RemoveAt(0); } return await Task.FromResult(new RenderedPromptSection>(output, tokens, tokens > budget)); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs index 51893ae33..edaeb4603 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs @@ -5,6 +5,7 @@ namespace Microsoft.Teams.AI.AI.Prompts.Sections { + // TODO: Rewrite to be in parity with JS & Python SDK. /// /// Base layout section that renders a set of `auto`, `fixed` or `proportional` length sections. /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/UserInputMessageSection.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/UserInputMessageSection.cs index ea929de8e..8613d311c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/UserInputMessageSection.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/UserInputMessageSection.cs @@ -33,6 +33,12 @@ public override Task>> RenderAsMessagesA string inputText = memory.GetValue(this.inputVariable) as string ?? string.Empty; List inputFiles = memory.GetValue(this.filesVariable) as List ?? new(); + // If no user input then return an empty section. + if (inputText == string.Empty && inputFiles.Count == 0) + { + return Task.FromResult(new RenderedPromptSection>(new())); + } + // Create message List messageContents = new(); ChatMessage message = new(ChatRole.User) @@ -65,7 +71,7 @@ public override Task>> RenderAsMessagesA { // Check for budget to add image. // TODO: This accounts for low detail images but not high detail images. - // Additional work is needed to accoutn for high detail images. + // Additional work is needed to account for high detail images. if (budget < 85) { break; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs index 28b8cd0cc..682d483b6 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Application.cs @@ -879,7 +879,6 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel await _StartLongRunningCall(turnContext, _OnTurnAsync, cancellationToken); } - // TODO: Make TypingTimer thread-safe and work for each turn /// /// Manually start a timer to periodically send "typing" activities. /// 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 66d591992..2d0a58f71 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -37,7 +37,7 @@ - + @@ -46,12 +46,17 @@ - - + + + + + + + diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Utilities/JsonConverters/ChatMessageJsonConverter.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Utilities/JsonConverters/ChatMessageJsonConverter.cs index 26500e45f..05cf541c2 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Utilities/JsonConverters/ChatMessageJsonConverter.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Utilities/JsonConverters/ChatMessageJsonConverter.cs @@ -4,29 +4,122 @@ namespace Microsoft.Teams.AI.Utilities.JsonConverters { + // Currently only supports serialization and deserialization of string `content` properties. + // TODO: Support deserializing non-string message content as well. internal class ChatMessageJsonConverter : JsonConverter { + private static JsonSerializerOptions serializerOptions = new(); + public override ChatMessage Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - string? response = JsonSerializer.Deserialize(ref reader); - - if (response == null) + if (reader.TokenType != JsonTokenType.StartObject) { - throw new JsonException(); + // Non-json string is being deserialized. + // Read the string into the ChatMessage "content" property. + string? response = JsonSerializer.Deserialize(ref reader); + + if (response == null) + { + throw new JsonException(); + } + + return new ChatMessage(ChatRole.Assistant) + { + Content = response + }; } - return new ChatMessage(ChatRole.Assistant) + var chatMessage = new ChatMessage(ChatRole.Assistant); + + while (reader.Read()) { - Content = response - }; + if (reader.TokenType == JsonTokenType.EndObject) + { + return chatMessage; + } + + if (reader.TokenType == JsonTokenType.PropertyName) + { + string? propertyName = reader.GetString(); + + reader.Read(); + + switch (propertyName) + { + case "role": + string role = reader.GetString() ?? ""; + chatMessage.Role = new ChatRole(role); + break; + case "content": + string content = reader.GetString() ?? ""; + chatMessage.Content = content; + break; + case "name": + chatMessage.Name = reader.GetString(); + break; + case "actionCallId": + chatMessage.ActionCallId = reader.GetString(); + break; + case "context": + // TODO: Implement deserializing message context. + break; + case "attachments": + // TODO: Implement deserializing attachments. + break; + case "actionCalls": + chatMessage.ActionCalls = JsonSerializer.Deserialize>(ref reader, options); + break; + default: + reader.Skip(); + break; + } + } + } + + throw new JsonException("Invalid JSON format for ChatMessage."); } - public override void Write(Utf8JsonWriter writer, ChatMessage value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ChatMessage chatMessage, JsonSerializerOptions options) { - writer.WriteStringValue(value.GetContent()); + writer.WriteStartObject(); + + writer.WritePropertyName("role"); + writer.WriteStringValue(chatMessage.Role.ToString()); + + if (chatMessage.Content != null && chatMessage.Content is string) + { + writer.WritePropertyName("content"); + writer.WriteStringValue(chatMessage.GetContent()); + } + + if (chatMessage.Name != null) + { + writer.WritePropertyName("name"); + writer.WriteStringValue(chatMessage.Name); + } + + if (chatMessage.ActionCallId != null) + { + writer.WritePropertyName("actionCallId"); + writer.WriteStringValue(chatMessage.ActionCallId); + } + + if (chatMessage.Context != null) + { + writer.WritePropertyName("context"); + writer.WriteRawValue(JsonSerializer.Serialize(chatMessage.Context)); + } + + if (chatMessage.ActionCalls != null) + { + writer.WritePropertyName("actionCalls"); + writer.WriteRawValue(JsonSerializer.Serialize(chatMessage.ActionCalls)); + } + + writer.WriteEndObject(); writer.Flush(); } } diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs index 091a6a770..19f9cd42f 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs @@ -30,13 +30,11 @@ public async Task LightsOff([ActionTurnContext] ITurnContext turnContext // Expecting "time" to be a number of milliseconds to pause. if (args.TryGetValue("time", out object? time)) { - if (time != null && time is string timeString) + if (time != null && time is long timeLong) { - if (int.TryParse(timeString, out int timeInt)) - { - await turnContext.SendActivityAsync(MessageFactory.Text($"[pausing for {timeInt / 1000} seconds]")); - await Task.Delay(timeInt); - } + int timeInt = (int)timeLong; + await turnContext.SendActivityAsync(MessageFactory.Text($"[pausing for {timeInt / 1000} seconds]")); + await Task.Delay(timeInt); } } diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs b/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs index 373efc067..9fcdd736a 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs @@ -35,7 +35,7 @@ if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey)) { builder.Services.AddSingleton(sp => new( - new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-3.5-turbo") + new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-4o") { LogRequests = true }, @@ -47,7 +47,7 @@ builder.Services.AddSingleton(sp => new( new AzureOpenAIModelOptions( config.Azure.OpenAIApiKey, - "gpt-35-turbo", + "gpt-4o", config.Azure.OpenAIEndpoint ) { @@ -87,7 +87,7 @@ prompts: prompts, defaultPrompt: async (context, state, planner) => { - PromptTemplate template = prompts.GetPrompt("sequence"); + PromptTemplate template = prompts.GetPrompt("tools"); return await Task.FromResult(template); } ) diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/sequence/actions.json b/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/tools/actions.json similarity index 100% rename from dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/sequence/actions.json rename to dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/tools/actions.json diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/sequence/config.json b/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/tools/config.json similarity index 86% rename from dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/sequence/config.json rename to dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/tools/config.json index 2f6897f1f..6105c9ca2 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/sequence/config.json +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/tools/config.json @@ -3,7 +3,7 @@ "description": "A bot that can turn the lights on and off", "type": "completion", "completion": { - "model": "gpt-3.5-turbo", + "model": "gpt-4o", "completion_type": "chat", "include_history": true, "include_input": true, @@ -16,6 +16,6 @@ "stop_sequences": [] }, "augmentation": { - "augmentation_type": "sequence" + "augmentation_type": "tools" } } \ No newline at end of file diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/sequence/skprompt.txt b/dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/tools/skprompt.txt similarity index 100% rename from dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/sequence/skprompt.txt rename to dotnet/samples/04.ai.c.actionMapping.lightBot/Prompts/tools/skprompt.txt diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/teamsapp.yml b/dotnet/samples/04.ai.c.actionMapping.lightBot/teamsapp.yml index c1e3bebb8..eebf85092 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/teamsapp.yml +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/teamsapp.yml @@ -94,3 +94,4 @@ deploy: # You can replace it with your existing Azure Resource id # or add it to your environment variable file. resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} +projectId: 7c828880-a7b6-4155-a729-03bfb6fc66d2 diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/Program.cs b/dotnet/samples/04.ai.d.chainedActions.listBot/Program.cs index 957f1bb41..c4a8b85bc 100644 --- a/dotnet/samples/04.ai.d.chainedActions.listBot/Program.cs +++ b/dotnet/samples/04.ai.d.chainedActions.listBot/Program.cs @@ -36,7 +36,7 @@ if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey)) { builder.Services.AddSingleton(sp => new( - new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-3.5-turbo") + new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-4o") { LogRequests = true }, @@ -48,7 +48,7 @@ builder.Services.AddSingleton(sp => new( new AzureOpenAIModelOptions( config.Azure.OpenAIApiKey, - "gpt-35-turbo", + "gpt-4o", config.Azure.OpenAIEndpoint ) { @@ -77,7 +77,7 @@ prompts, async (context, state, planner) => { - return await Task.FromResult(prompts.GetPrompt("Monologue")); + return await Task.FromResult(prompts.GetPrompt("Tools")); } ), loggerFactory diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/actions.json b/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/actions.json similarity index 98% rename from dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/actions.json rename to dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/actions.json index 7988ab599..cf01a5af3 100644 --- a/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/actions.json +++ b/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/actions.json @@ -62,7 +62,7 @@ "type": "string", "description": "The name of the list to remove the item from" }, - "items": { + "item": { "type": "string", "description": "The item to remove from the list" } diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/config.json b/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/config.json similarity index 86% rename from dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/config.json rename to dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/config.json index 0c2c08420..5ee5bae1d 100644 --- a/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/config.json +++ b/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/config.json @@ -3,7 +3,7 @@ "description": "A bot that can manage lists of items", "type": "completion", "completion": { - "model": "gpt-3.5-turbo", + "model": "gpt-4o", "completion_type": "chat", "include_history": true, "include_input": true, @@ -16,6 +16,6 @@ "stop_sequences": [] }, "augmentation": { - "augmentation_type": "monologue" + "augmentation_type": "tools" } } \ No newline at end of file diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/skprompt.txt b/dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/skprompt.txt similarity index 100% rename from dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Monologue/skprompt.txt rename to dotnet/samples/04.ai.d.chainedActions.listBot/Prompts/Tools/skprompt.txt diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/teamsapp.yml b/dotnet/samples/04.ai.d.chainedActions.listBot/teamsapp.yml index c128a95c4..d1acb78cf 100644 --- a/dotnet/samples/04.ai.d.chainedActions.listBot/teamsapp.yml +++ b/dotnet/samples/04.ai.d.chainedActions.listBot/teamsapp.yml @@ -95,3 +95,4 @@ deploy: # You can replace it with an existing Azure Resource ID or other # custom environment variable. resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} +projectId: 6fc5156c-2a22-48da-8f2c-4b2f22f3ace5 diff --git a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Program.cs b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Program.cs index cd9157aef..3c7a7c3d3 100644 --- a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Program.cs +++ b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Program.cs @@ -38,7 +38,7 @@ if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey)) { builder.Services.AddSingleton(sp => new( - new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-3.5-turbo") + new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-4o") { LogRequests = true }, @@ -50,7 +50,7 @@ builder.Services.AddSingleton(sp => new( new AzureOpenAIModelOptions( config.Azure.OpenAIApiKey, - "gpt-35-turbo", + "gpt-4o", config.Azure.OpenAIEndpoint ) { @@ -78,7 +78,7 @@ prompts, async (context, state, planner) => { - return await Task.FromResult(prompts.GetPrompt("Sequence")); + return await Task.FromResult(prompts.GetPrompt("Tools")); } ), loggerFactory diff --git a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Sequence/actions.json b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Tools/actions.json similarity index 100% rename from dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Sequence/actions.json rename to dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Tools/actions.json diff --git a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Sequence/config.json b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Tools/config.json similarity index 86% rename from dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Sequence/config.json rename to dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Tools/config.json index 98d0700fc..441c7b78a 100644 --- a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Sequence/config.json +++ b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Tools/config.json @@ -3,7 +3,7 @@ "description": "A bot that can manage a list of work items.", "type": "completion", "completion": { - "model": "gpt-3.5-turbo", + "model": "gpt-4o", "completion_type": "chat", "include_history": true, "include_input": true, @@ -16,6 +16,6 @@ "stop_sequences": [] }, "augmentation": { - "augmentation_type": "sequence" + "augmentation_type": "tools" } } \ No newline at end of file diff --git a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Sequence/skprompt.txt b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Tools/skprompt.txt similarity index 100% rename from dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Sequence/skprompt.txt rename to dotnet/samples/04.ai.e.chainedActions.devOpsBot/Prompts/Tools/skprompt.txt diff --git a/getting-started/CONCEPTS/FUNCTION-CALLS.md b/getting-started/CONCEPTS/FUNCTION-CALLS.md index 75eba3f2e..cc7bc92c3 100644 --- a/getting-started/CONCEPTS/FUNCTION-CALLS.md +++ b/getting-started/CONCEPTS/FUNCTION-CALLS.md @@ -46,6 +46,21 @@ To use function calling with the Chat Completions API: ``` + ### C# + + ```cs + ActionPlannerOptions options = new ActionPlannerOptions() + { + Model = model, + Prompts = prompts, + async (context, state, planner) => + { + return await Task.FromResult(prompts.GetPrompt("Tools")); + } + } + ActionPlanner planner = new ActionPlanner(options) + ``` + 2. Specify "tools" in your `config.json`. ```diff @@ -101,7 +116,24 @@ To use function calling with the Chat Completions API: ensure_list_exists(state, context.data["list"]) # Continues exectuion of next command in the plan. return "" - ```` + ``` + + ### C# + + ```cs + [Action("CreateList")] + public string CreateList([ActionTurnState] ListState turnState, [ActionParameters] Dictionary parameters) + { + ArgumentNullException.ThrowIfNull(turnState); + ArgumentNullException.ThrowIfNull(parameters); + + string listName = GetParameterString(parameters, "list"); + + EnsureListExists(turnState, listName); + + return "list created. think about your next action"; + } + ``` If the model requests to invoke any function(s), these are internally mapped to `DO` commands within a `Plan`, which are then invoked in our AI class' `run` function. These outputs are then returned to the model.