chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
|
||||
###############################
|
||||
# Core EditorConfig Options #
|
||||
###############################
|
||||
root = true
|
||||
# All files
|
||||
[*]
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
|
||||
# XML project files
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
||||
indent_size = 2
|
||||
|
||||
# XML config files
|
||||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
||||
indent_size = 2
|
||||
|
||||
# YAML config files
|
||||
[*.{yml,yaml}]
|
||||
tab_width = 2
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# JSON config files
|
||||
[*.json]
|
||||
tab_width = 2
|
||||
indent_size = 2
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Typescript files
|
||||
[*.{ts,tsx}]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
file_header_template = Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
# Stylesheet files
|
||||
[*.{css,scss,sass,less}]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
|
||||
# Code files
|
||||
[*.{cs,csx,vb,vbx}]
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8-bom
|
||||
file_header_template = Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
###############################
|
||||
# .NET Coding Conventions #
|
||||
###############################
|
||||
[*.{cs,vb}]
|
||||
# Organize usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
# this. preferences
|
||||
dotnet_style_qualification_for_field = true:error
|
||||
dotnet_style_qualification_for_property = true:error
|
||||
dotnet_style_qualification_for_method = true:error
|
||||
dotnet_style_qualification_for_event = true:error
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:error
|
||||
dotnet_style_readonly_field = true:warning
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:silent
|
||||
dotnet_style_prefer_auto_properties = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
# Code quality rules
|
||||
dotnet_code_quality_unused_parameters = all:suggestion
|
||||
|
||||
[*.cs]
|
||||
# Note: these settings cause "dotnet format" to fix the code. You should review each change if you uses "dotnet format".
|
||||
dotnet_diagnostic.RCS1036.severity = warning # Remove unnecessary blank line.
|
||||
dotnet_diagnostic.RCS1037.severity = warning # Remove trailing white-space.
|
||||
dotnet_diagnostic.RCS1097.severity = warning # Remove redundant 'ToString' call.
|
||||
dotnet_diagnostic.RCS1138.severity = warning # Add summary to documentation comment.
|
||||
dotnet_diagnostic.RCS1139.severity = warning # Add summary element to documentation comment.
|
||||
dotnet_diagnostic.RCS1168.severity = warning # Parameter name 'foo' differs from base name 'bar'.
|
||||
dotnet_diagnostic.RCS1175.severity = warning # Unused 'this' parameter 'operation'.
|
||||
dotnet_diagnostic.RCS1192.severity = warning # Unnecessary usage of verbatim string literal.
|
||||
dotnet_diagnostic.RCS1194.severity = warning # Implement exception constructors.
|
||||
dotnet_diagnostic.RCS1211.severity = warning # Remove unnecessary else clause.
|
||||
dotnet_diagnostic.RCS1214.severity = warning # Unnecessary interpolated string.
|
||||
dotnet_diagnostic.RCS1225.severity = warning # Make class sealed.
|
||||
dotnet_diagnostic.RCS1232.severity = warning # Order elements in documentation comment.
|
||||
|
||||
# Commented out because `dotnet format` change can be disruptive.
|
||||
# dotnet_diagnostic.RCS1085.severity = warning # Use auto-implemented property.
|
||||
|
||||
# Commented out because `dotnet format` removes the xmldoc element, while we should add the missing documentation instead.
|
||||
# dotnet_diagnostic.RCS1228.severity = warning # Unused element in documentation comment.
|
||||
|
||||
# Diagnostics elevated as warnings
|
||||
dotnet_diagnostic.CA1000.severity = warning # Do not declare static members on generic types
|
||||
dotnet_diagnostic.CA1050.severity = warning # Declare types in namespaces
|
||||
dotnet_diagnostic.CA1063.severity = warning # Implement IDisposable correctly
|
||||
dotnet_diagnostic.CA1064.severity = warning # Exceptions should be public
|
||||
dotnet_diagnostic.CA1416.severity = warning # Validate platform compatibility
|
||||
dotnet_diagnostic.CA1508.severity = warning # Avoid dead conditional code
|
||||
dotnet_diagnostic.CA1805.severity = warning # Member is explicitly initialized to its default value
|
||||
dotnet_diagnostic.CA1822.severity = suggestion # Member does not access instance data and can be marked as static
|
||||
dotnet_diagnostic.CA1852.severity = warning # Sealed classes
|
||||
dotnet_diagnostic.CA1859.severity = warning # Use concrete types when possible for improved performance
|
||||
dotnet_diagnostic.CA1860.severity = warning # Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance
|
||||
dotnet_diagnostic.CA2007.severity = warning # Do not directly await a Task
|
||||
dotnet_diagnostic.CA2201.severity = warning # Exception type System.Exception is not sufficiently specific
|
||||
|
||||
dotnet_diagnostic.IDE0001.severity = warning # Simplify name
|
||||
dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary using directives
|
||||
dotnet_diagnostic.IDE0009.severity = warning # Add this or Me qualification
|
||||
dotnet_diagnostic.IDE0011.severity = warning # Add braces
|
||||
dotnet_diagnostic.IDE0018.severity = warning # Inline variable declaration
|
||||
dotnet_diagnostic.IDE0032.severity = warning # Use auto-implemented property
|
||||
dotnet_diagnostic.IDE0034.severity = warning # Simplify 'default' expression
|
||||
dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code
|
||||
dotnet_diagnostic.IDE0040.severity = warning # Add accessibility modifiers
|
||||
dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references
|
||||
dotnet_diagnostic.IDE0050.severity = warning # Convert anonymous type to tuple
|
||||
dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member
|
||||
dotnet_diagnostic.IDE0055.severity = warning # Formatting rule
|
||||
dotnet_diagnostic.IDE0060.severity = warning # Remove unused parameter
|
||||
dotnet_diagnostic.IDE0070.severity = warning # Use 'System.HashCode.Combine'
|
||||
dotnet_diagnostic.IDE0071.severity = warning # Simplify interpolation
|
||||
dotnet_diagnostic.IDE0073.severity = warning # Require file header
|
||||
dotnet_diagnostic.IDE0082.severity = warning # Convert typeof to nameof
|
||||
dotnet_diagnostic.IDE0090.severity = warning # Simplify new expression
|
||||
dotnet_diagnostic.IDE0161.severity = warning # Use file-scoped namespace
|
||||
dotnet_diagnostic.IDE0280.severity = warning # Use nameof
|
||||
|
||||
dotnet_diagnostic.VSTHRD111.severity = warning # Use .ConfigureAwait(bool)
|
||||
dotnet_diagnostic.VSTHRD200.severity = warning # Use Async suffix for async methods
|
||||
|
||||
dotnet_diagnostic.RCS1021.severity = warning # Use expression-bodied lambda.
|
||||
dotnet_diagnostic.RCS1061.severity = warning # Merge 'if' with nested 'if'.
|
||||
dotnet_diagnostic.RCS1069.severity = warning # Remove unnecessary case label.
|
||||
dotnet_diagnostic.RCS1077.severity = warning # Optimize LINQ method call.
|
||||
dotnet_diagnostic.RCS1118.severity = warning # Mark local variable as const.
|
||||
dotnet_diagnostic.RCS1124.severity = warning # Inline local variable.
|
||||
dotnet_diagnostic.RCS1129.severity = warning # Remove redundant field initialization.
|
||||
dotnet_diagnostic.RCS1146.severity = warning # Use conditional access.
|
||||
dotnet_diagnostic.RCS1170.severity = warning # Use read-only auto-implemented property.
|
||||
dotnet_diagnostic.RCS1173.severity = warning # Use coalesce expression instead of 'if'.
|
||||
dotnet_diagnostic.RCS1186.severity = warning # Use Regex instance instead of static method.
|
||||
dotnet_diagnostic.RCS1188.severity = warning # Remove redundant auto-property initialization.
|
||||
dotnet_diagnostic.RCS1197.severity = suggestion # Optimize StringBuilder.AppendLine call.
|
||||
dotnet_diagnostic.RCS1201.severity = suggestion # Use method chaining.
|
||||
|
||||
dotnet_diagnostic.IDE0001.severity = warning # Simplify name
|
||||
dotnet_diagnostic.IDE0002.severity = warning # Simplify member access
|
||||
dotnet_diagnostic.IDE0004.severity = warning # Remove unnecessary cast
|
||||
dotnet_diagnostic.IDE0032.severity = warning # Use auto property
|
||||
dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code
|
||||
dotnet_diagnostic.IDE0047.severity = warning # Parentheses can be removed
|
||||
dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member
|
||||
dotnet_diagnostic.IDE0052.severity = warning # Remove unread private member
|
||||
dotnet_diagnostic.IDE0059.severity = warning # Unnecessary assignment of a value
|
||||
dotnet_diagnostic.IDE0110.severity = warning # Remove unnecessary discards
|
||||
dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations
|
||||
|
||||
# Suppressed diagnostics
|
||||
dotnet_diagnostic.CA1002.severity = none # Change 'List<string>' in '...' to use 'Collection<T>' ...
|
||||
dotnet_diagnostic.CA1031.severity = none # Do not catch general exception types
|
||||
dotnet_diagnostic.CA1032.severity = none # We're using RCS1194 which seems to cover more ctors
|
||||
dotnet_diagnostic.CA1034.severity = none # Do not nest type. Alternatively, change its accessibility so that it is not externally visible
|
||||
dotnet_diagnostic.CA1054.severity = none # Uri parameters should not be strings
|
||||
dotnet_diagnostic.CA1062.severity = none # Disable null check, C# already does it for us
|
||||
dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters
|
||||
dotnet_diagnostic.CA1305.severity = none # Operation could vary based on current user's locale settings
|
||||
dotnet_diagnostic.CA1307.severity = none # Operation has an overload that takes a StringComparison
|
||||
dotnet_diagnostic.CA1508.severity = none # Avoid dead conditional code. Too many false positives.
|
||||
dotnet_diagnostic.CA1510.severity = none # ArgumentNullException.Throw
|
||||
dotnet_diagnostic.CA1512.severity = none # ArgumentOutOfRangeException.Throw
|
||||
dotnet_diagnostic.CA1515.severity = none # Making public types from exes internal
|
||||
dotnet_diagnostic.CA1707.severity = none # Identifiers should not contain underscores
|
||||
dotnet_diagnostic.CA1846.severity = none # Prefer 'AsSpan' over 'Substring'
|
||||
dotnet_diagnostic.CA1848.severity = none # For improved performance, use the LoggerMessage delegates
|
||||
dotnet_diagnostic.CA1849.severity = none # Use async equivalent; analyzer is currently noisy
|
||||
dotnet_diagnostic.CA1865.severity = none # StartsWith(char)
|
||||
dotnet_diagnostic.CA1867.severity = none # EndsWith(char)
|
||||
dotnet_diagnostic.CS1998.severity = none # async method lacks 'await' operators and will run synchronously
|
||||
dotnet_diagnostic.CA2000.severity = none # Call System.IDisposable.Dispose on object before all references to it are out of scope
|
||||
dotnet_diagnostic.CA2225.severity = none # Operator overloads have named alternates
|
||||
dotnet_diagnostic.CA2227.severity = none # Change to be read-only by removing the property setter
|
||||
dotnet_diagnostic.CA2249.severity = suggestion # Consider using 'Contains' method instead of 'IndexOf' method
|
||||
dotnet_diagnostic.CA2252.severity = none # Requires preview
|
||||
dotnet_diagnostic.CA2253.severity = none # Named placeholders in the logging message template should not be comprised of only numeric characters
|
||||
dotnet_diagnostic.CA2253.severity = none # Named placeholders in the logging message template should not be comprised of only numeric characters
|
||||
dotnet_diagnostic.CA2263.severity = suggestion # Use generic overload
|
||||
dotnet_diagnostic.CA5394.severity = none # Do not use insecure sources of randomness
|
||||
|
||||
dotnet_diagnostic.VSTHRD003.severity = none # Waiting on thread from another context
|
||||
dotnet_diagnostic.VSTHRD103.severity = none # Use async equivalent; analyzer is currently noisy
|
||||
dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave
|
||||
|
||||
dotnet_diagnostic.xUnit1004.severity = none # Test methods should not be skipped. Remove the Skip property to start running the test again.
|
||||
dotnet_diagnostic.xUnit1042.severity = none # Untyped data rows
|
||||
|
||||
dotnet_diagnostic.RCS1032.severity = none # Remove redundant parentheses.
|
||||
dotnet_diagnostic.RCS1074.severity = none # Remove redundant constructor.
|
||||
dotnet_diagnostic.RCS1140.severity = none # Add exception to documentation comment.
|
||||
dotnet_diagnostic.RCS1141.severity = none # Add 'param' element to documentation comment.
|
||||
dotnet_diagnostic.RCS1142.severity = none # Add 'typeparam' element to documentation comment.
|
||||
dotnet_diagnostic.RCS1151.severity = none # Remove redundant cast.
|
||||
dotnet_diagnostic.RCS1158.severity = none # Static member in generic type should use a type parameter.
|
||||
dotnet_diagnostic.RCS1161.severity = none # Enum should declare explicit value
|
||||
dotnet_diagnostic.RCS1163.severity = none # Unused parameter 'foo'.
|
||||
dotnet_diagnostic.RCS1181.severity = none # Convert comment to documentation comment.
|
||||
dotnet_diagnostic.RCS1189.severity = none # Add region name to #endregion.
|
||||
dotnet_diagnostic.RCS1205.severity = none # Order named arguments according to the order of parameters.
|
||||
dotnet_diagnostic.RCS1212.severity = none # Remove redundant assignment.
|
||||
dotnet_diagnostic.RCS1217.severity = none # Convert interpolated string to concatenation.
|
||||
dotnet_diagnostic.RCS1222.severity = none # Merge preprocessor directives.
|
||||
dotnet_diagnostic.RCS1226.severity = none # Add paragraph to documentation comment.
|
||||
dotnet_diagnostic.RCS1229.severity = none # Use async/await when necessary.
|
||||
dotnet_diagnostic.RCS1234.severity = none # Enum duplicate value
|
||||
dotnet_diagnostic.RCS1238.severity = none # Avoid nested ?: operators.
|
||||
dotnet_diagnostic.RCS1241.severity = none # Implement IComparable when implementing IComparable<T>
|
||||
dotnet_diagnostic.RCS1246.severity = none # Use element access
|
||||
dotnet_diagnostic.RCS1261.severity = none # Resource can be disposed asynchronously
|
||||
|
||||
dotnet_diagnostic.IDE0010.severity = none # Populate switch
|
||||
dotnet_diagnostic.IDE0021.severity = none # Use block body for constructors
|
||||
dotnet_diagnostic.IDE0022.severity = none # Use block body for methods
|
||||
dotnet_diagnostic.IDE0024.severity = none # Use block body for operator
|
||||
dotnet_diagnostic.IDE0042.severity = none # Variable declaration can be deconstructed
|
||||
dotnet_diagnostic.IDE0046.severity = none # if statement can be simplified
|
||||
dotnet_diagnostic.IDE0056.severity = none # Indexing can be simplified
|
||||
dotnet_diagnostic.IDE0057.severity = none # Substring can be simplified
|
||||
dotnet_diagnostic.IDE0060.severity = none # Remove unused parameter
|
||||
dotnet_diagnostic.IDE0061.severity = none # Use block body for local function
|
||||
dotnet_diagnostic.IDE0079.severity = none # Remove unnecessary suppression.
|
||||
dotnet_diagnostic.IDE0080.severity = none # Remove unnecessary suppression operator.
|
||||
dotnet_diagnostic.IDE0100.severity = none # Remove unnecessary equality operator
|
||||
dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure
|
||||
dotnet_diagnostic.IDE0160.severity = none # Use block-scoped namespace
|
||||
dotnet_diagnostic.IDE0290.severity = none # Use primary constructor
|
||||
dotnet_diagnostic.IDE0305.severity = none # ToList can be simplified
|
||||
dotnet_diagnostic.IDE0330.severity = none # Use 'System.Threading.Lock'
|
||||
|
||||
# Testing
|
||||
dotnet_diagnostic.Moq1400.severity = none # Explicitly choose a mocking behavior instead of relying on the default (Loose) behavior
|
||||
|
||||
# Resharper disabled rules: https://www.jetbrains.com/help/resharper/Reference__Code_Inspections_CSHARP.html#CodeSmell
|
||||
resharper_not_resolved_in_text_highlighting = none # Disable Resharper's "Not resolved in text" highlighting
|
||||
resharper_check_namespace_highlighting = none # Disable Resharper's "Check namespace" highlighting
|
||||
resharper_object_creation_as_statement_highlighting = none # Disable Resharper's "Object creation as statement" highlighting
|
||||
|
||||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
|
||||
# Styles
|
||||
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.static_underscored.capitalization = camel_case
|
||||
dotnet_naming_style.static_underscored.required_prefix = s_
|
||||
|
||||
dotnet_naming_style.underscored.capitalization = camel_case
|
||||
dotnet_naming_style.underscored.required_prefix = _
|
||||
|
||||
dotnet_naming_style.uppercase_with_underscore_separator.capitalization = all_upper
|
||||
dotnet_naming_style.uppercase_with_underscore_separator.word_separator = _
|
||||
|
||||
dotnet_naming_style.end_in_async.required_prefix =
|
||||
dotnet_naming_style.end_in_async.required_suffix = Async
|
||||
dotnet_naming_style.end_in_async.capitalization = pascal_case
|
||||
dotnet_naming_style.end_in_async.word_separator =
|
||||
|
||||
# Symbols
|
||||
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.local_constant.applicable_kinds = local
|
||||
dotnet_naming_symbols.local_constant.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.local_constant.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
||||
|
||||
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
|
||||
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.any_async_methods.required_modifiers = async
|
||||
|
||||
# Rules
|
||||
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = error
|
||||
|
||||
dotnet_naming_rule.local_constant_should_be_pascal_case.symbols = local_constant
|
||||
dotnet_naming_rule.local_constant_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_rule.local_constant_should_be_pascal_case.severity = error
|
||||
|
||||
dotnet_naming_rule.private_static_fields_underscored.symbols = private_static_fields
|
||||
dotnet_naming_rule.private_static_fields_underscored.style = static_underscored
|
||||
dotnet_naming_rule.private_static_fields_underscored.severity = error
|
||||
|
||||
dotnet_naming_rule.private_fields_underscored.symbols = private_fields
|
||||
dotnet_naming_rule.private_fields_underscored.style = underscored
|
||||
dotnet_naming_rule.private_fields_underscored.severity = error
|
||||
|
||||
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
|
||||
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
|
||||
dotnet_naming_rule.async_methods_end_in_async.severity = error
|
||||
|
||||
###############################
|
||||
# C# Coding Conventions #
|
||||
###############################
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_for_built_in_types = false:none
|
||||
csharp_style_var_when_type_is_apparent = false:none
|
||||
csharp_style_var_elsewhere = false:none
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
# Null-checking preferences
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
# Modifier preferences
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
|
||||
# Expression-level preferences
|
||||
csharp_prefer_braces = true:error
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:error
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
|
||||
###############################
|
||||
# C# Formatting Rules #
|
||||
###############################
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = false # Does not work with resharper, forcing code to be on long lines instead of wrapping
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
# Indentation preferences
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = false
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = flush_left
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_statements = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_using_directive_placement = outside_namespace:warning
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
|
||||
###############################
|
||||
# Resharper Rules #
|
||||
###############################
|
||||
|
||||
# Resharper disabled rules: https://www.jetbrains.com/help/resharper/Reference__Code_Inspections_CSHARP.html#CodeSmell
|
||||
resharper_redundant_linebreak_highlighting = none # Disable Resharper's "Redundant line break" highlighting
|
||||
resharper_missing_linebreak_highlighting = none # Disable Resharper's "Missing line break" highlighting
|
||||
resharper_bad_empty_braces_line_breaks_highlighting = none # Disable Resharper's "Bad empty braces line breaks" highlighting
|
||||
resharper_missing_indent_highlighting = none # Disable Resharper's "Missing indent" highlighting
|
||||
resharper_missing_blank_lines_highlighting = none # Disable Resharper's "Missing blank lines" highlighting
|
||||
resharper_wrong_indent_size_highlighting = none # Disable Resharper's "Wrong indent size" highlighting
|
||||
resharper_bad_indent_highlighting = none # Disable Resharper's "Bad indent" highlighting
|
||||
resharper_bad_expression_braces_line_breaks_highlighting = none # Disable Resharper's "Bad expression braces line breaks" highlighting
|
||||
resharper_multiple_spaces_highlighting = none # Disable Resharper's "Multiple spaces" highlighting
|
||||
resharper_bad_expression_braces_indent_highlighting = none # Disable Resharper's "Bad expression braces indent" highlighting
|
||||
resharper_bad_control_braces_indent_highlighting = none # Disable Resharper's "Bad control braces indent" highlighting
|
||||
resharper_bad_preprocessor_indent_highlighting = none # Disable Resharper's "Bad preprocessor indent" highlighting
|
||||
resharper_redundant_blank_lines_highlighting = none # Disable Resharper's "Redundant blank lines" highlighting
|
||||
resharper_multiple_statements_on_one_line_highlighting = none # Disable Resharper's "Multiple statements on one line" highlighting
|
||||
resharper_bad_braces_spaces_highlighting = none # Disable Resharper's "Bad braces spaces" highlighting
|
||||
resharper_outdent_is_off_prev_level_highlighting = none # Disable Resharper's "Outdent is off previous level" highlighting
|
||||
resharper_bad_symbol_spaces_highlighting = none # Disable Resharper's "Bad symbol spaces" highlighting
|
||||
resharper_bad_colon_spaces_highlighting = none # Disable Resharper's "Bad colon spaces" highlighting
|
||||
resharper_bad_semicolon_spaces_highlighting = none # Disable Resharper's "Bad semicolon spaces" highlighting
|
||||
resharper_bad_square_brackets_spaces_highlighting = none # Disable Resharper's "Bad square brackets spaces" highlighting
|
||||
resharper_bad_parens_spaces_highlighting = none # Disable Resharper's "Bad parens spaces" highlighting
|
||||
|
||||
# Resharper enabled rules: https://www.jetbrains.com/help/resharper/Reference__Code_Inspections_CSHARP.html#CodeSmell
|
||||
resharper_comment_typo_highlighting = suggestion # Resharper's "Comment typo" highlighting
|
||||
resharper_redundant_using_directive_highlighting = warning # Resharper's "Redundant using directive" highlighting
|
||||
resharper_inconsistent_naming_highlighting = warning # Resharper's "Inconsistent naming" highlighting
|
||||
resharper_redundant_this_qualifier_highlighting = warning # Resharper's "Redundant 'this' qualifier" highlighting
|
||||
resharper_arrange_this_qualifier_highlighting = warning # Resharper's "Arrange 'this' qualifier" highlighting
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
csharp_prefer_system_threading_lock = true:suggestion
|
||||
csharp_style_prefer_simple_property_accessors = true:suggestion
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
---
|
||||
name: build-and-test
|
||||
description: How to build and test .NET projects in the Agent Framework repository. Use this when verifying or testing changes.
|
||||
---
|
||||
|
||||
- Only **UnitTest** projects need to be run locally; IntegrationTests require external dependencies.
|
||||
- See `../project-structure/SKILL.md` for project structure details.
|
||||
|
||||
## Build, Test, and Lint Commands
|
||||
|
||||
```bash
|
||||
# From dotnet/ directory
|
||||
dotnet restore --tl:off # Restore dependencies for all projects
|
||||
dotnet build --tl:off # Build all projects
|
||||
dotnet test # Run all tests
|
||||
dotnet format # Auto-fix formatting for all projects
|
||||
|
||||
# Build/test/format a specific project (preferred for isolated/internal changes)
|
||||
dotnet build src/Microsoft.Agents.AI.<Package> --tl:off
|
||||
dotnet test --project tests/Microsoft.Agents.AI.<Package>.UnitTests
|
||||
dotnet format src/Microsoft.Agents.AI.<Package>
|
||||
|
||||
# Run a single test
|
||||
# Replace the filter values with the appropriate assembly, namespace, class, and method names for the test you want to run and use * as a wildcard elsewhere, e.g. "/*/*/HttpClientTests/GetAsync_ReturnsSuccessStatusCode"
|
||||
# Use `--ignore-exit-code 8` to avoid failing the build when no tests are found for some projects
|
||||
dotnet test --filter-query "/<assemblyFilter>/<namespaceFilter>/<classFilter>/<methodFilter>" --ignore-exit-code 8
|
||||
|
||||
# Run unit tests only
|
||||
# Use `--ignore-exit-code 8` to avoid failing the build when no tests are found for integration test projects
|
||||
dotnet test --filter-query "/*UnitTests*/*/*/*" --ignore-exit-code 8
|
||||
```
|
||||
|
||||
Use `--tl:off` when building to avoid flickering when running commands in the agent.
|
||||
|
||||
## Speeding Up Builds and Testing
|
||||
|
||||
The full solution is large. Use these shortcuts:
|
||||
|
||||
| Change type | What to do |
|
||||
|-------------|------------|
|
||||
| Isolated/Internal logic | Build only the affected project and its `*.UnitTests` project. Fix issues, then build the full solution and run all unit tests. |
|
||||
| Public API surface | Build the full solution and run all unit tests immediately. |
|
||||
|
||||
Example: Building a single code project for all target frameworks
|
||||
|
||||
```bash
|
||||
# From dotnet/ directory
|
||||
dotnet build ./src/Microsoft.Agents.AI.Abstractions
|
||||
```
|
||||
|
||||
Example: Building a single code project for just .NET 10.
|
||||
|
||||
```bash
|
||||
# From dotnet/ directory
|
||||
dotnet build ./src/Microsoft.Agents.AI.Abstractions -f net10.0
|
||||
```
|
||||
|
||||
Example: Running tests for a single project using .NET 10.
|
||||
|
||||
```bash
|
||||
# From dotnet/ directory
|
||||
dotnet test --project ./tests/Microsoft.Agents.AI.Abstractions.UnitTests -f net10.0
|
||||
```
|
||||
|
||||
Example: Running a single test in a specific project using .NET 10.
|
||||
Provide the full namespace, class name, and method name for the test you want to run:
|
||||
|
||||
```bash
|
||||
# From dotnet/ directory
|
||||
dotnet test --project ./tests/Microsoft.Agents.AI.Abstractions.UnitTests -f net10.0 --filter-query "/*/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests/CloningConstructorCopiesProperties"
|
||||
```
|
||||
|
||||
### Multi-target framework tip
|
||||
|
||||
Most projects target multiple .NET frameworks. If the affected code does **not** use `#if` directives for framework-specific logic, pass `-f net10.0` to speed up building and testing.
|
||||
|
||||
### Package Restore tip
|
||||
|
||||
`dotnet build` will try and restore packages for all projects on each build, which can be slow.
|
||||
Unless packages have been changed, or it's the first time building the solution, add `--no-restore` to the build command to skip this step and speed up builds.
|
||||
|
||||
Just remember to run `dotnet restore` after pulling changes, making changes to project references, or when building for the first time.
|
||||
|
||||
### Testing on Linux tip
|
||||
|
||||
Unit tests target both .NET Framework as well as .NET Core. When running on Linux, only the .NET Core tests can be run, as .NET Framework is not supported on Linux.
|
||||
|
||||
To run only the .NET Core tests, use the `-f net10.0` option with `dotnet test`.
|
||||
|
||||
### Microsoft Testing Platform (MTP)
|
||||
|
||||
Tests use the [Microsoft Testing Platform](https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-intro) via xUnit v3. Key differences from the legacy VSTest runner:
|
||||
|
||||
- **`dotnet test` requires `--project`** to specify a test project directly (positional arguments are no longer supported).
|
||||
- **Test output** uses the MTP format (e.g., `[✓112/x0/↓0]` progress and `Test run summary: Passed!`).
|
||||
- **TRX reports** use `--report-xunit-trx` instead of `--logger trx`.
|
||||
- **Code coverage** uses `Microsoft.Testing.Extensions.CodeCoverage` with `--coverage --coverage-output-format cobertura`.
|
||||
- **Running a test project directly** is supported via `dotnet run --project <test-project>`. This bypasses the `dotnet test` infrastructure and runs the test executable directly with the MTP command line.
|
||||
|
||||
- **Running tests across the solution** with a filter may cause some projects to match zero tests, which MTP treats as a failure (exit code 8). Use `--ignore-exit-code 8` to suppress this:
|
||||
|
||||
```bash
|
||||
# Run all unit tests across the solution, ignoring projects with no matching tests
|
||||
dotnet test --solution ./agent-framework-dotnet.slnx --no-build -f net10.0 --ignore-exit-code 8
|
||||
```
|
||||
|
||||
- **Running tests with `--solution` for a specific TFM** requires all projects in the solution to support that TFM. Not all projects target every framework (e.g., some are `net10.0`-only). Use `./dotnet/eng/scripts/New-FilteredSolution.ps1` to generate a filtered solution:
|
||||
|
||||
```powershell
|
||||
# Generate a filtered solution for net472 and run tests
|
||||
$filtered = ./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net472
|
||||
dotnet test --solution $filtered --no-build -f net472 --ignore-exit-code 8
|
||||
|
||||
# Exclude samples and keep only unit test projects
|
||||
./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net10.0 -ExcludeSamples -TestProjectNameFilter "*UnitTests*" -OutputPath dotnet/filtered-unit.slnx
|
||||
```
|
||||
|
||||
```bash
|
||||
# Run tests via dotnet test (uses MTP under the hood)
|
||||
dotnet test --project ./tests/Microsoft.Agents.AI.UnitTests -f net10.0
|
||||
|
||||
# Run tests with code coverage (Cobertura format)
|
||||
dotnet test --project ./tests/Microsoft.Agents.AI.UnitTests -f net10.0 --coverage --coverage-output-format cobertura --coverage-settings ./tests/coverage.runsettings
|
||||
|
||||
# Run tests directly via dotnet run (MTP native command line)
|
||||
dotnet run --project ./tests/Microsoft.Agents.AI.UnitTests -f net10.0
|
||||
|
||||
# Show MTP command line help
|
||||
dotnet run --project ./tests/Microsoft.Agents.AI.UnitTests -f net10.0 -- -?
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: project-structure
|
||||
description: Explains the project structure of the agent-framework .NET solution
|
||||
---
|
||||
|
||||
# Agent Framework .NET Project Structure
|
||||
|
||||
```
|
||||
dotnet/
|
||||
├── src/
|
||||
│ ├── Microsoft.Agents.AI/ # Core AI agent implementations
|
||||
│ ├── Microsoft.Agents.AI.Abstractions/ # Core AI agent abstractions
|
||||
│ ├── Microsoft.Agents.AI.A2A/ # Agent-to-Agent (A2A) provider
|
||||
│ ├── Microsoft.Agents.AI.OpenAI/ # OpenAI provider
|
||||
│ ├── Microsoft.Agents.AI.Foundry/ # Microsoft Foundry Agents (v2) provider
|
||||
│ ├── Microsoft.Agents.AI.AzureAI.Persistent/ # Legacy Microsoft Foundry Agents (v1) provider
|
||||
│ ├── Microsoft.Agents.AI.Anthropic/ # Anthropic provider
|
||||
│ ├── Microsoft.Agents.AI.Workflows/ # Workflow orchestration
|
||||
│ └── ... # Other packages
|
||||
├── samples/ # Sample applications
|
||||
└── tests/ # Unit and integration tests
|
||||
```
|
||||
|
||||
## Main Folders
|
||||
|
||||
| Folder | Contents |
|
||||
|--------|----------|
|
||||
| `src/` | Source code projects |
|
||||
| `tests/` | Test projects — named `<Source-Code-Project>.UnitTests` or `<Source-Code-Project>.IntegrationTests` |
|
||||
| `samples/` | Sample projects |
|
||||
| `src/Shared`, `src/LegacySupport` | Shared code files included by multiple source code projects (see README.md files in these folders or their subdirectories for instructions on how to include them in a project) |
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: pull-requests
|
||||
description: >
|
||||
Guidance for creating pull requests and handling PR review comments in the
|
||||
Agent Framework repository. Use this when writing a PR description (filling out
|
||||
the PR template) or when responding to and resolving review comments on an
|
||||
existing PR.
|
||||
---
|
||||
|
||||
# Pull Request Workflow
|
||||
|
||||
This skill covers two tasks: (1) writing a high-quality PR description, and
|
||||
(2) handling review comments on an existing PR.
|
||||
|
||||
## 1. Writing the PR description
|
||||
|
||||
Always follow the repository PR template at
|
||||
[`.github/pull_request_template.md`](../../../../.github/pull_request_template.md). Keep its
|
||||
exact structure and headings. Fill every section:
|
||||
|
||||
### `### Motivation & Context`
|
||||
Explain *why* the change is needed: the problem it solves and the scenario it
|
||||
contributes to. Describe the net change relative to `main` — this is implied, so
|
||||
do **not** spell out "vs main" explicitly.
|
||||
|
||||
### `### Description & Review Guide`
|
||||
Describe the changes, the overall approach, and the design. Answer the three
|
||||
prompts:
|
||||
- **What are the major changes?**
|
||||
- **What is the impact of these changes?**
|
||||
- **What do you want reviewers to focus on?** — This item is for **human
|
||||
reviewers only**. Automated/AI reviewers must ignore it and review the entire
|
||||
change rather than narrowing scope to it.
|
||||
|
||||
### `### Related Issue`
|
||||
Link the issue the PR fixes using a GitHub closing keyword (`Fixes #123` /
|
||||
`Closes #123`) so it closes automatically on merge. A PR with no linked issue may
|
||||
be closed regardless of how valid the change is. Before opening, confirm there is
|
||||
no other open PR for the same issue; if there is, explain how this PR differs.
|
||||
|
||||
### `### Contribution Checklist`
|
||||
Check every item that applies. For the breaking-change item:
|
||||
- Leave **"This is not a breaking change."** checked for the common case.
|
||||
- If the change **is** breaking, add the `breaking change` label **or** put
|
||||
`[BREAKING]` in the title prefix, before or after a language prefix such as
|
||||
`Python:` or `.NET:` — workflows keep the label and the title prefix in sync
|
||||
automatically (see `.github/workflows/label-title-prefix.yml` and
|
||||
`.github/workflows/label-pr.yml`).
|
||||
|
||||
### Do not
|
||||
- Do **not** add ad-hoc sections such as "Validation" or "Tests run"; CI/CD and
|
||||
the checklist already cover validation status.
|
||||
- Do **not** remove or reorder the template's headings.
|
||||
|
||||
### Creating the PR
|
||||
Open new PRs as **drafts** until they are ready for review. Example:
|
||||
|
||||
```bash
|
||||
gh pr create --repo microsoft/agent-framework --base main \
|
||||
--head <your-fork-owner>:<branch> --draft \
|
||||
--title "<concise title>" --body "<body following the template>"
|
||||
```
|
||||
|
||||
## 2. Handling review comments
|
||||
|
||||
When a PR receives review comments, follow this sequence — **do not start editing
|
||||
code before the user has reviewed the plan**:
|
||||
|
||||
1. **Review the comments.** Read every review comment and thread on the PR,
|
||||
including inline code comments and general review summaries.
|
||||
2. **Make a plan.** Produce a concrete plan describing how each comment will be
|
||||
addressed (or why it should not be, with reasoning).
|
||||
3. **Let the user review the plan.** Present the plan and wait for the user's
|
||||
approval or adjustments before implementing anything.
|
||||
4. **Implement.** Make the agreed changes.
|
||||
5. **Reply to every comment.** Add a reply to **all** comments explaining how it
|
||||
was addressed (or the agreed outcome) — leave none unanswered.
|
||||
6. **Resolve resolved threads.** Mark a review thread as resolved only when the
|
||||
comment has actually been addressed.
|
||||
|
||||
### Useful commands
|
||||
|
||||
List review comments and threads:
|
||||
|
||||
```bash
|
||||
# Inline review comments
|
||||
gh api repos/{owner}/{repo}/pulls/{pr}/comments
|
||||
|
||||
# Review threads with resolution state (GraphQL)
|
||||
gh api graphql -f query='
|
||||
query($owner:String!,$repo:String!,$pr:Int!){
|
||||
repository(owner:$owner,name:$repo){
|
||||
pullRequest(number:$pr){
|
||||
reviewThreads(first:100){
|
||||
nodes{ id isResolved comments(first:50){ nodes{ id body author{login} } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -F owner={owner} -F repo={repo} -F pr={pr}
|
||||
```
|
||||
|
||||
Reply to an inline review comment:
|
||||
|
||||
```bash
|
||||
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
|
||||
-f body="Addressed in <commit>: <explanation>"
|
||||
```
|
||||
|
||||
Resolve a review thread (needs the thread node id from the GraphQL query above):
|
||||
|
||||
```bash
|
||||
gh api graphql -f query='
|
||||
mutation($threadId:ID!){
|
||||
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
|
||||
}' -F threadId={thread_id}
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: verify-dotnet-samples
|
||||
description: How to build, run and verify the .NET sample projects in the Agent Framework repository. Use this when a user wants to verify that the samples still function as expected.
|
||||
---
|
||||
|
||||
# Verifying .NET Sample Projects
|
||||
|
||||
## Sample Pre-requisites
|
||||
|
||||
We should only support verifying samples that:
|
||||
1. Use environment variables for configuration.
|
||||
2. Have no complex setup requirements, e.g., where multiple applications need to be run together, or where we need to launch a browser, etc.
|
||||
|
||||
Always report to the user which samples were run and which were not, and why.
|
||||
|
||||
## Verifying a sample
|
||||
|
||||
Samples should be verified to ensure that they actually work as intended and that their output matches what is expected.
|
||||
For each sample that is run, output should be produced that shows the result and explains the reasoning about what output
|
||||
was expected, what was produced, and why it didn't match what the sample was expected to produce.
|
||||
|
||||
Steps to verify a sample:
|
||||
1. Read the code for the sample
|
||||
1. Check what environment variables are required for the sample
|
||||
1. Check if each environment variable has been set
|
||||
1. If there are any missing, give the user a list of missing environment variables to set and terminate
|
||||
1. Summarize what the expected output of the sample should be
|
||||
1. Run the sample
|
||||
1. Show the user any output from the sample run as it gets produced, so that they can see the run progress
|
||||
1. Check the output of the run against expectations
|
||||
1. After running all requested samples, produce output for each sample that was verified:
|
||||
1. If expectations were matched, output the following:
|
||||
```text
|
||||
[Sample Name] Succeeded
|
||||
```
|
||||
1. If expectations were not matched, output the following:
|
||||
```text
|
||||
[Sample Name] Failed
|
||||
Actual Output:
|
||||
[What the sample produced]
|
||||
Expected Output:
|
||||
[Explanation of what was expected and why the actual output didn't match expectations]
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Most samples use environment variables to configure settings.
|
||||
|
||||
```csharp
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||
```
|
||||
|
||||
To run a sample, the environment variables should be set first.
|
||||
Before running a sample, check whether each environment variable in the sample has a value and
|
||||
then give the user a list of environment variables to set.
|
||||
|
||||
You can provide the user some examples of how to set the variables like this:
|
||||
|
||||
```bash
|
||||
export AZURE_OPENAI_ENDPOINT="https://my-openai-instance.openai.azure.com/"
|
||||
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
|
||||
```
|
||||
|
||||
To check if a variable has a value use e.g.:
|
||||
|
||||
```bash
|
||||
echo $AZURE_OPENAI_ENDPOINT
|
||||
```
|
||||
|
||||
## How to Run a Sample (General Pattern)
|
||||
|
||||
```bash
|
||||
cd dotnet/samples/<category>/<sample-dir>
|
||||
dotnet run
|
||||
```
|
||||
|
||||
For multi-targeted projects (e.g., Durable console apps), specify the framework:
|
||||
|
||||
```bash
|
||||
dotnet run --framework net10.0
|
||||
```
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
---
|
||||
name: verify-samples-tool
|
||||
description: How to use the verify-samples tool to run, verify, and manage sample definitions in the Agent Framework repository. Use this when adding, updating, or running sample verification.
|
||||
---
|
||||
|
||||
# verify-samples Tool
|
||||
|
||||
The `verify-samples` project (`dotnet/eng/verify-samples/`) is an automated tool that runs sample projects and verifies their output using deterministic checks and AI-powered verification.
|
||||
|
||||
## Running verify-samples
|
||||
|
||||
**Important:** By default, samples must be pre-built before running verify-samples. Build the solution first, or pass `--build` to build samples during the run:
|
||||
|
||||
```bash
|
||||
cd dotnet
|
||||
dotnet build agent-framework-dotnet.slnx -f net10.0
|
||||
```
|
||||
|
||||
Then run verify-samples:
|
||||
|
||||
```bash
|
||||
# Run all samples across all categories
|
||||
dotnet run --project eng/verify-samples -- --log results.log --csv results.csv
|
||||
|
||||
# Run a specific category
|
||||
dotnet run --project eng/verify-samples -- --category 02-agents --log results.log
|
||||
|
||||
# Run specific samples by name
|
||||
dotnet run --project eng/verify-samples -- Agent_Step02_StructuredOutput Agent_Step09_AsFunctionTool
|
||||
|
||||
# Control parallelism (default 8)
|
||||
dotnet run --project eng/verify-samples -- --parallel 8 --log results.log
|
||||
|
||||
# Build samples during run (skips the need for a prior build step)
|
||||
# This may cause build conflicts as multiple samples are built in parallel, so use with caution
|
||||
dotnet run --project eng/verify-samples -- --build --log results.log
|
||||
|
||||
# Combine options
|
||||
dotnet run --project eng/verify-samples -- --category 03-workflows --parallel 4 --log results.log --csv results.csv --md results.md
|
||||
```
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
The tool itself needs:
|
||||
- `AZURE_OPENAI_ENDPOINT` — for the AI verification agent
|
||||
- `AZURE_OPENAI_DEPLOYMENT_NAME` (optional, defaults to `gpt-5-mini`)
|
||||
|
||||
Individual samples require their own env vars (e.g., `AZURE_AI_PROJECT_ENDPOINT`). The tool automatically checks and skips samples with missing env vars.
|
||||
|
||||
### Output Files
|
||||
|
||||
- `--log results.log` — detailed per-sample log with stdout/stderr, AI reasoning, and a summary
|
||||
- `--csv results.csv` — tabular summary with Sample, ProjectPath, Status, FailedChecks, and Failures columns
|
||||
- `--md results.md` — Markdown summary with results table and collapsible failure details (suitable for GitHub PR comments)
|
||||
|
||||
## Sample Categories
|
||||
|
||||
Definitions are in the `dotnet/eng/verify-samples/` directory:
|
||||
|
||||
| Category | Config File | Registered Key |
|
||||
|----------|-------------|----------------|
|
||||
| 01-get-started | `GetStartedSamples.cs` | `01-get-started` |
|
||||
| 02-agents | `AgentsSamples.cs` | `02-agents` |
|
||||
| 03-workflows | `WorkflowSamples.cs` | `03-workflows` |
|
||||
|
||||
Categories are registered in `VerifyOptions.cs` in the `s_sampleSets` dictionary.
|
||||
|
||||
## SampleDefinition Properties
|
||||
|
||||
Each sample is defined as a `SampleDefinition` in the appropriate config file. Key properties:
|
||||
|
||||
```csharp
|
||||
new SampleDefinition
|
||||
{
|
||||
// Required: Display name for the sample
|
||||
Name = "Agent_Step02_StructuredOutput",
|
||||
|
||||
// Required: Relative path from dotnet/ to the sample project directory
|
||||
ProjectPath = "samples/02-agents/Agents/Agent_Step02_StructuredOutput",
|
||||
|
||||
// Environment variables the sample requires (throws if missing)
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
|
||||
// Environment variables with defaults that would prompt on console if unset
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
|
||||
// Skip this sample with a reason (for structural issues only)
|
||||
SkipReason = null, // or "Requires external service X."
|
||||
|
||||
// Deterministic checks: substrings that must appear in stdout
|
||||
MustContain = ["=== Section Header ==="],
|
||||
|
||||
// Substrings that must NOT appear in stdout
|
||||
MustNotContain = [],
|
||||
|
||||
// If true, only MustContain checks are used (no AI verification)
|
||||
IsDeterministic = false,
|
||||
|
||||
// AI verification: natural-language descriptions of expected output
|
||||
// Each entry describes one aspect to verify independently
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show structured person information with Name, Age, and Occupation fields.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
|
||||
// Stdin inputs to feed to the sample (for interactive samples)
|
||||
Inputs = ["Y", "Y", "Y"],
|
||||
|
||||
// Delay between stdin inputs in ms (default 2000, increase for LLM calls between inputs)
|
||||
InputDelayMs = 3000,
|
||||
}
|
||||
```
|
||||
|
||||
## How to Add a New Sample Definition
|
||||
|
||||
1. **Check the sample's Program.cs** to understand:
|
||||
- What environment variables it reads (look for `GetEnvironmentVariable`)
|
||||
- Whether it needs stdin input (look for `Console.ReadLine`, `Application.GetInput`)
|
||||
- Whether it has an external loop (look for `EXIT` patterns in YAML workflows)
|
||||
- What output it produces (section headers, markers, expected behavior)
|
||||
- Whether it exits on its own or runs as a server
|
||||
|
||||
2. **Choose the right verification strategy:**
|
||||
- **Deterministic** (`IsDeterministic = true`): Use `MustContain` for samples with fixed output strings. No AI verification.
|
||||
- **AI-verified** (default): Use `ExpectedOutputDescription` with semantic descriptions. Write expectations that are flexible enough for non-deterministic LLM output.
|
||||
- **Both**: Use `MustContain` for fixed markers AND `ExpectedOutputDescription` for LLM-generated content.
|
||||
|
||||
3. **Set `SkipReason` only for structural issues:**
|
||||
- Web servers that don't exit
|
||||
- Multi-process client/server architectures
|
||||
- Samples requiring external infrastructure (MCP servers you can't reach, Docker, etc.)
|
||||
- Do NOT skip for missing env vars — the tool checks those dynamically.
|
||||
|
||||
4. **For interactive samples, provide `Inputs`:**
|
||||
- Samples using `Application.GetInput(args)` need one initial input
|
||||
- Samples with `Console.ReadLine()` approval loops need `"Y"` inputs
|
||||
- YAML workflows with `externalLoop` need `"EXIT"` as the last input
|
||||
- Set `InputDelayMs` to 3000-8000ms for samples with LLM calls between inputs
|
||||
|
||||
5. **Add the definition** to the appropriate config file (e.g., `AgentsSamples.cs`) in the `All` list.
|
||||
|
||||
6. **Register new categories** (if needed) in `VerifyOptions.cs` `s_sampleSets` dictionary.
|
||||
|
||||
### Writing Good ExpectedOutputDescription
|
||||
|
||||
- Write descriptions that are **semantically flexible** — LLM output varies between runs
|
||||
- Each array entry should describe **one independent aspect** to verify
|
||||
- Always include `"The output should not contain error messages or stack traces."` as the last entry
|
||||
- Avoid exact wording expectations — use "should mention", "should contain information about", "should show"
|
||||
- Bad: `"The output should say 'The weather in Amsterdam is cloudy with a high of 15°C'"`
|
||||
- Good: `"The output should contain weather information about Amsterdam mentioning cloudy weather with a high of 15°C."`
|
||||
|
||||
### Example: Simple LLM Sample
|
||||
|
||||
```csharp
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Agent_With_AzureOpenAIChatCompletion",
|
||||
ProjectPath = "samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should contain a joke about a pirate.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
### Example: Deterministic Sample
|
||||
|
||||
```csharp
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Visualization",
|
||||
ProjectPath = "samples/03-workflows/Visualization",
|
||||
IsDeterministic = true,
|
||||
MustContain = ["Generating workflow visualization...", "Mermaid string:", "DiGraph string:"],
|
||||
ExpectedOutputDescription = ["The output should show workflow visualization in Mermaid and DiGraph formats."],
|
||||
},
|
||||
```
|
||||
|
||||
### Example: Interactive Sample with Approval Loop
|
||||
|
||||
```csharp
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "FoundryAgent_Hosted_MCP",
|
||||
ProjectPath = "samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
Inputs = ["Y", "Y", "Y", "Y", "Y"],
|
||||
InputDelayMs = 5000,
|
||||
ExpectedOutputDescription = ["The output should show an agent using the Microsoft Learn MCP tool with approval prompts."],
|
||||
},
|
||||
```
|
||||
|
||||
### Example: Declarative Workflow with External Loop
|
||||
|
||||
```csharp
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_FunctionTools",
|
||||
ProjectPath = "samples/03-workflows/Declarative/FunctionTools",
|
||||
RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
Inputs = ["What are today's specials?", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow calling function tools to answer a question about restaurant specials."],
|
||||
},
|
||||
```
|
||||
|
||||
### Example: Skipped Sample
|
||||
|
||||
```csharp
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Agent_MCP_Server",
|
||||
ProjectPath = "samples/02-agents/ModelContextProtocol/Agent_MCP_Server",
|
||||
RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["AZURE_OPENAI_DEPLOYMENT_NAME"],
|
||||
SkipReason = "Runs as an MCP stdio server that does not exit on its own.",
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# AWS SAM Build and Temporary Artifacts folder
|
||||
.aws-sam
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
# Foundry agent CLI config (contains secrets, auto-generated)
|
||||
.foundry-agent.json
|
||||
.foundry-agent-build.log
|
||||
|
||||
# Pre-published output for Docker builds
|
||||
out/
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csdevkit"
|
||||
]
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "agent-framework-dotnet.slnx",
|
||||
"git.openRepositoryInParentFolders": "always",
|
||||
"chat.agent.enabled": true,
|
||||
"dotnet.automaticallySyncWithActiveItem": true
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "dotnet",
|
||||
"task": "build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"label": "dotnet: build"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
# AGENTS.md
|
||||
|
||||
Instructions for AI coding agents working in the .NET codebase.
|
||||
|
||||
## Build, Test, and Lint Commands
|
||||
|
||||
See `./.github/skills/build-and-test/SKILL.md` for detailed instructions on building, testing, and linting projects.
|
||||
|
||||
## Project Structure
|
||||
|
||||
See `./.github/skills/project-structure/SKILL.md` for an overview of the project structure.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
See `./.github/skills/pull-requests/SKILL.md` for guidance on writing PR descriptions and handling/resolving PR review comments.
|
||||
|
||||
### Core types
|
||||
|
||||
- `AIAgent`: The abstract base class that all agents derive from, providing common methods for interacting with an agent.
|
||||
- `AgentSession`: The abstract base class that all agent sessions derive from, representing a conversation with an agent.
|
||||
- `ChatClientAgent`: An `AIAgent` implementation that uses an `IChatClient` to send messages to an AI provider and receive responses.
|
||||
- `IChatClient`: Interface for sending messages to an AI provider and receiving responses. Used by `ChatClientAgent` and implemented by provider-specific packages.
|
||||
- `FunctionInvokingChatClient`: Decorator for `IChatClient` that adds function invocation capabilities.
|
||||
- `AITool`: Represents a tool that an agent/AI provider can use, with metadata and an execution delegate.
|
||||
- `AIFunction`: A specific type of `AITool` that represents a local function the agent/AI provider can call, with parameters and return types defined.
|
||||
- `ChatMessage`: Represents a message in a conversation.
|
||||
- `AIContent`: Represents content in a message, which can be text, a function call, tool output and more.
|
||||
|
||||
### External Dependencies
|
||||
|
||||
The framework integrates with `Microsoft.Extensions.AI` and `Microsoft.Extensions.AI.Abstractions` (external NuGet packages)
|
||||
using types like `IChatClient`, `FunctionInvokingChatClient`, `AITool`, `AIFunction`, `ChatMessage`, and `AIContent`.
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- **Command output capture**: When running `dotnet build`, `dotnet test`, `dotnet format`, or similar commands, redirect output to a temp file first (e.g., `dotnet build --tl:off 2>&1 | Out-File $env:TEMP\build.log`), then analyze the file as needed. This avoids re-running expensive commands when the initial analysis misses something.
|
||||
- **Encoding**: All new files must be saved with UTF-8 encoding with BOM (Byte Order Mark). This is required for `dotnet format` to work correctly. When using PowerShell `Set-Content`, always pass `-Encoding UTF8BOM` to preserve the BOM (e.g., `Set-Content $file $content -NoNewline -Encoding UTF8BOM`).
|
||||
- **Copyright header**: `// Copyright (c) Microsoft. All rights reserved.` at top of all `.cs` files
|
||||
- **XML docs**: Required for all public methods and classes
|
||||
- **Async**: Use `Async` suffix for methods returning `Task`/`ValueTask`
|
||||
- **Private classes**: Should be `sealed` unless subclassed
|
||||
- **Config**: Read from environment variables with `UPPER_SNAKE_CASE` naming
|
||||
- **Tests**: Add Arrange/Act/Assert comments; use Moq for mocking; test methods returning `Task`/`ValueTask` must use the `Async` suffix.
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
When developing or reviewing code, verify adherence to these key design principles:
|
||||
|
||||
- **DRY**: Avoid code duplication by moving common logic into helper methods or helper classes.
|
||||
- **Single Responsibility**: Each class should have one clear responsibility.
|
||||
- **Encapsulation**: Keep implementation details private and expose only necessary public APIs.
|
||||
- **Strong Typing**: Use strong typing to ensure that code is self-documenting and to catch errors at compile time.
|
||||
|
||||
## Sample Structure
|
||||
|
||||
Samples (in `./samples/` folder) should follow this structure:
|
||||
|
||||
1. Copyright header: `// Copyright (c) Microsoft. All rights reserved.`
|
||||
2. Description comment explaining what the sample demonstrates
|
||||
3. Using statements
|
||||
4. Main code logic
|
||||
5. Helper methods at bottom
|
||||
|
||||
Configuration via environment variables (never hardcode secrets). Keep samples simple and focused.
|
||||
|
||||
When adding a new sample:
|
||||
|
||||
- Create a standalone project in `samples/` with matching directory and project names
|
||||
- Include a README.md explaining what the sample does and how to run it
|
||||
- Add the project to the solution file
|
||||
- Reference the sample in the parent directory's README.md
|
||||
@@ -0,0 +1,51 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Default properties inherited by all projects. Projects can override. -->
|
||||
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisLevel>10.0-all</AnalysisLevel>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);NU5128;CS8002</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<TargetFrameworksCore>net10.0;net9.0;net8.0</TargetFrameworksCore>
|
||||
<TargetFrameworks>$(TargetFrameworksCore);netstandard2.0;net472</TargetFrameworks>
|
||||
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IsReleaseCandidate>false</IsReleaseCandidate>
|
||||
<IsReleased>false</IsReleased>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Disable NuGet packaging by default. Projects can override. -->
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Publish'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- .NET Framework/.NET Standard don't properly support nullable reference types, suppress any warnings for those TFMs -->
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'net472' ">
|
||||
<NoWarn>$(NoWarn);nullable</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RepoRoot>$([System.IO.Path]::GetDirectoryName($([MSBuild]::GetPathOfFileAbove('CODE_OF_CONDUCT.md', '$(MSBuildThisFileDirectory)'))))</RepoRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Add CLSCompliant=false to all projects by default. Projects can override. -->
|
||||
<AssemblyAttribute Include="System.CLSCompliantAttribute">
|
||||
<_Parameter1>false</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Common properties -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)\eng\MSBuild\LegacySupport.props" />
|
||||
<Import Project="$(MSBuildThisFileDirectory)\eng\MSBuild\Shared.props" />
|
||||
</Project>
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project>
|
||||
<!-- Direct all packages under 'dotnet' to get versions from Directory.Packages.props -->
|
||||
<!-- using Central Package Management feature -->
|
||||
<!-- https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management -->
|
||||
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
|
||||
<!-- Only run 'dotnet format' on dev machines, Release builds. Skip on GitHub Actions -->
|
||||
<!-- as this runs in its own Actions job. Only run for net10.0 target frameworks since the dotnet format command -->
|
||||
<!-- already formats all target frameworks in project. Otherwise it will run format x times x where x is the number of target frameworks -->
|
||||
<Target Name="DotnetFormatOnBuild" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Release' AND '$(GITHUB_ACTIONS)' == '' AND '$(TargetFramework)' == 'net10.0' ">
|
||||
<Message Text="Running dotnet format" Importance="high" />
|
||||
<Exec Command="dotnet format --no-restore -v diag $(ProjectFileName)" />
|
||||
</Target>
|
||||
|
||||
<Import Project="$(MSBuildThisFileDirectory)\eng\MSBuild\Shared.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,211 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Enable central package management -->
|
||||
<!-- https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management -->
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Aspire -->
|
||||
<AspireAppHostSdkVersion>13.1.0</AspireAppHostSdkVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Aspire.* -->
|
||||
<PackageVersion Include="Anthropic" Version="12.31.0" />
|
||||
<PackageVersion Include="Anthropic.Foundry" Version="0.6.0" />
|
||||
<PackageVersion Include="Aspire.Hosting" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
|
||||
<PackageVersion Include="Aspire.Azure.AI.Inference" Version="13.1.0-preview.1.25616.3" />
|
||||
<PackageVersion Include="Aspire.Hosting.Azure.AIFoundry" Version="13.1.0-preview.1.25616.3" />
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0" />
|
||||
<PackageVersion Include="MessagePack" Version="3.1.7" /> <!-- Transitive dependency of Aspire pinned to newer version due to vulnerability in 2.5.192 -->
|
||||
<!-- Azure.* -->
|
||||
<PackageVersion Include="Azure.AI.AgentServer.Core" Version="1.0.0-beta.26" />
|
||||
<PackageVersion Include="Azure.AI.AgentServer.Invocations" Version="1.0.0-beta.5" />
|
||||
<PackageVersion Include="Azure.AI.AgentServer.Responses" Version="1.0.0-beta.6" />
|
||||
<PackageVersion Include="Azure.Search.Documents" Version="12.0.0" />
|
||||
<PackageVersion Include="Azure.AI.Projects" Version="2.1.0-beta.4" />
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.10" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
|
||||
<PackageVersion Include="Azure.Core" Version="1.60.0" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
|
||||
<PackageVersion Include="DotNetEnv" Version="3.1.1" />
|
||||
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.5.0" />
|
||||
<!-- Google Gemini -->
|
||||
<PackageVersion Include="Google.GenAI" Version="1.6.0" />
|
||||
<PackageVersion Include="Mscc.GenerativeAI.Microsoft" Version="2.9.3" />
|
||||
<!-- Microsoft.Azure.* -->
|
||||
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.54.0" />
|
||||
<!-- Newtonsoft.Json -->
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<!-- System.* -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.9" />
|
||||
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.Memory" Version="10.0.5" />
|
||||
<PackageVersion Include="System.ClientModel" Version="1.14.0" />
|
||||
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.9" />
|
||||
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.8" />
|
||||
<!-- AG-UI .NET SDK packages (published by the AG-UI team). -->
|
||||
<PackageVersion Include="AGUI.Abstractions" Version="0.0.3" />
|
||||
<PackageVersion Include="AGUI.Formatting" Version="0.0.3" />
|
||||
<PackageVersion Include="AGUI.Protobuf" Version="0.0.3" />
|
||||
<PackageVersion Include="AGUI.Client" Version="0.0.3" />
|
||||
<PackageVersion Include="AGUI.Server" Version="0.0.3" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.9" />
|
||||
<PackageVersion Include="System.Threading.Channels" Version="10.0.8" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
|
||||
<!-- OpenTelemetry -->
|
||||
<PackageVersion Include="OpenTelemetry" Version="1.15.3" />
|
||||
<PackageVersion Include="OpenTelemetry.Api" Version="1.15.3" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.15.3" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
|
||||
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
|
||||
<!-- Microsoft.AspNetCore.* -->
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
|
||||
<PackageVersion Include="Microsoft.OpenApi" Version="2.7.5" /> <!-- Pin patched OpenAPI.NET to remediate GHSA-v5pm-xwqc-g5wc -->
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
|
||||
<!-- Microsoft.Extensions.* -->
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.6.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.6.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.6.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.6.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.6.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.6.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Compliance.Abstractions" Version="10.5.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="9.7.0" />
|
||||
<!-- Vector Stores -->
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.InMemory" Version="1.67.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="1.67.0-preview" />
|
||||
<!-- Agent SDKs -->
|
||||
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.5" />
|
||||
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.3.171-beta" />
|
||||
<!-- M365 Agents SDK -->
|
||||
<PackageVersion Include="AdaptiveCards" Version="3.1.0" />
|
||||
<PackageVersion Include="Microsoft.Agents.Authentication.Msal" Version="1.3.171-beta" />
|
||||
<PackageVersion Include="Microsoft.Agents.Hosting.AspNetCore" Version="1.3.171-beta" />
|
||||
<!-- A2A -->
|
||||
<PackageVersion Include="A2A" Version="1.0.0-preview2" />
|
||||
<PackageVersion Include="A2A.AspNetCore" Version="1.0.0-preview2" />
|
||||
<!-- MCP -->
|
||||
<PackageVersion Include="ModelContextProtocol" Version="1.2.0" />
|
||||
<!-- Hyperlight -->
|
||||
<PackageVersion Include="Hyperlight.HyperlightSandbox.Api" Version="0.4.0" />
|
||||
<PackageVersion Include="Hyperlight.HyperlightSandbox.Guest.Python" Version="0.4.0" />
|
||||
<!-- Inference SDKs -->
|
||||
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.10.0" />
|
||||
<PackageVersion Include="Microsoft.ML.Tokenizers" Version="2.0.0" />
|
||||
<PackageVersion Include="OllamaSharp" Version="5.4.8" />
|
||||
<PackageVersion Include="OpenAI" Version="2.10.0" />
|
||||
<!-- Identity -->
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.84.2" />
|
||||
<!-- Workflows -->
|
||||
<PackageVersion Include="Microsoft.Agents.ObjectModel" Version="2026.2.4.1" />
|
||||
<PackageVersion Include="Microsoft.Agents.ObjectModel.Json" Version="2026.2.4.1" />
|
||||
<PackageVersion Include="Microsoft.Agents.ObjectModel.PowerFx" Version="2026.2.4.1" />
|
||||
<PackageVersion Include="Microsoft.PowerFx.Interpreter" Version="1.8.1" />
|
||||
<!-- Durable Task -->
|
||||
<PackageVersion Include="Microsoft.DurableTask.Client" Version="1.18.0" />
|
||||
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.18.0" />
|
||||
<PackageVersion Include="Microsoft.DurableTask.Worker" Version="1.18.0" />
|
||||
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.18.0" />
|
||||
<!-- Azure Functions -->
|
||||
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="2.50.0" />
|
||||
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.12.1" />
|
||||
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.0.1" />
|
||||
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
|
||||
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
|
||||
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Mcp" Version="1.0.0" />
|
||||
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" />
|
||||
<!-- Valkey -->
|
||||
<!-- Redis -->
|
||||
<PackageVersion Include="StackExchange.Redis" Version="2.10.1" />
|
||||
<!-- Valkey -->
|
||||
<PackageVersion Include="Valkey.Glide" Version="1.1.0" />
|
||||
<!-- Console UX -->
|
||||
<PackageVersion Include="Spectre.Console" Version="0.49.1" />
|
||||
<!-- AWS -->
|
||||
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.6.10" />
|
||||
<!-- Test -->
|
||||
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.22" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.0.11" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net10.0'" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||
<PackageVersion Include="Moq" Version="[4.18.4]" />
|
||||
<PackageVersion Include="xunit.v3.mtp-v2" Version="3.2.2" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xRetry.v3" Version="1.0.0-rc3" />
|
||||
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.4.1" />
|
||||
<!-- Symbols -->
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||
<!-- Toolset -->
|
||||
<PackageVersion Include="ReferenceTrimmer" Version="3.4.5" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.100" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="xunit.analyzers" Version="1.23.0" />
|
||||
<PackageReference Include="xunit.analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Moq.Analyzers" Version="0.3.1" />
|
||||
<PackageReference Include="Moq.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.1" />
|
||||
<PackageReference Include="Roslynator.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.1" />
|
||||
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.14.1" />
|
||||
<PackageReference Include="Roslynator.Formatting.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,36 @@
|
||||
# Get Started with Microsoft Agent Framework for C# Developers
|
||||
|
||||
## Quickstart
|
||||
|
||||
### Basic Agent - .NET
|
||||
|
||||
```c#
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")!;
|
||||
|
||||
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.GetResponsesClient(deploymentName)
|
||||
.AsAIAgent(name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||
```
|
||||
|
||||
## Examples & Samples
|
||||
|
||||
- [Getting Started with Agents](./samples/02-agents/Agents): basic agent creation and tool usage
|
||||
- [Agent Provider Samples](./samples/02-agents/AgentProviders): samples showing different agent providers
|
||||
- [Workflow Samples](./samples/03-workflows): advanced multi-agent patterns and workflow orchestration
|
||||
|
||||
## Agent Framework Documentation
|
||||
|
||||
- [Documentation](https://learn.microsoft.com/agent-framework/)
|
||||
- [Agent Framework Repository](https://github.com/microsoft/agent-framework)
|
||||
- [Design Documents](../docs/design)
|
||||
- [Architectural Decision Records](../docs/decisions)
|
||||
- [MSFT Learn Docs](https://learn.microsoft.com/agent-framework/overview/agent-framework-overview)
|
||||
|
||||
@@ -0,0 +1,698 @@
|
||||
<Solution>
|
||||
<Configurations>
|
||||
<BuildType Name="Debug" />
|
||||
<BuildType Name="Publish" />
|
||||
<BuildType Name="Release" />
|
||||
</Configurations>
|
||||
<Folder Name="/Samples/">
|
||||
<File Path="samples/AGENTS.md" />
|
||||
<File Path="samples/README.md" />
|
||||
<Project Path="eng/verify-samples/verify-samples.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/01-get-started/">
|
||||
<Project Path="samples/01-get-started/01_hello_agent/01_hello_agent.csproj" />
|
||||
<Project Path="samples/01-get-started/02_add_tools/02_add_tools.csproj" />
|
||||
<Project Path="samples/01-get-started/03_multi_turn/03_multi_turn.csproj" />
|
||||
<Project Path="samples/01-get-started/04_memory/04_memory.csproj" />
|
||||
<Project Path="samples/01-get-started/05_first_workflow/05_first_workflow.csproj" />
|
||||
<Project Path="samples/01-get-started/06_host_your_agent/06_host_your_agent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/">
|
||||
<File Path="samples/02-agents/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentProviders/">
|
||||
<File Path="samples/02-agents/AgentProviders/README.md" />
|
||||
<Project Path="samples/02-agents/AgentProviders/a2a/Agent_With_A2A/Agent_With_A2A.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/azure/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/custom/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/github-copilot/Agent_With_GitHubCopilot/Agent_With_GitHubCopilot.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/google-gemini/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/ollama/Agent_With_Ollama/Agent_With_Ollama.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/onnx/Agent_With_ONNX/Agent_With_ONNX.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/05-end-to-end/DevUIAspireIntegration/">
|
||||
<Project Path="samples/05-end-to-end/DevUIAspireIntegration/DevUIIntegration.AppHost/DevUIIntegration.AppHost.csproj" />
|
||||
<Project Path="samples/05-end-to-end/DevUIAspireIntegration/DevUIIntegration.ServiceDefaults/DevUIIntegration.ServiceDefaults.csproj" />
|
||||
<Project Path="samples/05-end-to-end/DevUIAspireIntegration/EditorAgent/EditorAgent.csproj" />
|
||||
<Project Path="samples/05-end-to-end/DevUIAspireIntegration/WriterAgent/WriterAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/Agents/">
|
||||
<File Path="samples/02-agents/Agents/README.md" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step01_UsingFunctionToolsWithApprovals/Agent_Step01_UsingFunctionToolsWithApprovals.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step02_StructuredOutput/Agent_Step02_StructuredOutput.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step03_PersistedConversations/Agent_Step03_PersistedConversations.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step04_3rdPartyChatHistoryStorage/Agent_Step04_3rdPartyChatHistoryStorage.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step05_Observability/Agent_Step05_Observability.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step06_DependencyInjection/Agent_Step06_DependencyInjection.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step07_AsMcpTool/Agent_Step07_AsMcpTool.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step08_UsingImages/Agent_Step08_UsingImages.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step09_AsFunctionTool/Agent_Step09_AsFunctionTool.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step10_BackgroundResponsesWithToolsAndPersistence/Agent_Step10_BackgroundResponsesWithToolsAndPersistence.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step11_Middleware/Agent_Step11_Middleware.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step12_Plugins/Agent_Step12_Plugins.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step13_ChatReduction/Agent_Step13_ChatReduction.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step14_BackgroundResponses/Agent_Step14_BackgroundResponses.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step15_DeepResearch/Agent_Step15_DeepResearch.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step16_Declarative/Agent_Step16_Declarative.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step17_AdditionalAIContext/Agent_Step17_AdditionalAIContext.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step18_CompactionPipeline/Agent_Step18_CompactionPipeline.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step19_InFunctionLoopCheckpointing/Agent_Step19_InFunctionLoopCheckpointing.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step20_DynamicFunctionTools/Agent_Step20_DynamicFunctionTools.csproj" />
|
||||
<Project Path="samples/02-agents/Agents/Agent_Step21_ShellWithEnvironment/Agent_Step21_ShellWithEnvironment.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/DeclarativeAgents/">
|
||||
<Project Path="samples/02-agents/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/DurableWorkflows/" />
|
||||
<Folder Name="/Samples/04-hosting/DurableWorkflows/ConsoleApps/">
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/03_ConditionalEdges/03_ConditionalEdges.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/04_WorkflowAndAgents/04_WorkflowAndAgents.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/05_WorkflowEvents/05_WorkflowEvents.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/06_WorkflowSharedState/06_WorkflowSharedState.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/07_SubWorkflows/07_SubWorkflows.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/08_WorkflowHITL/08_WorkflowHITL.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/DurableWorkflows/AzureFunctions/">
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/03_WorkflowHITL/03_WorkflowHITL.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/04_WorkflowMcpTool/04_WorkflowMcpTool.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/05_WorkflowAndAgents.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/">
|
||||
<File Path="samples/GettingStarted/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AGUI/">
|
||||
<File Path="samples/02-agents/AGUI/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AGUI/Step01_GettingStarted/">
|
||||
<Project Path="samples/02-agents/AGUI/Step01_GettingStarted/Client/Client.csproj" />
|
||||
<Project Path="samples/02-agents/AGUI/Step01_GettingStarted/Server/Server.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AGUI/Step02_BackendTools/">
|
||||
<Project Path="samples/02-agents/AGUI/Step02_BackendTools/Client/Client.csproj" />
|
||||
<Project Path="samples/02-agents/AGUI/Step02_BackendTools/Server/Server.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AGUI/Step03_FrontendTools/">
|
||||
<Project Path="samples/02-agents/AGUI/Step03_FrontendTools/Client/Client.csproj" />
|
||||
<Project Path="samples/02-agents/AGUI/Step03_FrontendTools/Server/Server.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AGUI/Step04_HumanInLoop/">
|
||||
<Project Path="samples/02-agents/AGUI/Step04_HumanInLoop/Client/Client.csproj" />
|
||||
<Project Path="samples/02-agents/AGUI/Step04_HumanInLoop/Server/Server.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentSkills/">
|
||||
<File Path="samples/02-agents/AgentSkills/README.md" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Agent_Step01_FileBasedSkills.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Agent_Step02_CodeDefinedSkills.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Agent_Step03_ClassBasedSkills.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step06_McpBasedSkills/Agent_Step06_McpBasedSkills.csproj" />
|
||||
<Project Path="samples/02-agents/AgentSkills/Agent_Step07_SkillsAutoApproval/Agent_Step07_SkillsAutoApproval.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/Harness/">
|
||||
<File Path="samples/02-agents/Harness/README.md" />
|
||||
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step01_MeetYourClaw/Claw_Step01_MeetYourClaw.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData/Claw_Step02_WorkingWithData.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/Claw_Step03_ScalingCapabilities.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/ConsoleReactiveComponents/ConsoleReactiveComponents.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/ConsoleReactiveFramework/ConsoleReactiveFramework.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/Harness_Shared_Console/Harness_Shared_Console.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/Harness_Shared_Console_OpenAI/Harness_Shared_Console_OpenAI.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/Harness_Step01_Research/Harness_Step01_Research.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Harness_Step02_Research_WithBackgroundAgents.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/Harness_Step03_DataProcessing/Harness_Step03_DataProcessing.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/Harness_Step04_CodeExecution/Harness_Step04_CodeExecution.csproj" />
|
||||
<Project Path="samples/02-agents/Harness/Harness_Step05_Loop/Harness_Step05_Loop.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AGUI/Step05_StateManagement/">
|
||||
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Client/Client.csproj" />
|
||||
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Server/Server.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/DevUI/">
|
||||
<File Path="samples/02-agents/DevUI/README.md" />
|
||||
<Project Path="samples/02-agents/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentProviders/anthropic/">
|
||||
<File Path="samples/02-agents/AgentProviders/anthropic/README.md" />
|
||||
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/anthropic/Agent_Anthropic_Step04_UsingSkills/Agent_Anthropic_Step04_UsingSkills.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentProviders/foundry/">
|
||||
<File Path="samples/02-agents/AgentProviders/foundry/README.md" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step00_FoundryAgentLifecycle/Agent_Step00_FoundryAgentLifecycle.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step01_Basics/Agent_Step01_Basics.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step02.1_MultiturnConversation/Agent_Step02.1_MultiturnConversation.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step02.2_MultiturnWithServerConversations/Agent_Step02.2_MultiturnWithServerConversations.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step07_Observability/Agent_Step07_Observability.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step08_DependencyInjection/Agent_Step08_DependencyInjection.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step09_UsingMcpClientAsTools/Agent_Step09_UsingMcpClientAsTools.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step10_UsingImages/Agent_Step10_UsingImages.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step11_AsFunctionTool/Agent_Step11_AsFunctionTool.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step12_Middleware/Agent_Step12_Middleware.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step13_Plugins/Agent_Step13_Plugins.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step14_CodeInterpreter/Agent_Step14_CodeInterpreter.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step15_ComputerUse/Agent_Step15_ComputerUse.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step16_FileSearch/Agent_Step16_FileSearch.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step17_OpenAPITools/Agent_Step17_OpenAPITools.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step18_BingCustomSearch/Agent_Step18_BingCustomSearch.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step19_SharePoint/Agent_Step19_SharePoint.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step20_MicrosoftFabric/Agent_Step20_MicrosoftFabric.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step21_WebSearch/Agent_Step21_WebSearch.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step22_MemorySearch/Agent_Step22_MemorySearch.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step23_LocalMCP/Agent_Step23_LocalMCP.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step24_CodeInterpreterFileDownload/Agent_Step24_CodeInterpreterFileDownload.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Agent_Step25_FoundryToolboxMcp.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Agent_Step26_FoundryToolboxMcpSkills.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/Evaluation/">
|
||||
<Project Path="samples/02-agents/Evaluation/Evaluation_CustomEvals/Evaluation_CustomEvals.csproj" />
|
||||
<Project Path="samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/Evaluation_ExpectedOutputs.csproj" />
|
||||
<Project Path="samples/02-agents/Evaluation/Evaluation_Multimodal/Evaluation_Multimodal.csproj" />
|
||||
<Project Path="samples/02-agents/Evaluation/Evaluation_SimpleEval/Evaluation_SimpleEval.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentWithCodeAct/">
|
||||
<File Path="samples/02-agents/AgentWithCodeAct/README.md" />
|
||||
<Project Path="samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/AgentWithCodeAct_Step01_Interpreter.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/AgentWithCodeAct_Step02_ToolEnabled.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/AgentWithCodeAct_Step03_ManualWiring.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentWithMemory/">
|
||||
<File Path="samples/02-agents/AgentWithMemory/README.md" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/AgentWithMemory_Step02_MemoryUsingMem0.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/AgentWithMemory_Step03_MemoryUsingValkey.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentProviders/openai/">
|
||||
<File Path="samples/02-agents/AgentProviders/openai/README.md" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step05_Conversation/Agent_OpenAI_Step05_Conversation.csproj" />
|
||||
<Project Path="samples/02-agents/AgentProviders/openai/Agent_OpenAI_Step06_CodeInterpreterFileDownload/Agent_OpenAI_Step06_CodeInterpreterFileDownload.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/AgentWithRAG/">
|
||||
<File Path="samples/02-agents/AgentWithRAG/README.md" />
|
||||
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/AgentWithRAG_Step01_BasicTextRAG.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/AgentWithRAG_Step02_CustomVectorStoreRAG.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj" />
|
||||
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/ModelContextProtocol/">
|
||||
<File Path="samples/02-agents/ModelContextProtocol/README.md" />
|
||||
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_LongRunningTask_Client/Agent_MCP_LongRunningTask_Client.csproj" />
|
||||
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_PerRun_AuthHeaders/Agent_MCP_PerRun_AuthHeaders.csproj" />
|
||||
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj" />
|
||||
<Project Path="samples/02-agents/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj" />
|
||||
<Project Path="samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj" />
|
||||
<Project Path="samples/02-agents/ModelContextProtocol/ResponseAgent_Hosted_MCP/ResponseAgent_Hosted_MCP.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/Observability/">
|
||||
<Project Path="samples/02-agents/AgentOpenTelemetry/AgentOpenTelemetry.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/">
|
||||
<File Path="samples/03-workflows/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Concurrent/">
|
||||
<Project Path="samples/03-workflows/Concurrent/Concurrent/Concurrent.csproj" />
|
||||
<Project Path="samples/03-workflows/Concurrent/MapReduce/MapReduce.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/ConditionalEdges/">
|
||||
<Project Path="samples/03-workflows/ConditionalEdges/01_EdgeCondition/01_EdgeCondition.csproj" />
|
||||
<Project Path="samples/03-workflows/ConditionalEdges/02_SwitchCase/02_SwitchCase.csproj" />
|
||||
<Project Path="samples/03-workflows/ConditionalEdges/03_MultiSelection/03_MultiSelection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Declarative/">
|
||||
<File Path="samples/03-workflows/Declarative/README.md" />
|
||||
<Project Path="samples/03-workflows/Declarative/AotCheckpointing/AotCheckpointing.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/ConfirmInput/ConfirmInput.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/CustomerSupport/CustomerSupport.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/DeepResearch/DeepResearch.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/FunctionTools/FunctionTools.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/InputArguments/InputArguments.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/InvokeFunctionTool/InvokeFunctionTool.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/InvokeHttpRequest/InvokeHttpRequest.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/InvokeMcpTool/InvokeMcpTool.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/Marketing/Marketing.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/StudentTeacher/StudentTeacher.csproj" />
|
||||
<Project Path="samples/03-workflows/Declarative/ToolApproval/ToolApproval.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Declarative/Examples/">
|
||||
<File Path="../declarative-agents/workflow-samples/CustomerSupport.yaml" />
|
||||
<File Path="../declarative-agents/workflow-samples/DeepResearch.yaml" />
|
||||
<File Path="../declarative-agents/workflow-samples/Marketing.yaml" />
|
||||
<File Path="../declarative-agents/workflow-samples/MathChat.yaml" />
|
||||
<File Path="../declarative-agents/workflow-samples/README.md" />
|
||||
<File Path="../declarative-agents/workflow-samples/wttr.json" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/SharedStates/">
|
||||
<Project Path="samples/03-workflows/SharedStates/SharedStates.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Loop/">
|
||||
<Project Path="samples/03-workflows/Loop/Loop.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Agents/">
|
||||
<Project Path="samples/03-workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj" />
|
||||
<Project Path="samples/03-workflows/Agents/FoundryAgent/FoundryAgent.csproj" />
|
||||
<Project Path="samples/03-workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj" />
|
||||
<Project Path="samples/03-workflows/Agents/WorkflowAsAnAgent/WorkflowAsAnAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Checkpoint/">
|
||||
<Project Path="samples/03-workflows/Checkpoint/CheckpointAndRehydrate/CheckpointAndRehydrate.csproj" />
|
||||
<Project Path="samples/03-workflows/Checkpoint/CheckpointAndResume/CheckpointAndResume.csproj" />
|
||||
<Project Path="samples/03-workflows/Checkpoint/CheckpointWithHumanInTheLoop/CheckpointWithHumanInTheLoop.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/HumanInTheLoop/">
|
||||
<Project Path="samples/03-workflows/HumanInTheLoop/HumanInTheLoopBasic/HumanInTheLoopBasic.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Orchestration/">
|
||||
<Project Path="samples/03-workflows/Orchestration/Handoff/Handoff.csproj" />
|
||||
<Project Path="samples/03-workflows/Orchestration/Magentic/Magentic.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Observability/">
|
||||
<Project Path="samples/03-workflows/Observability/ApplicationInsights/ApplicationInsights.csproj" />
|
||||
<Project Path="samples/03-workflows/Observability/AspireDashboard/AspireDashboard.csproj" />
|
||||
<Project Path="samples/03-workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Visualization/">
|
||||
<Project Path="samples/03-workflows/Visualization/Visualization.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/_StartHere/">
|
||||
<Project Path="samples/03-workflows/_StartHere/01_Streaming/01_Streaming.csproj" />
|
||||
<Project Path="samples/03-workflows/_StartHere/02_AgentsInWorkflows/02_AgentsInWorkflows.csproj" />
|
||||
<Project Path="samples/03-workflows/_StartHere/03_AgentWorkflowPatterns/03_AgentWorkflowPatterns.csproj" />
|
||||
<Project Path="samples/03-workflows/_StartHere/04_MultiModelService/04_MultiModelService.csproj" />
|
||||
<Project Path="samples/03-workflows/_StartHere/05_SubWorkflows/05_SubWorkflows.csproj" />
|
||||
<Project Path="samples/03-workflows/_StartHere/06_MixedWorkflowAgentsAndExecutors/06_MixedWorkflowAgentsAndExecutors.csproj" />
|
||||
<Project Path="samples/03-workflows/_StartHere/07_WriterCriticWorkflow/07_WriterCriticWorkflow.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/03-workflows/Evaluation/">
|
||||
<Project Path="samples/03-workflows/Evaluation/Evaluation_WorkflowEval/Evaluation_WorkflowEval.csproj" />
|
||||
<Project Path="samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/Evaluation_WorkflowExpectedOutputs.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/">
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/" />
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/invocations/" />
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/invocations/Using-Samples/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/invocations/Using-Samples/SimpleInvocationsAgent/SimpleInvocationsAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/" />
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/HostedChatClientAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/HostedFoundryAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/HostedFiles.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/HostedLocalTools.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct/HostedLocalCodeAct.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/HostedMcpTools.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/HostedMemoryAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/HostedObservability.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted_Shared_Contributor_Setup/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted_Shared_Contributor_Setup/Hosted_Shared_Contributor_Setup.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/HostedToolbox.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/Hosted-Toolbox-AuthPaths.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/HostedToolboxMcpSkills.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/HostedAzureSearchRag.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/HostedTextRag.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/HostedWorkflowSimple.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/Hosted-Toolbox-AuthPaths-Client/Hosted-Toolbox-AuthPaths-Client.csproj" />
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/SessionFilesClient.csproj" />
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/SimpleAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/HostedWorkflowHandoff.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/HostedAgentSkills.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/DurableAgents/" />
|
||||
<Folder Name="/Samples/04-hosting/DurableAgents/AzureFunctions/">
|
||||
<File Path="samples/04-hosting/DurableAgents/AzureFunctions/.editorconfig" />
|
||||
<File Path="samples/04-hosting/DurableAgents/AzureFunctions/README.md" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/AzureFunctions/08_ReliableStreaming/08_ReliableStreaming.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/DurableAgents/ConsoleApps/">
|
||||
<File Path="samples/04-hosting/DurableAgents/ConsoleApps/README.md" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/01_SingleAgent/01_SingleAgent.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/06_LongRunningTools/06_LongRunningTools.csproj" />
|
||||
<Project Path="samples/04-hosting/DurableAgents/ConsoleApps/07_ReliableStreaming/07_ReliableStreaming.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/02-agents/A2A/">
|
||||
<File Path="samples/02-agents/A2A/README.md" />
|
||||
<Project Path="samples/02-agents/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
|
||||
<Project Path="samples/02-agents/A2A/A2AAgent_PollingForTaskCompletion/A2AAgent_PollingForTaskCompletion.csproj" />
|
||||
<Project Path="samples/02-agents/A2A/A2AAgent_ProtocolSelection/A2AAgent_ProtocolSelection.csproj" />
|
||||
<Project Path="samples/02-agents/A2A/A2AAgent_StreamReconnection/A2AAgent_StreamReconnection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/05-end-to-end/">
|
||||
<Project Path="samples/05-end-to-end/AgentWithPurview/AgentWithPurview.csproj" />
|
||||
<Project Path="samples/05-end-to-end/M365Agent/M365Agent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/05-end-to-end/Evaluation/">
|
||||
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/Evaluation_ConversationSplits.csproj" />
|
||||
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/Evaluation_FoundryQuality.csproj" />
|
||||
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_FoundryRubric/Evaluation_FoundryRubric.csproj" />
|
||||
<Project Path="samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/Evaluation_MixedProviders.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/05-end-to-end/A2AClientServer/">
|
||||
<File Path="samples/05-end-to-end/A2AClientServer/README.md" />
|
||||
<Project Path="samples/05-end-to-end/A2AClientServer/A2AClient/A2AClient.csproj" />
|
||||
<Project Path="samples/05-end-to-end/A2AClientServer/A2AServer/A2AServer.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/05-end-to-end/AgentWebChat/">
|
||||
<Project Path="samples/05-end-to-end/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj" />
|
||||
<Project Path="samples/05-end-to-end/AgentWebChat/AgentWebChat.AppHost/AgentWebChat.AppHost.csproj" />
|
||||
<Project Path="samples/05-end-to-end/AgentWebChat/AgentWebChat.ServiceDefaults/AgentWebChat.ServiceDefaults.csproj" />
|
||||
<Project Path="samples/05-end-to-end/AgentWebChat/AgentWebChat.Web/AgentWebChat.Web.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/05-end-to-end/AGUIClientServer/">
|
||||
<File Path="samples/05-end-to-end/AGUIClientServer/README.md" />
|
||||
<Project Path="samples/05-end-to-end/AGUIClientServer/AGUIClient/AGUIClient.csproj" />
|
||||
<Project Path="samples/05-end-to-end/AGUIClientServer/AGUIDojoServer/AGUIDojoServer.csproj" />
|
||||
<Project Path="samples/05-end-to-end/AGUIClientServer/AGUIServer/AGUIServer.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/05-end-to-end/AspNetAgentAuthorization/">
|
||||
<File Path="samples/05-end-to-end/AspNetAgentAuthorization/docker-compose.yml" />
|
||||
<File Path="samples/05-end-to-end/AspNetAgentAuthorization/README.md" />
|
||||
<Project Path="samples/05-end-to-end/AspNetAgentAuthorization/RazorWebClient/RazorWebClient.csproj" />
|
||||
<Project Path="samples/05-end-to-end/AspNetAgentAuthorization/Service/Service.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".editorconfig" />
|
||||
<File Path=".gitignore" />
|
||||
<File Path="AGENTS.md" />
|
||||
<File Path="Directory.Build.props" />
|
||||
<File Path="Directory.Build.targets" />
|
||||
<File Path="Directory.Packages.props" />
|
||||
<File Path="global.json" />
|
||||
<File Path="nuget.config" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/.github/" />
|
||||
<Folder Name="/Solution Items/.github/upgrades/" />
|
||||
<Folder Name="/Solution Items/.github/upgrades/prompts/">
|
||||
<File Path="../.github/upgrades/prompts/SemanticKernelToAgentFramework.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/.github/workflows/">
|
||||
<File Path="../.github/workflows/dotnet-build-and-test.yml" />
|
||||
<File Path="../.github/workflows/dotnet-format.yml" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/demos/">
|
||||
<File Path="demos/.editorconfig" />
|
||||
<File Path="demos/Directory.Build.props" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/docs/" />
|
||||
<Folder Name="/Solution Items/docs/decisions/">
|
||||
<File Path="../docs/decisions/0001-agent-run-response.md" />
|
||||
<File Path="../docs/decisions/0002-agent-tools.md" />
|
||||
<File Path="../docs/decisions/0003-agent-opentelemetry-instrumentation.md" />
|
||||
<File Path="../docs/decisions/0004-foundry-sdk-extensions.md" />
|
||||
<File Path="../docs/decisions/0005-python-naming-conventions.md" />
|
||||
<File Path="../docs/decisions/0006-userapproval.md" />
|
||||
<File Path="../docs/decisions/0007-agent-filtering-middleware.md" />
|
||||
<File Path="../docs/decisions/0008-python-subpackages.md" />
|
||||
<File Path="../docs/decisions/0009-support-long-running-operations.md" />
|
||||
<File Path="../docs/decisions/0010-ag-ui-support.md" />
|
||||
<File Path="../docs/decisions/0011-create-get-agent-api.md" />
|
||||
<File Path="../docs/decisions/0012-python-typeddict-options.md" />
|
||||
<File Path="../docs/decisions/0013-python-get-response-simplification.md" />
|
||||
<File Path="../docs/decisions/0014-feature-collections.md" />
|
||||
<File Path="../docs/decisions/0015-agent-run-context.md" />
|
||||
<File Path="../docs/decisions/0016-python-context-middleware.md" />
|
||||
<File Path="../docs/decisions/0017-agent-additional-properties.md" />
|
||||
<File Path="../docs/decisions/0018-agentthread-serialization.md" />
|
||||
<File Path="../docs/decisions/adr-short-template.md" />
|
||||
<File Path="../docs/decisions/adr-template.md" />
|
||||
<File Path="../docs/decisions/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/eng/" />
|
||||
<Folder Name="/Solution Items/eng/MSBuild/">
|
||||
<File Path="eng/MSBuild/LegacySupport.props" />
|
||||
<File Path="eng/MSBuild/Shared.props" />
|
||||
<File Path="eng/MSBuild/Shared.targets" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/eng/scripts/">
|
||||
<File Path="eng/scripts/dotnet-check-coverage.ps1" />
|
||||
<File Path="eng/scripts/New-FilteredSolution.ps1" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/nuget/">
|
||||
<File Path="nuget/icon.png" />
|
||||
<File Path="nuget/nuget-package.props" />
|
||||
<File Path="nuget/NUGET.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/samples/">
|
||||
<File Path="samples/.editorconfig" />
|
||||
<File Path="samples/Directory.Build.props" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/" />
|
||||
<Folder Name="/Solution Items/src/LegacySupport/">
|
||||
<File Path="src/LegacySupport/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/CallerAttributes/">
|
||||
<File Path="src/LegacySupport/CallerAttributes/CallerArgumentExpressionAttribute.cs" />
|
||||
<File Path="src/LegacySupport/CallerAttributes/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/CompilerFeatureRequiredAttribute/">
|
||||
<File Path="src/LegacySupport/CompilerFeatureRequiredAttribute/CompilerFeatureRequiredAttribute.cs" />
|
||||
<File Path="src/LegacySupport/CompilerFeatureRequiredAttribute/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/DiagnosticAttributes/">
|
||||
<File Path="src/LegacySupport/DiagnosticAttributes/NullableAttributes.cs" />
|
||||
<File Path="src/LegacySupport/DiagnosticAttributes/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/DiagnosticClasses/">
|
||||
<File Path="src/LegacySupport/DiagnosticClasses/README.md" />
|
||||
<File Path="src/LegacySupport/DiagnosticClasses/UnreachableException.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/ExperimentalAttribute/">
|
||||
<File Path="src/LegacySupport/ExperimentalAttribute/ExperimentalAttribute.cs" />
|
||||
<File Path="src/LegacySupport/ExperimentalAttribute/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/IsExternalInit/">
|
||||
<File Path="src/LegacySupport/IsExternalInit/IsExternalInit.cs" />
|
||||
<File Path="src/LegacySupport/IsExternalInit/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/RequiredMemberAttribute/">
|
||||
<File Path="src/LegacySupport/RequiredMemberAttribute/README.md" />
|
||||
<File Path="src/LegacySupport/RequiredMemberAttribute/RequiredMemberAttribute.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/LegacySupport/TrimAttributes/">
|
||||
<File Path="src/LegacySupport/TrimAttributes/DynamicallyAccessedMembersAttribute.cs" />
|
||||
<File Path="src/LegacySupport/TrimAttributes/DynamicallyAccessedMemberTypes.cs" />
|
||||
<File Path="src/LegacySupport/TrimAttributes/README.md" />
|
||||
<File Path="src/LegacySupport/TrimAttributes/RequiresDynamicCodeAttribute.cs" />
|
||||
<File Path="src/LegacySupport/TrimAttributes/RequiresUnreferencedCodeAttribute.cs" />
|
||||
<File Path="src/LegacySupport/TrimAttributes/UnconditionalSuppressMessageAttribute.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/" />
|
||||
<Folder Name="/Solution Items/src/Shared/Demos/">
|
||||
<File Path="src/Shared/Demos/README.md" />
|
||||
<File Path="src/Shared/Demos/SampleEnvironment.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/DiagnosticIds/">
|
||||
<File Path="src/Shared/DiagnosticIds/DiagnosticsIds.cs" />
|
||||
<File Path="src/Shared/DiagnosticIds/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/IntegrationTests/">
|
||||
<File Path="src/Shared/IntegrationTests/AnthropicConfiguration.cs" />
|
||||
<File Path="src/Shared/IntegrationTests/AzureAIConfiguration.cs" />
|
||||
<File Path="src/Shared/IntegrationTests/Mem0Configuration.cs" />
|
||||
<File Path="src/Shared/IntegrationTests/OpenAIConfiguration.cs" />
|
||||
<File Path="src/Shared/IntegrationTests/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/IntegrationTestsAzureCredentials/">
|
||||
<File Path="src/Shared/IntegrationTestsAzureCredentials/README.md" />
|
||||
<File Path="src/Shared/IntegrationTestsAzureCredentials/TestAzureCliCredentials.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/Samples/">
|
||||
<File Path="src/Shared/Samples/BaseSample.cs" />
|
||||
<File Path="src/Shared/Samples/README.md" />
|
||||
<File Path="src/Shared/Samples/TestConfiguration.cs" />
|
||||
<File Path="src/Shared/Samples/TextOutputHelperExtensions.cs" />
|
||||
<File Path="src/Shared/Samples/XunitLogger.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/Redaction/">
|
||||
<File Path="src/Shared/Redaction/README.md" />
|
||||
<File Path="src/Shared/Redaction/ReplacingRedactor.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/Throw/">
|
||||
<File Path="src/Shared/Throw/README.md" />
|
||||
<File Path="src/Shared/Throw/Throw.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/StructuredOutput/">
|
||||
<File Path="src/Shared/StructuredOutput/StructuredOutputSchemaUtilities.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/Workflows/" />
|
||||
<Folder Name="/Solution Items/src/Shared/Workflows/Execution/">
|
||||
<File Path="src/Shared/Workflows/Execution/README.md" />
|
||||
<File Path="src/Shared/Workflows/Execution/WorkflowFactory.cs" />
|
||||
<File Path="src/Shared/Workflows/Execution/WorkflowRunner.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/Workflows/Settings/">
|
||||
<File Path="src/Shared/Workflows/Settings/Application.cs" />
|
||||
<File Path="src/Shared/Workflows/Settings/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/tests/">
|
||||
<File Path="tests/.editorconfig" />
|
||||
<File Path="tests/Directory.Build.props" />
|
||||
</Folder>
|
||||
<Folder Name="/src/">
|
||||
<Project Path="src/Aspire.Hosting.AgentFramework.DevUI/Aspire.Hosting.AgentFramework.DevUI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.GitHub.Copilot/Microsoft.Agents.AI.GitHub.Copilot.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.AspNetCore/Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hyperlight/Microsoft.Agents.AI.Hyperlight.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.LocalCodeAct/Microsoft.Agents.AI.LocalCodeAct.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Mcp/Microsoft.Agents.AI.Mcp.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Tools.Shell/Microsoft.Agents.AI.Tools.Shell.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.Foundry/Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Generators/Microsoft.Agents.AI.Workflows.Generators.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Tests/" />
|
||||
<Folder Name="/Tests/IntegrationTests/">
|
||||
<Project Path="tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj" />
|
||||
<Project Path="tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Foundry.Hosting.IntegrationTests.TestContainer/Foundry.Hosting.IntegrationTests.TestContainer.csproj" />
|
||||
<Project Path="tests/Foundry.Hosting.IntegrationTests/Foundry.Hosting.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Foundry.IntegrationTests/Foundry.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.GitHub.Copilot.IntegrationTests/Microsoft.Agents.AI.GitHub.Copilot.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hyperlight.IntegrationTests/Microsoft.Agents.AI.Hyperlight.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Tools.Shell.IntegrationTests/Microsoft.Agents.AI.Tools.Shell.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.csproj" />
|
||||
<Project Path="tests/OpenAIAssistant.IntegrationTests/OpenAIAssistant.IntegrationTests.csproj" />
|
||||
<Project Path="tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletion.IntegrationTests.csproj" />
|
||||
<Project Path="tests/OpenAIResponse.IntegrationTests/OpenAIResponse.IntegrationTests.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Tests/UnitTests/">
|
||||
<Project Path="tests/Aspire.Hosting.AgentFramework.DevUI.UnitTests/Aspire.Hosting.AgentFramework.DevUI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Harness.UnitTests/Microsoft.Agents.AI.Harness.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hyperlight.UnitTests/Microsoft.Agents.AI.Hyperlight.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests/Microsoft.Agents.AI.LocalCodeAct.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Mcp.UnitTests/Microsoft.Agents.AI.Mcp.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Tools.Shell.UnitTests/Microsoft.Agents.AI.Tools.Shell.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.UnitTests/Microsoft.Agents.AI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Valkey.UnitTests/Microsoft.Agents.AI.Valkey.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"solution": {
|
||||
"path": "agent-framework-dotnet.slnx",
|
||||
"projects": [
|
||||
"src\\Microsoft.Agents.AI.A2A\\Microsoft.Agents.AI.A2A.csproj",
|
||||
"src\\Microsoft.Agents.AI.Abstractions\\Microsoft.Agents.AI.Abstractions.csproj",
|
||||
"src\\Microsoft.Agents.AI.Anthropic\\Microsoft.Agents.AI.Anthropic.csproj",
|
||||
"src\\Microsoft.Agents.AI.GitHub.Copilot\\Microsoft.Agents.AI.GitHub.Copilot.csproj",
|
||||
"src\\Microsoft.Agents.AI.Harness\\Microsoft.Agents.AI.Harness.csproj",
|
||||
"src\\Microsoft.Agents.AI.AzureAI.Persistent\\Microsoft.Agents.AI.AzureAI.Persistent.csproj",
|
||||
"src\\Microsoft.Agents.AI.Foundry\\Microsoft.Agents.AI.Foundry.csproj",
|
||||
"src\\Microsoft.Agents.AI.Foundry.Hosting\\Microsoft.Agents.AI.Foundry.Hosting.csproj",
|
||||
"src\\Microsoft.Agents.AI.CopilotStudio\\Microsoft.Agents.AI.CopilotStudio.csproj",
|
||||
"src\\Microsoft.Agents.AI.CosmosNoSql\\Microsoft.Agents.AI.CosmosNoSql.csproj",
|
||||
"src\\Microsoft.Agents.AI.Declarative\\Microsoft.Agents.AI.Declarative.csproj",
|
||||
"src\\Microsoft.Agents.AI.DevUI\\Microsoft.Agents.AI.DevUI.csproj",
|
||||
"src\\Microsoft.Agents.AI.DurableTask\\Microsoft.Agents.AI.DurableTask.csproj",
|
||||
|
||||
"src\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj",
|
||||
"src\\Microsoft.Agents.AI.Hosting.A2A\\Microsoft.Agents.AI.Hosting.A2A.csproj",
|
||||
"src\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj",
|
||||
"src\\Microsoft.Agents.AI.Hosting.AspNetCore\\Microsoft.Agents.AI.Hosting.AspNetCore.csproj",
|
||||
"src\\Microsoft.Agents.AI.Hosting.AzureFunctions\\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj",
|
||||
"src\\Microsoft.Agents.AI.Hosting.OpenAI\\Microsoft.Agents.AI.Hosting.OpenAI.csproj",
|
||||
"src\\Microsoft.Agents.AI.Hosting\\Microsoft.Agents.AI.Hosting.csproj",
|
||||
"src\\Microsoft.Agents.AI.Mcp\\Microsoft.Agents.AI.Mcp.csproj",
|
||||
"src\\Microsoft.Agents.AI.Mem0\\Microsoft.Agents.AI.Mem0.csproj",
|
||||
"src\\Microsoft.Agents.AI.OpenAI\\Microsoft.Agents.AI.OpenAI.csproj",
|
||||
"src\\Microsoft.Agents.AI.Purview\\Microsoft.Agents.AI.Purview.csproj",
|
||||
"src\\Microsoft.Agents.AI.Tools.Shell\\Microsoft.Agents.AI.Tools.Shell.csproj",
|
||||
"src\\Microsoft.Agents.AI.Workflows.Declarative.Foundry\\Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj",
|
||||
"src\\Microsoft.Agents.AI.Workflows.Declarative.Mcp\\Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj",
|
||||
"src\\Microsoft.Agents.AI.Workflows.Declarative\\Microsoft.Agents.AI.Workflows.Declarative.csproj",
|
||||
"src\\Microsoft.Agents.AI.Workflows.Generators\\Microsoft.Agents.AI.Workflows.Generators.csproj",
|
||||
"src\\Microsoft.Agents.AI.Workflows\\Microsoft.Agents.AI.Workflows.csproj",
|
||||
"src\\Microsoft.Agents.AI\\Microsoft.Agents.AI.csproj",
|
||||
"src\\Aspire.Hosting.AgentFramework.DevUI\\Aspire.Hosting.AgentFramework.DevUI.csproj",
|
||||
"src\\Microsoft.Agents.AI.Hyperlight\\Microsoft.Agents.AI.Hyperlight.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project>
|
||||
<ItemGroup Condition="'$(InjectDiagnosticClassesOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\DiagnosticClasses\UnreachableException.cs" LinkBase="LegacySupport\DiagnosticClasses" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(InjectDiagnosticAttributesOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\DiagnosticAttributes\*.cs" LinkBase="LegacySupport\DiagnosticAttributes" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(InjectCallerAttributesOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\CallerAttributes\*.cs" LinkBase="LegacySupport\CallerAttributes" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(InjectExperimentalAttributeOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\ExperimentalAttribute\*.cs" LinkBase="LegacySupport\ExperimentalAttribute" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(InjectIsExternalInitOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\IsExternalInit\*.cs" LinkBase="LegacySupport\IsExternalInit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(InjectTrimAttributesOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\TrimAttributes\*.cs" LinkBase="LegacySupport\TrimAttributes" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(InjectRequiredMemberOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\RequiredMemberAttribute\*.cs" LinkBase="LegacySupport\RequiredMemberAttribute" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(InjectCompilerFeatureRequiredOnLegacy)' == 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\CompilerFeatureRequiredAttribute\*.cs" LinkBase="LegacySupport\CompilerFeatureRequiredAttribute" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,32 @@
|
||||
<Project>
|
||||
<ItemGroup Condition="'$(InjectSharedThrow)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Throw\*.cs" LinkBase="Shared\Throw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedSamples)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Samples\*.cs" LinkBase="Shared\Samples" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedIntegrationTestCode)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\IntegrationTests\*.cs" LinkBase="Shared\IntegrationTests" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedIntegrationTestAzureCredentialsCode)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\IntegrationTestsAzureCredentials\*.cs" LinkBase="Shared\IntegrationTestsAzureCredentials" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedWorkflowsExecution)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Workflows\Execution\*.cs" LinkBase="Shared\Workflows" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedWorkflowsSettings)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Workflows\Settings\*.cs" LinkBase="Shared\Workflows" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedFoundryAgents)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Foundry\Agents\*.cs" LinkBase="Shared\Foundry" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedStructuredOutput)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\StructuredOutput\*.cs" LinkBase="Shared\StructuredOutput" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedDiagnosticIds)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\DiagnosticIds\*.cs" LinkBase="Shared\DiagnosticIds" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(InjectSharedRedaction)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Redaction\*.cs" LinkBase="Shared\Redaction" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project>
|
||||
<!-- This configuration is required to automatically inject all dependencies for specific classes. -->
|
||||
<PropertyGroup Condition="'$(InjectSharedThrow)' == 'true'">
|
||||
<InjectCallerAttributesOnLegacy Condition="'$(InjectCallerAttributesOnLegacy)' == ''">true</InjectCallerAttributesOnLegacy>
|
||||
<InjectDiagnosticAttributesOnLegacy Condition="'$(InjectDiagnosticAttributesOnLegacy)' == ''">true</InjectDiagnosticAttributesOnLegacy>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates a filtered .slnx solution file by removing projects that don't match the specified criteria.
|
||||
|
||||
.DESCRIPTION
|
||||
Parses a .slnx solution file and applies one or more filters:
|
||||
- Removes projects that don't support the specified target framework (via MSBuild query).
|
||||
- Optionally removes all sample projects (under samples/).
|
||||
- Optionally filters test projects by name pattern (e.g., only *UnitTests*).
|
||||
Writes the filtered solution to the specified output path and prints the path.
|
||||
|
||||
.PARAMETER Solution
|
||||
Path to the source .slnx solution file.
|
||||
|
||||
.PARAMETER TargetFramework
|
||||
The target framework to filter by (e.g., net10.0, net472).
|
||||
|
||||
.PARAMETER Configuration
|
||||
Optional MSBuild configuration used when querying TargetFrameworks. Defaults to Debug.
|
||||
|
||||
.PARAMETER TestProjectNameIncludeFilter
|
||||
Optional wildcard pattern to filter test project names (e.g., *UnitTests*, *IntegrationTests*).
|
||||
When specified, only test projects whose filename matches this pattern are kept.
|
||||
|
||||
.PARAMETER TestProjectNameExcludeFilter
|
||||
Optional wildcard pattern(s) to exclude test projects by name (e.g., *DurableTask.IntegrationTests*).
|
||||
When specified, test projects whose filename matches any of these patterns are removed.
|
||||
Applied after TestProjectNameIncludeFilter. Can be a single string or an array of strings.
|
||||
|
||||
.PARAMETER ExcludeSamples
|
||||
When specified, removes all projects under the samples/ directory from the solution.
|
||||
|
||||
.PARAMETER OutputPath
|
||||
Optional output path for the filtered .slnx file. If not specified, a temp file is created.
|
||||
|
||||
.EXAMPLE
|
||||
# Generate a filtered solution and run tests
|
||||
$filtered = ./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net472
|
||||
dotnet test --solution $filtered --no-build -f net472
|
||||
|
||||
.EXAMPLE
|
||||
# Generate a solution with only unit test projects
|
||||
./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameIncludeFilter "*UnitTests*" -OutputPath filtered-unit.slnx
|
||||
|
||||
.EXAMPLE
|
||||
# Inline usage with dotnet test (PowerShell)
|
||||
dotnet test --solution (./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net472) --no-build -f net472
|
||||
|
||||
.EXAMPLE
|
||||
# Generate integration tests excluding DurableTask and AzureFunctions
|
||||
./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameIncludeFilter "*IntegrationTests*" -TestProjectNameExcludeFilter "*DurableTask.IntegrationTests*","*AzureFunctions.IntegrationTests*" -OutputPath filtered-other-integration.slnx
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Solution,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$TargetFramework,
|
||||
|
||||
[string]$Configuration = "Debug",
|
||||
|
||||
[string]$TestProjectNameIncludeFilter,
|
||||
|
||||
[string[]]$TestProjectNameExcludeFilter,
|
||||
|
||||
[switch]$ExcludeSamples,
|
||||
|
||||
[string]$OutputPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Resolve the solution path
|
||||
$solutionPath = Resolve-Path $Solution
|
||||
$solutionDir = Split-Path $solutionPath -Parent
|
||||
|
||||
if (-not $OutputPath) {
|
||||
$OutputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "filtered-$(Split-Path $solutionPath -Leaf)")
|
||||
}
|
||||
|
||||
# Parse the .slnx XML
|
||||
[xml]$slnx = Get-Content $solutionPath -Raw
|
||||
|
||||
$removed = @()
|
||||
$kept = @()
|
||||
|
||||
# Remove sample projects if requested
|
||||
if ($ExcludeSamples) {
|
||||
$sampleProjects = $slnx.SelectNodes("//Project[contains(@Path, 'samples/')]")
|
||||
foreach ($proj in $sampleProjects) {
|
||||
$projRelPath = $proj.GetAttribute("Path")
|
||||
Write-Verbose "Removing (sample): $projRelPath"
|
||||
$removed += $projRelPath
|
||||
$proj.ParentNode.RemoveChild($proj) | Out-Null
|
||||
}
|
||||
Write-Host "Removed $($sampleProjects.Count) sample project(s)." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Filter all remaining projects by target framework
|
||||
$allProjects = $slnx.SelectNodes("//Project")
|
||||
|
||||
foreach ($proj in $allProjects) {
|
||||
$projRelPath = $proj.GetAttribute("Path")
|
||||
$projFullPath = Join-Path $solutionDir $projRelPath
|
||||
$projFileName = Split-Path $projRelPath -Leaf
|
||||
$isTestProject = $projRelPath -like "*tests/*"
|
||||
|
||||
# Filter test projects by name pattern if specified
|
||||
if ($isTestProject -and $TestProjectNameIncludeFilter -and ($projFileName -notlike $TestProjectNameIncludeFilter)) {
|
||||
Write-Verbose "Removing (name filter): $projRelPath"
|
||||
$removed += $projRelPath
|
||||
$proj.ParentNode.RemoveChild($proj) | Out-Null
|
||||
continue
|
||||
}
|
||||
|
||||
# Exclude test projects matching any exclusion pattern
|
||||
if ($isTestProject -and $TestProjectNameExcludeFilter) {
|
||||
$excluded = $false
|
||||
foreach ($pattern in $TestProjectNameExcludeFilter) {
|
||||
if ($projFileName -like $pattern) {
|
||||
$excluded = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($excluded) {
|
||||
Write-Verbose "Removing (exclude filter): $projRelPath"
|
||||
$removed += $projRelPath
|
||||
$proj.ParentNode.RemoveChild($proj) | Out-Null
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $projFullPath)) {
|
||||
Write-Verbose "Project not found, keeping in solution: $projRelPath"
|
||||
$kept += $projRelPath
|
||||
continue
|
||||
}
|
||||
|
||||
# Query the project's target frameworks using MSBuild
|
||||
$targetFrameworks = & dotnet msbuild $projFullPath -getProperty:TargetFrameworks -p:Configuration=$Configuration -nologo 2>$null
|
||||
$targetFrameworks = $targetFrameworks.Trim()
|
||||
|
||||
if ($targetFrameworks -like "*$TargetFramework*") {
|
||||
Write-Verbose "Keeping: $projRelPath (targets: $targetFrameworks)"
|
||||
$kept += $projRelPath
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Removing: $projRelPath (targets: $targetFrameworks, missing: $TargetFramework)"
|
||||
$removed += $projRelPath
|
||||
$proj.ParentNode.RemoveChild($proj) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Write the filtered solution
|
||||
$slnx.Save($OutputPath)
|
||||
|
||||
# Report results to stderr so stdout is clean for piping
|
||||
Write-Host "Filtered solution written to: $OutputPath" -ForegroundColor Green
|
||||
if ($removed.Count -gt 0) {
|
||||
Write-Host "Removed $($removed.Count) project(s):" -ForegroundColor Yellow
|
||||
foreach ($r in $removed) {
|
||||
Write-Host " - $r" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
Write-Host "Kept $($kept.Count) project(s)." -ForegroundColor Green
|
||||
|
||||
# Output the path for piping
|
||||
Write-Output $OutputPath
|
||||
@@ -0,0 +1,82 @@
|
||||
param (
|
||||
[string]$JsonReportPath,
|
||||
[double]$CoverageThreshold
|
||||
)
|
||||
|
||||
$jsonContent = Get-Content $JsonReportPath -Raw | ConvertFrom-Json
|
||||
$coverageBelowThreshold = $false
|
||||
|
||||
$nonExperimentalAssemblies = [System.Collections.Generic.HashSet[string]]::new()
|
||||
|
||||
$assembliesCollection = @(
|
||||
'Microsoft.Agents.AI.Abstractions'
|
||||
'Microsoft.Agents.AI'
|
||||
)
|
||||
|
||||
foreach ($assembly in $assembliesCollection) {
|
||||
$nonExperimentalAssemblies.Add($assembly)
|
||||
}
|
||||
|
||||
function Get-FormattedValue {
|
||||
param (
|
||||
[float]$Coverage,
|
||||
[bool]$UseIcon = $false
|
||||
)
|
||||
$formattedNumber = "{0:N1}" -f $Coverage
|
||||
$icon = if (-not $UseIcon) { "" } elseif ($Coverage -ge $CoverageThreshold) { '✅' } else { '❌' }
|
||||
|
||||
return "$formattedNumber% $icon"
|
||||
}
|
||||
|
||||
$totallines = $jsonContent.summary.totallines
|
||||
$totalbranches = $jsonContent.summary.totalbranches
|
||||
$lineCoverage = $jsonContent.summary.linecoverage
|
||||
$branchCoverage = $jsonContent.summary.branchcoverage
|
||||
|
||||
$totalTableData = [PSCustomObject]@{
|
||||
'Metric' = 'Total Coverage'
|
||||
'Total Lines' = $totallines
|
||||
'Total Branches' = $totalbranches
|
||||
'Line Coverage' = Get-FormattedValue -Coverage $lineCoverage
|
||||
'Branch Coverage' = Get-FormattedValue -Coverage $branchCoverage
|
||||
}
|
||||
|
||||
$totalTableData | Format-Table -AutoSize
|
||||
|
||||
$assemblyTableData = @()
|
||||
|
||||
foreach ($assembly in $jsonContent.coverage.assemblies) {
|
||||
$assemblyName = $assembly.name
|
||||
$assemblyTotallines = $assembly.totallines
|
||||
$assemblyTotalbranches = $assembly.totalbranches
|
||||
$assemblyLineCoverage = $assembly.coverage
|
||||
$assemblyBranchCoverage = $assembly.branchcoverage
|
||||
|
||||
$isNonExperimentalAssembly = $nonExperimentalAssemblies -contains $assemblyName
|
||||
|
||||
$lineCoverageFailed = $assemblyLineCoverage -lt $CoverageThreshold -and $assemblyTotallines -gt 0
|
||||
$branchCoverageFailed = $assemblyBranchCoverage -lt $CoverageThreshold -and $assemblyTotalbranches -gt 0
|
||||
|
||||
if ($isNonExperimentalAssembly -and ($lineCoverageFailed -or $branchCoverageFailed)) {
|
||||
$coverageBelowThreshold = $true
|
||||
}
|
||||
|
||||
$assemblyTableData += [PSCustomObject]@{
|
||||
'Assembly Name' = $assemblyName
|
||||
'Total Lines' = $assemblyTotallines
|
||||
'Total Branches' = $assemblyTotalbranches
|
||||
'Line Coverage' = Get-FormattedValue -Coverage $assemblyLineCoverage -UseIcon $isNonExperimentalAssembly
|
||||
'Branch Coverage' = Get-FormattedValue -Coverage $assemblyBranchCoverage -UseIcon $isNonExperimentalAssembly
|
||||
}
|
||||
}
|
||||
|
||||
$sortedTable = $assemblyTableData | Sort-Object {
|
||||
$nonExperimentalAssemblies -contains $_.'Assembly Name'
|
||||
} -Descending
|
||||
|
||||
$sortedTable | Format-Table -AutoSize
|
||||
|
||||
if ($coverageBelowThreshold) {
|
||||
Write-Host "Code coverage is lower than defined threshold: $CoverageThreshold. Stopping the task."
|
||||
exit 1
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe console output with sample-name prefixes and colored status.
|
||||
/// </summary>
|
||||
internal sealed class ConsoleReporter
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Writes a complete prefixed line atomically to the console.
|
||||
/// </summary>
|
||||
public void WriteLineWithPrefix(string sampleName, string message, ConsoleColor? color = null)
|
||||
{
|
||||
lock (this._lock)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write($"[{sampleName}] ");
|
||||
if (color.HasValue)
|
||||
{
|
||||
Console.ForegroundColor = color.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
Console.WriteLine(message);
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the final summary table and elapsed time to the console.
|
||||
/// </summary>
|
||||
public void PrintSummary(
|
||||
IReadOnlyList<VerificationResult> orderedResults,
|
||||
IReadOnlyList<(string Name, string Reason)> skipped,
|
||||
TimeSpan elapsed)
|
||||
{
|
||||
var passCount = orderedResults.Count(r => r.Passed);
|
||||
var failCount = orderedResults.Count(r => !r.Passed);
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(new string('─', 60));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("SUMMARY");
|
||||
Console.ResetColor();
|
||||
|
||||
foreach (var result in orderedResults)
|
||||
{
|
||||
Console.ForegroundColor = result.Passed ? ConsoleColor.Green : ConsoleColor.Red;
|
||||
Console.Write(result.Passed ? " ✓ " : " ✗ ");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($"{result.SampleName}: {result.Summary}");
|
||||
}
|
||||
|
||||
foreach (var (name, reason) in skipped)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.Write(" ○ ");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($"{name}: Skipped — {reason}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.Write("Results: ");
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write($"{passCount} passed");
|
||||
Console.ResetColor();
|
||||
|
||||
if (failCount > 0)
|
||||
{
|
||||
Console.Write(", ");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Write($"{failCount} failed");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
if (skipped.Count > 0)
|
||||
{
|
||||
Console.Write(", ");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.Write($"{skipped.Count} skipped");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($"Elapsed: {elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Writes a CSV summary of sample verification results.
|
||||
/// </summary>
|
||||
internal static class CsvResultWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes the results to a CSV file at the specified path.
|
||||
/// </summary>
|
||||
public static async Task WriteAsync(
|
||||
string path,
|
||||
IReadOnlyList<VerificationResult> orderedResults,
|
||||
IReadOnlyList<(string Name, string Reason)> skipped,
|
||||
IReadOnlyList<SampleDefinition> samples)
|
||||
{
|
||||
var pathLookup = samples.ToDictionary(s => s.Name, s => s.ProjectPath);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Sample,ProjectPath,Status,FailedChecks,Failures");
|
||||
|
||||
foreach (var result in orderedResults)
|
||||
{
|
||||
var status = result.Passed ? "PASSED" : "FAILED";
|
||||
var failedChecks = result.Failures.Count;
|
||||
var failures = string.Join("; ", result.Failures);
|
||||
pathLookup.TryGetValue(result.SampleName, out var projectPath);
|
||||
sb.AppendLine($"{CsvEscape(result.SampleName)},{CsvEscape(projectPath ?? "")},{status},{failedChecks},{CsvEscape(failures)}");
|
||||
}
|
||||
|
||||
foreach (var (name, reason) in skipped)
|
||||
{
|
||||
pathLookup.TryGetValue(name, out var projectPath);
|
||||
sb.AppendLine($"{CsvEscape(name)},{CsvEscape(projectPath ?? "")},SKIPPED,0,{CsvEscape(reason)}");
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(path, sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes a value for CSV: wraps in quotes if it contains commas, quotes, or newlines.
|
||||
/// </summary>
|
||||
private static string CsvEscape(string value)
|
||||
{
|
||||
if (value.Contains('"') || value.Contains(',') || value.Contains('\n') || value.Contains('\r'))
|
||||
{
|
||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the expected behavior for each sample in 01-get-started.
|
||||
/// </summary>
|
||||
internal static class GetStartedSamples
|
||||
{
|
||||
public static IReadOnlyList<SampleDefinition> All { get; } =
|
||||
[
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "05_first_workflow",
|
||||
ProjectPath = "samples/01-get-started/05_first_workflow",
|
||||
RequiredEnvironmentVariables = [],
|
||||
IsDeterministic = true,
|
||||
MustContain =
|
||||
[
|
||||
"UppercaseExecutor: HELLO, WORLD!",
|
||||
"ReverseTextExecutor: !DLROW ,OLLEH",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "01_hello_agent",
|
||||
ProjectPath = "samples/01-get-started/01_hello_agent",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should contain a joke about a pirate.",
|
||||
"There should be two separate joke responses — one from a non-streaming call and one from a streaming call.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "02_add_tools",
|
||||
ProjectPath = "samples/01-get-started/02_add_tools",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
MustContain = [],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should contain information about the weather in Amsterdam.",
|
||||
"The response should mention that it is cloudy with a high of 15°C (or equivalent), since this comes from a tool that returns a canned response.",
|
||||
"There should be two responses — one from a non-streaming call and one from a streaming call.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "03_multi_turn",
|
||||
ProjectPath = "samples/01-get-started/03_multi_turn",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should contain a joke about a pirate.",
|
||||
"After the initial joke, there should be a modified version that includes emojis and is told in the voice of a pirate's parrot.",
|
||||
"The pattern repeats: first a non-streaming pirate joke + parrot version, then a streaming pirate joke + parrot version.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "04_memory",
|
||||
ProjectPath = "samples/01-get-started/04_memory",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
MustContain =
|
||||
[
|
||||
">> Use session with blank memory",
|
||||
">> Use deserialized session with previously created memories",
|
||||
">> Read memories using memory component",
|
||||
"MEMORY - User Name:",
|
||||
"MEMORY - User Age:",
|
||||
">> Use new session with previously created memories",
|
||||
],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"In the 'Use session with blank memory' section, the agent should respond to the user's messages. It may ask for the user's name or age if not yet known.",
|
||||
"In the 'Use deserialized session with previously created memories' section, the agent should correctly recall that the user's name is Ruaidhrí and age is 20.",
|
||||
"The 'MEMORY - User Name:' line should show 'Ruaidhrí' (or a close transliteration).",
|
||||
"The 'MEMORY - User Age:' line should show '20'.",
|
||||
"In the 'Use new session with previously created memories' section, the agent should know the user's name and age from the transferred memory.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "06_host_your_agent",
|
||||
ProjectPath = "samples/01-get-started/06_host_your_agent",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
SkipReason = "Requires Azure Functions Core Tools runtime and starts a web server.",
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Incrementally writes a sequential (non-interleaved) log file, appending after each sample completes.
|
||||
/// Thread-safe: multiple parallel tasks may call write methods concurrently.
|
||||
/// </summary>
|
||||
internal sealed class LogFileWriter : IDisposable
|
||||
{
|
||||
private readonly string _path;
|
||||
private readonly SemaphoreSlim _writeLock = new(1, 1);
|
||||
|
||||
public LogFileWriter(string path)
|
||||
{
|
||||
this._path = path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
this._writeLock.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the log file header. Call once at the start of the run.
|
||||
/// </summary>
|
||||
public async Task WriteHeaderAsync()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Sample Verification Log — {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
|
||||
sb.AppendLine(new string('═', 72));
|
||||
sb.AppendLine();
|
||||
|
||||
await File.WriteAllTextAsync(this._path, sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a skipped-sample entry to the log file.
|
||||
/// </summary>
|
||||
public async Task WriteSkippedAsync(string name, string reason)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"── {name} ──");
|
||||
sb.AppendLine($"Status: SKIPPED — {reason}");
|
||||
sb.AppendLine();
|
||||
|
||||
await this.AppendAsync(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a completed sample's full output section to the log file.
|
||||
/// </summary>
|
||||
public async Task WriteSampleResultAsync(VerificationResult result)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(new string('─', 72));
|
||||
sb.AppendLine($"── {result.SampleName} ──");
|
||||
sb.AppendLine($"Status: {(result.Passed ? "PASSED" : "FAILED")}");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var line in result.LogLines)
|
||||
{
|
||||
sb.AppendLine(line);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result.Stdout))
|
||||
{
|
||||
sb.AppendLine("--- stdout ---");
|
||||
sb.AppendLine(result.Stdout.TrimEnd());
|
||||
sb.AppendLine("--- end stdout ---");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result.Stderr))
|
||||
{
|
||||
sb.AppendLine("--- stderr ---");
|
||||
sb.AppendLine(result.Stderr.TrimEnd());
|
||||
sb.AppendLine("--- end stderr ---");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
if (result.Failures.Count > 0)
|
||||
{
|
||||
sb.AppendLine("Failures:");
|
||||
foreach (var failure in result.Failures)
|
||||
{
|
||||
sb.AppendLine($" ✗ {failure}");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
if (result.AIReasoning is not null)
|
||||
{
|
||||
sb.AppendLine("AI Reasoning:");
|
||||
sb.AppendLine(result.AIReasoning);
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
await this.AppendAsync(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the final summary section and elapsed time to the log file.
|
||||
/// </summary>
|
||||
public async Task WriteSummaryAsync(
|
||||
IReadOnlyList<VerificationResult> orderedResults,
|
||||
IReadOnlyList<(string Name, string Reason)> skipped,
|
||||
TimeSpan elapsed)
|
||||
{
|
||||
var passCount = orderedResults.Count(r => r.Passed);
|
||||
var failCount = orderedResults.Count(r => !r.Passed);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(new string('═', 72));
|
||||
sb.AppendLine("SUMMARY");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var result in orderedResults)
|
||||
{
|
||||
sb.AppendLine($" {(result.Passed ? "✓" : "✗")} {result.SampleName}: {result.Summary}");
|
||||
}
|
||||
|
||||
foreach (var (name, reason) in skipped)
|
||||
{
|
||||
sb.AppendLine($" ○ {name}: Skipped — {reason}");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"Results: {passCount} passed{(failCount > 0 ? $", {failCount} failed" : "")}{(skipped.Count > 0 ? $", {skipped.Count} skipped" : "")}");
|
||||
sb.AppendLine($"Elapsed: {elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}");
|
||||
|
||||
await this.AppendAsync(sb.ToString());
|
||||
}
|
||||
|
||||
private async Task AppendAsync(string text)
|
||||
{
|
||||
await this._writeLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
await File.AppendAllTextAsync(this._path, text);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._writeLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Markdown summary of sample verification results.
|
||||
/// </summary>
|
||||
internal static class MarkdownResultWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes the results to a Markdown file at the specified path.
|
||||
/// </summary>
|
||||
public static async Task WriteAsync(
|
||||
string path,
|
||||
IReadOnlyList<VerificationResult> orderedResults,
|
||||
IReadOnlyList<(string Name, string Reason)> skipped,
|
||||
TimeSpan elapsed)
|
||||
{
|
||||
var passCount = orderedResults.Count(r => r.Passed);
|
||||
var failCount = orderedResults.Count(r => !r.Passed);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("# Sample Verification Results");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**{passCount} passed, {failCount} failed, {skipped.Count} skipped** | Elapsed: {elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}");
|
||||
sb.AppendLine();
|
||||
|
||||
// Results table
|
||||
sb.AppendLine("| Sample | Status | Failed Checks | Failures |");
|
||||
sb.AppendLine("|--------|--------|---------------|----------|");
|
||||
|
||||
foreach (var result in orderedResults)
|
||||
{
|
||||
var status = result.Passed ? "✅ PASSED" : "❌ FAILED";
|
||||
var failedChecks = result.Failures.Count;
|
||||
var failures = MdEscape(string.Join("; ", result.Failures));
|
||||
sb.AppendLine($"| {MdEscape(result.SampleName)} | {status} | {failedChecks} | {failures} |");
|
||||
}
|
||||
|
||||
foreach (var (name, reason) in skipped)
|
||||
{
|
||||
sb.AppendLine($"| {MdEscape(name)} | ⏭️ SKIPPED | 0 | {MdEscape(reason)} |");
|
||||
}
|
||||
|
||||
// Collapsible AI reasoning details for failures
|
||||
var failures2 = orderedResults.Where(r => !r.Passed && !string.IsNullOrEmpty(r.AIReasoning)).ToList();
|
||||
if (failures2.Count > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("## Failure Details");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var result in failures2)
|
||||
{
|
||||
sb.AppendLine($"<details><summary><strong>{HtmlEscape(result.SampleName)}</strong></summary>");
|
||||
sb.AppendLine();
|
||||
if (result.Failures.Count > 0)
|
||||
{
|
||||
foreach (var failure in result.Failures)
|
||||
{
|
||||
sb.AppendLine($"- {MdEscape(failure)}");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine("**AI Reasoning:**");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("```");
|
||||
sb.AppendLine(result.AIReasoning);
|
||||
sb.AppendLine("```");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("</details>");
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(path, sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes pipe characters and newlines for use inside Markdown table cells.
|
||||
/// </summary>
|
||||
private static string MdEscape(string value)
|
||||
{
|
||||
return value.Replace("|", "\\|").Replace("\n", " ").Replace("\r", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes HTML special characters for use inside HTML tags.
|
||||
/// </summary>
|
||||
private static string HtmlEscape(string value)
|
||||
{
|
||||
return value.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This tool runs the 01-get-started, 02-agents, and 03-workflows samples and verifies their output.
|
||||
// Deterministic samples are verified with exact string matching.
|
||||
// Non-deterministic (LLM) samples are verified using an agent-framework agent.
|
||||
//
|
||||
// Usage:
|
||||
// dotnet run # Run all samples
|
||||
// dotnet run -- 01_hello_agent 05_first_workflow # Run specific samples by name
|
||||
// dotnet run -- --category 01-get-started # Run the 01-get-started category
|
||||
// dotnet run -- --category 02-agents # Run the 02-agents category
|
||||
// dotnet run -- --category 03-workflows # Run the 03-workflows category
|
||||
// dotnet run -- --parallel 16 # Run up to 16 samples concurrently
|
||||
// dotnet run -- --log results.log # Write sequential log to file
|
||||
// dotnet run -- --csv results.csv # Write CSV summary to file
|
||||
// dotnet run -- --md results.md # Write Markdown summary to file
|
||||
// dotnet run -- --build # Build samples during run (default: --no-build)
|
||||
// Note: By default, this tool expects sample build outputs to already exist.
|
||||
// Pre-build the solution before running, or pass --build to avoid missing build output failures.
|
||||
//
|
||||
// Required environment variables (for AI-powered verification):
|
||||
// FOUNDRY_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
// FOUNDRY_MODEL — Model deployment name (optional, defaults to gpt-5.4-mini)
|
||||
|
||||
using System.Diagnostics;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using VerifySamples;
|
||||
|
||||
var options = VerifyOptions.Parse(args);
|
||||
if (options is null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
// Resolve the dotnet/ root directory (verify-samples is at dotnet/eng/verify-samples/)
|
||||
var dotnetRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
|
||||
if (!File.Exists(Path.Combine(dotnetRoot, "agent-framework-dotnet.slnx")))
|
||||
{
|
||||
dotnetRoot = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", ".."));
|
||||
}
|
||||
|
||||
// Set up the AI verifier
|
||||
var foundryEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT");
|
||||
var foundryModel = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
AIAgent? verifierAgent = null;
|
||||
if (!string.IsNullOrEmpty(foundryEndpoint))
|
||||
{
|
||||
verifierAgent = new AIProjectClient(new Uri(foundryEndpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(
|
||||
model: foundryModel,
|
||||
instructions: """
|
||||
You are a test output verifier. You will be given:
|
||||
1. The actual stdout output of a program
|
||||
2. The stderr output (if any)
|
||||
3. A list of expectations about what the output should contain or demonstrate
|
||||
|
||||
Your job is to determine whether the actual output satisfies each expectation.
|
||||
Be reasonable — the output comes from an LLM so exact wording won't match, but the
|
||||
semantic intent should be clearly satisfied.
|
||||
|
||||
In your response, you MUST:
|
||||
- Always provide ai_reasoning with a brief overall assessment.
|
||||
- Always provide exactly one entry in expectation_results for each expectation,
|
||||
in the same order as the input list.
|
||||
- For each expectation_results entry, echo the expectation text in the expectation
|
||||
field and explain your assessment in the detail field, citing evidence from the output.
|
||||
""",
|
||||
name: "OutputVerifier");
|
||||
}
|
||||
|
||||
// Set up optional log file writer
|
||||
LogFileWriter? logWriter = null;
|
||||
if (options.LogFilePath is not null)
|
||||
{
|
||||
logWriter = new LogFileWriter(options.LogFilePath);
|
||||
await logWriter.WriteHeaderAsync();
|
||||
}
|
||||
|
||||
Console.WriteLine($"Foundry endpoint: {foundryEndpoint ?? "(not set — AI verification disabled)"}, Model: {foundryModel}");
|
||||
|
||||
try
|
||||
{
|
||||
// Run all samples
|
||||
var reporter = new ConsoleReporter();
|
||||
var verifier = new SampleVerifier(verifierAgent);
|
||||
var orchestrator = new VerificationOrchestrator(verifier, reporter, dotnetRoot, TimeSpan.FromMinutes(3), logWriter, buildSamples: options.BuildSamples);
|
||||
|
||||
var run = await orchestrator.RunAllAsync(options.Samples, options.MaxParallelism);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
// Print summary
|
||||
var orderedResults = run.SampleOrder
|
||||
.Where(run.Results.ContainsKey)
|
||||
.Select(name => run.Results[name])
|
||||
.ToList();
|
||||
|
||||
reporter.PrintSummary(orderedResults, run.Skipped, stopwatch.Elapsed);
|
||||
|
||||
// Write log file summary
|
||||
if (logWriter is not null)
|
||||
{
|
||||
await logWriter.WriteSummaryAsync(orderedResults, run.Skipped, stopwatch.Elapsed);
|
||||
Console.WriteLine($"Log written to: {options.LogFilePath}");
|
||||
}
|
||||
|
||||
// Write CSV summary
|
||||
if (options.CsvFilePath is not null)
|
||||
{
|
||||
await CsvResultWriter.WriteAsync(options.CsvFilePath, orderedResults, run.Skipped, options.Samples);
|
||||
Console.WriteLine($"CSV written to: {options.CsvFilePath}");
|
||||
}
|
||||
|
||||
// Write Markdown summary
|
||||
if (options.MarkdownFilePath is not null)
|
||||
{
|
||||
await MarkdownResultWriter.WriteAsync(options.MarkdownFilePath, orderedResults, run.Skipped, stopwatch.Elapsed);
|
||||
Console.WriteLine($"Markdown written to: {options.MarkdownFilePath}");
|
||||
}
|
||||
|
||||
return orderedResults.Any(r => !r.Passed) ? 1 : 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
logWriter?.Dispose();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a sample to verify, including its expected output.
|
||||
/// </summary>
|
||||
internal sealed class SampleDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Display name for the sample (e.g., "01_hello_agent").
|
||||
/// </summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Relative path from the dotnet/ directory to the sample project directory.
|
||||
/// </summary>
|
||||
public required string ProjectPath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Environment variables that the sample requires for a meaningful run.
|
||||
/// The runner checks these before running and will skip the sample if any are unset,
|
||||
/// recording a skip reason that indicates which required variables are missing.
|
||||
/// </summary>
|
||||
public string[] RequiredEnvironmentVariables { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Environment variables that the sample can use but typically has fallbacks or defaults for.
|
||||
/// If these are not set, the sample might prompt or behave interactively, which could cause
|
||||
/// automated verification to hang. The runner checks these and skips the sample if they are unset
|
||||
/// to avoid non-deterministic or blocking behavior in automated runs.
|
||||
/// </summary>
|
||||
public string[] OptionalEnvironmentVariables { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// If set, the sample is skipped with this reason.
|
||||
/// Use only for structural reasons (e.g., web server, multi-process, needs external service).
|
||||
/// Do NOT use for missing environment variables — those are checked dynamically.
|
||||
/// </summary>
|
||||
public string? SkipReason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Substrings that must appear in stdout for the sample to pass.
|
||||
/// Used for deterministic verification.
|
||||
/// </summary>
|
||||
public string[] MustContain { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Substrings that must not appear in stdout for the sample to pass.
|
||||
/// </summary>
|
||||
public string[] MustNotContain { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// If true, <see cref="MustContain"/> entries cover the entire expected output —
|
||||
/// no AI verification is needed.
|
||||
/// </summary>
|
||||
public bool IsDeterministic { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Natural-language description of what the sample output should look like.
|
||||
/// Used by the AI verifier for non-deterministic samples.
|
||||
/// Each entry describes one aspect of the expected output that should be verified.
|
||||
/// </summary>
|
||||
public string[] ExpectedOutputDescription { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Sequence of stdin inputs to feed to the sample process.
|
||||
/// Each entry is written as a line (followed by newline) to the process stdin.
|
||||
/// A <c>null</c> entry inserts a delay without writing anything.
|
||||
/// Inputs are sent with a short delay between each to allow the process to prompt.
|
||||
/// </summary>
|
||||
public string?[] Inputs { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Delay in milliseconds between each input line. Default is 2000ms.
|
||||
/// Increase for samples that need more time between prompts (e.g., LLM calls between inputs).
|
||||
/// </summary>
|
||||
public int InputDelayMs { get; init; } = 2000;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Result of running a sample process.
|
||||
/// </summary>
|
||||
internal sealed record SampleRunResult(
|
||||
string Stdout,
|
||||
string Stderr,
|
||||
int ExitCode,
|
||||
TimeSpan Elapsed);
|
||||
|
||||
/// <summary>
|
||||
/// Runs a sample project via <c>dotnet run</c> and captures its output.
|
||||
/// </summary>
|
||||
internal static class SampleRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs <c>dotnet run --framework net10.0</c> in the given project directory.
|
||||
/// When <paramref name="build"/> is false (the default), <c>--no-build</c> is passed
|
||||
/// to skip building, assuming the project was pre-built.
|
||||
/// </summary>
|
||||
public static Task<SampleRunResult> RunAsync(
|
||||
string projectPath,
|
||||
TimeSpan timeout,
|
||||
bool build = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> RunAsync(projectPath, DotnetRunArgs(build), timeout, inputs: null, inputDelayMs: 0, cancellationToken: cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Runs <c>dotnet run --framework net10.0</c> with stdin inputs.
|
||||
/// When <paramref name="build"/> is false (the default), <c>--no-build</c> is passed
|
||||
/// to skip building, assuming the project was pre-built.
|
||||
/// </summary>
|
||||
public static Task<SampleRunResult> RunAsync(
|
||||
string projectPath,
|
||||
TimeSpan timeout,
|
||||
string?[]? inputs,
|
||||
int inputDelayMs = 2000,
|
||||
bool build = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> RunAsync(projectPath, DotnetRunArgs(build), timeout, inputs, inputDelayMs, cancellationToken);
|
||||
|
||||
private static string DotnetRunArgs(bool build) =>
|
||||
$"run {(build ? "" : "--no-build")} --framework net10.0";
|
||||
|
||||
/// <summary>
|
||||
/// Runs an arbitrary <c>dotnet</c> command in the given working directory.
|
||||
/// </summary>
|
||||
public static async Task<SampleRunResult> RunAsync(
|
||||
string workingDirectory,
|
||||
string dotnetArgs,
|
||||
TimeSpan timeout,
|
||||
string?[]? inputs = null,
|
||||
int inputDelayMs = 0,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = dotnetArgs,
|
||||
WorkingDirectory = workingDirectory,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = inputs is { Length: > 0 },
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
using var process = new Process { StartInfo = psi };
|
||||
process.Start();
|
||||
|
||||
var stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
|
||||
var stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
|
||||
|
||||
// Feed stdin inputs with delays if configured
|
||||
if (inputs is { Length: > 0 })
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var input in inputs)
|
||||
{
|
||||
await Task.Delay(inputDelayMs, cancellationToken);
|
||||
if (input is not null)
|
||||
{
|
||||
await process.StandardInput.WriteLineAsync(input.AsMemory(), cancellationToken);
|
||||
await process.StandardInput.FlushAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
process.StandardInput.Close();
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or ObjectDisposedException or OperationCanceledException)
|
||||
{
|
||||
// Process may have exited before all inputs were sent
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(timeout);
|
||||
|
||||
try
|
||||
{
|
||||
await process.WaitForExitAsync(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Timeout — kill the process
|
||||
try
|
||||
{
|
||||
process.Kill(entireProcessTree: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
return new SampleRunResult(
|
||||
Stdout: await stdoutTask,
|
||||
Stderr: $"TIMEOUT: Sample did not complete within {timeout.TotalSeconds}s.\n{await stderrTask}",
|
||||
ExitCode: -1,
|
||||
Elapsed: sw.Elapsed);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
return new SampleRunResult(
|
||||
Stdout: await stdoutTask,
|
||||
Stderr: await stderrTask,
|
||||
ExitCode: process.ExitCode,
|
||||
Elapsed: sw.Elapsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies sample output using deterministic checks and an AI agent
|
||||
/// for non-deterministic output validation.
|
||||
/// </summary>
|
||||
internal sealed class SampleVerifier
|
||||
{
|
||||
private readonly AIAgent? _verifierAgent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a verifier. If <paramref name="verifierAgent"/> is provided,
|
||||
/// AI-based verification is available for non-deterministic samples.
|
||||
/// </summary>
|
||||
public SampleVerifier(AIAgent? verifierAgent = null)
|
||||
{
|
||||
this._verifierAgent = verifierAgent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the output of a sample run against its definition.
|
||||
/// </summary>
|
||||
public async Task<VerificationResult> VerifyAsync(SampleDefinition sample, SampleRunResult run)
|
||||
{
|
||||
var failures = new List<string>();
|
||||
|
||||
// 1. Exit code check
|
||||
if (run.ExitCode != 0)
|
||||
{
|
||||
failures.Add($"Exit code was {run.ExitCode}, expected 0. Stderr: {Truncate(run.Stderr, 500)}");
|
||||
}
|
||||
|
||||
// 2. Must-contain checks
|
||||
foreach (var expected in sample.MustContain)
|
||||
{
|
||||
if (!run.Stdout.Contains(expected, StringComparison.Ordinal))
|
||||
{
|
||||
failures.Add($"Output missing expected substring: \"{expected}\"");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Must-not-contain checks
|
||||
foreach (var unexpected in sample.MustNotContain)
|
||||
{
|
||||
if (run.Stdout.Contains(unexpected, StringComparison.Ordinal))
|
||||
{
|
||||
failures.Add($"Output contains unexpected substring: \"{unexpected}\"");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. AI verification for non-deterministic samples
|
||||
string? aiReasoning = null;
|
||||
if (!sample.IsDeterministic && sample.ExpectedOutputDescription.Length > 0)
|
||||
{
|
||||
if (this._verifierAgent is null)
|
||||
{
|
||||
failures.Add("AI verification required but no AI agent configured (missing AZURE_OPENAI_ENDPOINT).");
|
||||
}
|
||||
else
|
||||
{
|
||||
var aiResult = await this.VerifyWithAIAsync(run.Stdout, run.Stderr, sample.ExpectedOutputDescription);
|
||||
aiReasoning = aiResult.Reasoning;
|
||||
|
||||
foreach (var unmet in aiResult.UnmetExpectations)
|
||||
{
|
||||
failures.Add($"AI expectation not met: {unmet}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool passed = failures.Count == 0;
|
||||
return new VerificationResult
|
||||
{
|
||||
SampleName = sample.Name,
|
||||
Passed = passed,
|
||||
Summary = passed ? "All checks passed" : $"{failures.Count} check(s) failed",
|
||||
Failures = failures,
|
||||
AIReasoning = aiReasoning,
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(string Reasoning, List<string> UnmetExpectations)> VerifyWithAIAsync(
|
||||
string stdout,
|
||||
string stderr,
|
||||
string[] expectations)
|
||||
{
|
||||
var expectationList = string.Join("\n", expectations.Select((e, i) => $" {i + 1}. {e}"));
|
||||
|
||||
var stderrSection = string.IsNullOrWhiteSpace(stderr)
|
||||
? ""
|
||||
: $"""
|
||||
|
||||
Stderr output:
|
||||
---
|
||||
{Truncate(stderr, 2000)}
|
||||
---
|
||||
""";
|
||||
|
||||
var prompt = $"""
|
||||
Actual program output:
|
||||
---
|
||||
{Truncate(stdout, 4000)}
|
||||
---
|
||||
{stderrSection}
|
||||
Expectations to verify:
|
||||
{expectationList}
|
||||
|
||||
Does the output satisfy all expectations?
|
||||
""";
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this._verifierAgent!.RunAsync<AIVerificationResponse>(prompt);
|
||||
var result = response.Result;
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
return ($"AI verification returned null result. Raw: {response.Text}", ["AI verification returned null result."]);
|
||||
}
|
||||
|
||||
var reasoning = string.IsNullOrWhiteSpace(result.AIReasoning)
|
||||
? "(no reasoning provided)"
|
||||
: result.AIReasoning;
|
||||
|
||||
// Collect unmet expectations as individual failures
|
||||
var unmet = new List<string>();
|
||||
if (result.ExpectationResults is { Count: > 0 })
|
||||
{
|
||||
foreach (var er in result.ExpectationResults.Where(er => !er.Met))
|
||||
{
|
||||
var detail = string.IsNullOrWhiteSpace(er.Detail) ? er.Expectation : $"{er.Expectation} — {er.Detail}";
|
||||
unmet.Add(detail ?? "Unknown expectation");
|
||||
}
|
||||
|
||||
// If the model flagged overall failure but all individual expectations were met,
|
||||
// still treat as failure using the overall reasoning.
|
||||
if (unmet.Count == 0 && !result.Pass)
|
||||
{
|
||||
unmet.Add(reasoning);
|
||||
}
|
||||
}
|
||||
else if (!result.Pass)
|
||||
{
|
||||
// Fallback: no per-expectation detail but overall pass is false
|
||||
unmet.Add(reasoning);
|
||||
}
|
||||
|
||||
return (reasoning, unmet);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ($"AI verification error: {ex.Message}", [$"AI verification error: {ex.Message}"]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string Truncate(string text, int maxLength)
|
||||
=> text.Length <= maxLength ? text : text[..maxLength] + "... (truncated)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Structured response from the AI verification agent.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by JSON deserialization via RunAsync<T>.")]
|
||||
internal sealed class AIVerificationResponse
|
||||
{
|
||||
/// <summary>Whether all expectations were met.</summary>
|
||||
[JsonPropertyName("pass")]
|
||||
public bool Pass { get; set; }
|
||||
|
||||
/// <summary>Brief explanation of the overall assessment.</summary>
|
||||
[JsonPropertyName("ai_reasoning")]
|
||||
[Description("Always required. Brief explanation of the overall assessment, covering all expectations.")]
|
||||
public string AIReasoning { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Per-expectation results.</summary>
|
||||
[JsonPropertyName("expectation_results")]
|
||||
[Description("Always required. One entry per expectation, in the same order as the input list.")]
|
||||
public List<ExpectationResult> ExpectationResults { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result for an individual expectation check.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by JSON deserialization via RunAsync<T>.")]
|
||||
internal sealed class ExpectationResult
|
||||
{
|
||||
/// <summary>The expectation text that was evaluated.</summary>
|
||||
[JsonPropertyName("expectation")]
|
||||
[Description("Echo back the expectation text being evaluated.")]
|
||||
public string Expectation { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Whether this expectation was met.</summary>
|
||||
[JsonPropertyName("met")]
|
||||
public bool Met { get; set; }
|
||||
|
||||
/// <summary>Detail about how the expectation was or was not met.</summary>
|
||||
[JsonPropertyName("detail")]
|
||||
[Description("Explain how the expectation was or was not met, citing specific evidence from the output.")]
|
||||
public string Detail { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Orchestrates sample verification: filters, runs in parallel, and collects results.
|
||||
/// </summary>
|
||||
internal sealed class VerificationOrchestrator
|
||||
{
|
||||
private readonly SampleVerifier _verifier;
|
||||
private readonly ConsoleReporter _reporter;
|
||||
private readonly LogFileWriter? _logWriter;
|
||||
private readonly string _dotnetRoot;
|
||||
private readonly TimeSpan _timeout;
|
||||
private readonly bool _buildSamples;
|
||||
|
||||
public VerificationOrchestrator(
|
||||
SampleVerifier verifier,
|
||||
ConsoleReporter reporter,
|
||||
string dotnetRoot,
|
||||
TimeSpan timeout,
|
||||
LogFileWriter? logWriter = null,
|
||||
bool buildSamples = false)
|
||||
{
|
||||
this._verifier = verifier;
|
||||
this._reporter = reporter;
|
||||
this._logWriter = logWriter;
|
||||
this._dotnetRoot = dotnetRoot;
|
||||
this._timeout = timeout;
|
||||
this._buildSamples = buildSamples;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The result of running all samples through the orchestrator.
|
||||
/// </summary>
|
||||
internal sealed record RunAllResult(
|
||||
ConcurrentDictionary<string, VerificationResult> Results,
|
||||
List<(string Name, string Reason)> Skipped,
|
||||
List<string> SampleOrder);
|
||||
|
||||
/// <summary>
|
||||
/// Filters samples, runs the runnable ones in parallel, and returns all results.
|
||||
/// </summary>
|
||||
public async Task<RunAllResult> RunAllAsync(
|
||||
IReadOnlyList<SampleDefinition> samples,
|
||||
int maxParallelism)
|
||||
{
|
||||
var skipped = new List<(string Name, string Reason)>();
|
||||
var runnableSamples = new List<SampleDefinition>();
|
||||
var sampleOrder = new List<string>();
|
||||
|
||||
// Separate samples into skipped and runnable
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
sampleOrder.Add(sample.Name);
|
||||
|
||||
if (sample.SkipReason is not null)
|
||||
{
|
||||
skipped.Add((sample.Name, sample.SkipReason));
|
||||
this._reporter.WriteLineWithPrefix(sample.Name, $"SKIPPED — {sample.SkipReason}", ConsoleColor.Yellow);
|
||||
|
||||
if (this._logWriter is not null)
|
||||
{
|
||||
await this._logWriter.WriteSkippedAsync(sample.Name, sample.SkipReason);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var missingRequired = sample.RequiredEnvironmentVariables
|
||||
.Where(v => string.IsNullOrEmpty(Environment.GetEnvironmentVariable(v)))
|
||||
.ToList();
|
||||
|
||||
var missingOptional = sample.OptionalEnvironmentVariables
|
||||
.Where(v => string.IsNullOrEmpty(Environment.GetEnvironmentVariable(v)))
|
||||
.ToList();
|
||||
|
||||
if (missingRequired.Count > 0 || missingOptional.Count > 0)
|
||||
{
|
||||
var reasons = new List<string>();
|
||||
if (missingRequired.Count > 0)
|
||||
{
|
||||
reasons.Add($"Missing required: {string.Join(", ", missingRequired)}");
|
||||
}
|
||||
|
||||
if (missingOptional.Count > 0)
|
||||
{
|
||||
reasons.Add($"Missing optional (would cause console prompt hang): {string.Join(", ", missingOptional)}");
|
||||
}
|
||||
|
||||
var skipReason = string.Join("; ", reasons);
|
||||
skipped.Add((sample.Name, skipReason));
|
||||
this._reporter.WriteLineWithPrefix(sample.Name, $"SKIPPED — {skipReason}", ConsoleColor.Yellow);
|
||||
|
||||
if (this._logWriter is not null)
|
||||
{
|
||||
await this._logWriter.WriteSkippedAsync(sample.Name, skipReason);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
runnableSamples.Add(sample);
|
||||
}
|
||||
|
||||
// Run samples in parallel
|
||||
var results = new ConcurrentDictionary<string, VerificationResult>();
|
||||
var semaphore = new SemaphoreSlim(maxParallelism);
|
||||
|
||||
this._reporter.WriteLineWithPrefix(
|
||||
"runner", $"Running {runnableSamples.Count} samples (max {maxParallelism} parallel)...");
|
||||
|
||||
try
|
||||
{
|
||||
var tasks = runnableSamples.Select(sample => this.RunSingleAsync(sample, results, semaphore)).ToArray();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Dispose();
|
||||
}
|
||||
|
||||
return new RunAllResult(results, skipped, sampleOrder);
|
||||
}
|
||||
|
||||
private async Task RunSingleAsync(
|
||||
SampleDefinition sample,
|
||||
ConcurrentDictionary<string, VerificationResult> results,
|
||||
SemaphoreSlim semaphore)
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
var log = new List<string>();
|
||||
log.Add($"[{sample.Name}] Running...");
|
||||
this._reporter.WriteLineWithPrefix(sample.Name, "Running...");
|
||||
|
||||
var projectPath = Path.Combine(this._dotnetRoot, sample.ProjectPath);
|
||||
var run = sample.Inputs.Length > 0
|
||||
? await SampleRunner.RunAsync(projectPath, this._timeout, sample.Inputs, sample.InputDelayMs, build: this._buildSamples)
|
||||
: await SampleRunner.RunAsync(projectPath, this._timeout, build: this._buildSamples);
|
||||
|
||||
log.Add($"[{sample.Name}] Completed ({run.Elapsed.TotalSeconds:F1}s, exit={run.ExitCode})");
|
||||
this._reporter.WriteLineWithPrefix(
|
||||
sample.Name, $"Completed ({run.Elapsed.TotalSeconds:F1}s, exit={run.ExitCode}). Verifying...");
|
||||
|
||||
var result = await this._verifier.VerifyAsync(sample, run);
|
||||
|
||||
if (result.Passed)
|
||||
{
|
||||
log.Add($"[{sample.Name}] PASSED");
|
||||
this._reporter.WriteLineWithPrefix(sample.Name, "PASSED", ConsoleColor.Green);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Add($"[{sample.Name}] FAILED");
|
||||
this._reporter.WriteLineWithPrefix(sample.Name, "FAILED", ConsoleColor.Red);
|
||||
foreach (var failure in result.Failures)
|
||||
{
|
||||
log.Add($"[{sample.Name}] ✗ {failure}");
|
||||
this._reporter.WriteLineWithPrefix(sample.Name, $" ✗ {failure}", ConsoleColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.AIReasoning is not null)
|
||||
{
|
||||
log.Add($"[{sample.Name}] AI: {result.AIReasoning}");
|
||||
this._reporter.WriteLineWithPrefix(
|
||||
sample.Name, $" AI: {Truncate(result.AIReasoning, 300)}", ConsoleColor.DarkGray);
|
||||
}
|
||||
|
||||
var verificationResult = new VerificationResult
|
||||
{
|
||||
SampleName = result.SampleName,
|
||||
Passed = result.Passed,
|
||||
Summary = result.Summary,
|
||||
Failures = result.Failures,
|
||||
AIReasoning = result.AIReasoning,
|
||||
Stdout = run.Stdout,
|
||||
Stderr = run.Stderr,
|
||||
LogLines = log,
|
||||
};
|
||||
results[sample.Name] = verificationResult;
|
||||
|
||||
if (this._logWriter is not null)
|
||||
{
|
||||
await this._logWriter.WriteSampleResultAsync(verificationResult);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static string Truncate(string text, int maxLength)
|
||||
=> text.Length <= maxLength ? text : text[..maxLength] + "...";
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// The result of verifying a single sample.
|
||||
/// </summary>
|
||||
internal sealed class VerificationResult
|
||||
{
|
||||
public required string SampleName { get; init; }
|
||||
public required bool Passed { get; init; }
|
||||
public required string Summary { get; init; }
|
||||
public List<string> Failures { get; init; } = [];
|
||||
public string? AIReasoning { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The sample's stdout output, captured for log file output.
|
||||
/// </summary>
|
||||
public string? Stdout { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The sample's stderr output, captured for log file output.
|
||||
/// </summary>
|
||||
public string? Stderr { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Per-sample log lines, buffered during parallel execution
|
||||
/// and written sequentially to the log file.
|
||||
/// </summary>
|
||||
public List<string> LogLines { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Parsed command-line options for the sample verification tool.
|
||||
/// </summary>
|
||||
internal sealed class VerifyOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum number of samples to run concurrently.
|
||||
/// </summary>
|
||||
public int MaxParallelism { get; init; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Path to write a CSV summary file, or <c>null</c> to skip.
|
||||
/// </summary>
|
||||
public string? CsvFilePath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to write a Markdown summary file, or <c>null</c> to skip.
|
||||
/// </summary>
|
||||
public string? MarkdownFilePath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to write a sequential log file, or <c>null</c> to skip.
|
||||
/// </summary>
|
||||
public string? LogFilePath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, samples are built as part of <c>dotnet run</c>.
|
||||
/// When false (the default), <c>--no-build</c> is passed, assuming a prior build step.
|
||||
/// </summary>
|
||||
public bool BuildSamples { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The filtered list of samples to process.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<SampleDefinition> Samples { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// All known sample set registries, keyed by category name.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, IReadOnlyList<SampleDefinition>> s_sampleSets =
|
||||
new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["01-get-started"] = GetStartedSamples.All,
|
||||
["02-agents"] = AgentsSamples.All,
|
||||
["03-workflows"] = WorkflowSamples.All,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parses command-line arguments and resolves the sample list.
|
||||
/// Returns <c>null</c> and writes to stderr if the arguments are invalid.
|
||||
/// </summary>
|
||||
public static VerifyOptions? Parse(string[] args)
|
||||
{
|
||||
var argList = args.ToList();
|
||||
|
||||
var categoryFilter = ExtractArg(argList, "--category");
|
||||
var logFilePath = ExtractArg(argList, "--log");
|
||||
var csvFilePath = ExtractArg(argList, "--csv");
|
||||
var markdownFilePath = ExtractArg(argList, "--md");
|
||||
var buildSamples = ExtractFlag(argList, "--build");
|
||||
|
||||
int maxParallelism = 8;
|
||||
var parallelArg = ExtractArg(argList, "--parallel");
|
||||
if (parallelArg is not null && int.TryParse(parallelArg, out var p) && p > 0)
|
||||
{
|
||||
maxParallelism = p;
|
||||
}
|
||||
|
||||
HashSet<string>? nameFilter = null;
|
||||
if (argList.Count > 0)
|
||||
{
|
||||
nameFilter = argList.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// Build the sample list
|
||||
IReadOnlyList<SampleDefinition> samples;
|
||||
if (categoryFilter is not null)
|
||||
{
|
||||
if (!s_sampleSets.TryGetValue(categoryFilter, out var categoryList))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"Unknown category '{categoryFilter}'. Available: {string.Join(", ", s_sampleSets.Keys)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
samples = categoryList;
|
||||
}
|
||||
else
|
||||
{
|
||||
samples = s_sampleSets.Values.SelectMany(s => s).ToList();
|
||||
}
|
||||
|
||||
if (nameFilter is not null)
|
||||
{
|
||||
samples = samples.Where(s => nameFilter.Contains(s.Name)).ToList();
|
||||
}
|
||||
|
||||
if (samples.Count == 0)
|
||||
{
|
||||
var allNames = s_sampleSets.Values.SelectMany(s => s).Select(s => s.Name);
|
||||
Console.Error.WriteLine($"No matching samples found. Available: {string.Join(", ", allNames)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new VerifyOptions
|
||||
{
|
||||
MaxParallelism = maxParallelism,
|
||||
LogFilePath = logFilePath,
|
||||
CsvFilePath = csvFilePath,
|
||||
MarkdownFilePath = markdownFilePath,
|
||||
BuildSamples = buildSamples,
|
||||
Samples = samples,
|
||||
};
|
||||
}
|
||||
|
||||
private static string? ExtractArg(List<string> list, string flag)
|
||||
{
|
||||
var idx = list.IndexOf(flag);
|
||||
if (idx < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (idx + 1 >= list.Count)
|
||||
{
|
||||
Console.Error.WriteLine($"Missing value for {flag}.");
|
||||
list.RemoveAt(idx);
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = list[idx + 1];
|
||||
list.RemoveRange(idx, 2);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool ExtractFlag(List<string> list, string flag)
|
||||
{
|
||||
var idx = list.IndexOf(flag);
|
||||
if (idx < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
list.RemoveAt(idx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace VerifySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the expected behavior for each sample in 03-workflows.
|
||||
/// </summary>
|
||||
internal static class WorkflowSamples
|
||||
{
|
||||
public static IReadOnlyList<SampleDefinition> All { get; } =
|
||||
[
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// _StartHere
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_StartHere_01_Streaming",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/01_Streaming",
|
||||
RequiredEnvironmentVariables = [],
|
||||
IsDeterministic = true,
|
||||
MustContain =
|
||||
[
|
||||
"UppercaseExecutor: HELLO, WORLD!",
|
||||
"ReverseTextExecutor: !DLROW ,OLLEH",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_StartHere_02_AgentsInWorkflows",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/02_AgentsInWorkflows",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show agent responses from a translation workflow.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_StartHere_03_AgentWorkflowPatterns",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/03_AgentWorkflowPatterns",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["sequential"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show a sequential workflow pattern with multiple agents executing tasks in order.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_StartHere_04_MultiModelService",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/04_MultiModelService",
|
||||
RequiredEnvironmentVariables = ["BEDROCK_ACCESS_KEY", "BEDROCK_SECRET_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
|
||||
SkipReason = "Requires multiple external provider API keys (Bedrock, Anthropic, OpenAI).",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_StartHere_05_SubWorkflows",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/05_SubWorkflows",
|
||||
RequiredEnvironmentVariables = [],
|
||||
IsDeterministic = true,
|
||||
MustContain =
|
||||
[
|
||||
"=== Sub-Workflow Demonstration ===",
|
||||
"Final Output:",
|
||||
"=== Main Workflow Completed ===",
|
||||
"Sample Complete: Workflows can be composed hierarchically using sub-workflows",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_StartHere_06_MixedWorkflowAgentsAndExecutors",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/06_MixedWorkflowAgentsAndExecutors",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What is 2 plus 2?"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show agents and executors working together to process a user question.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_StartHere_07_WriterCriticWorkflow",
|
||||
ProjectPath = "samples/03-workflows/_StartHere/07_WriterCriticWorkflow",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
MustContain = ["=== Writer-Critic Iteration Workflow ==="],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show a writer-critic iteration workflow with writer and critic sections.",
|
||||
"The critic should either approve or request revisions.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Agents
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Agents_CustomAgentExecutors",
|
||||
ProjectPath = "samples/03-workflows/Agents/CustomAgentExecutors",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show custom workflow events including slogan generation and feedback.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Agents_FoundryAgent",
|
||||
ProjectPath = "samples/03-workflows/Agents/FoundryAgent",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
SkipReason = "Requires Azure AI Foundry project endpoint.",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Agents_GroupChatToolApproval",
|
||||
ProjectPath = "samples/03-workflows/Agents/GroupChatToolApproval",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
MustContain = ["Starting group chat workflow for software deployment..."],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show a group chat workflow with QA and DevOps agents for software deployment.",
|
||||
"There should be approval requests for tool calls.",
|
||||
"The workflow should show interaction between QA and DevOps agents toward deployment.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Agents_WorkflowAsAnAgent",
|
||||
ProjectPath = "samples/03-workflows/Agents/WorkflowAsAnAgent",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["hello", "exit"],
|
||||
InputDelayMs = 5000,
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show a conversational workflow responding to the user's hello message.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Checkpoint
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Checkpoint_CheckpointAndRehydrate",
|
||||
ProjectPath = "samples/03-workflows/Checkpoint/CheckpointAndRehydrate",
|
||||
RequiredEnvironmentVariables = [],
|
||||
IsDeterministic = true,
|
||||
MustContain =
|
||||
[
|
||||
"Workflow completed with result:",
|
||||
"Number of checkpoints created:",
|
||||
"Hydrating a new workflow instance from the 6th checkpoint.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Checkpoint_CheckpointAndResume",
|
||||
ProjectPath = "samples/03-workflows/Checkpoint/CheckpointAndResume",
|
||||
RequiredEnvironmentVariables = [],
|
||||
IsDeterministic = true,
|
||||
MustContain =
|
||||
[
|
||||
"Workflow completed with result:",
|
||||
"Number of checkpoints created:",
|
||||
"Restoring from the 6th checkpoint.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Checkpoint_CheckpointWithHumanInTheLoop",
|
||||
ProjectPath = "samples/03-workflows/Checkpoint/CheckpointWithHumanInTheLoop",
|
||||
RequiredEnvironmentVariables = [],
|
||||
Inputs = ["50", "25", "40", "45", "42", "50", "25", "40", "45", "42"],
|
||||
InputDelayMs = 1000,
|
||||
MustContain = ["found in"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show a number guessing game with higher/lower hints that eventually reaches the correct number.",
|
||||
"The output should demonstrate checkpoint save and restore behavior.",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Concurrent
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Concurrent_Concurrent",
|
||||
ProjectPath = "samples/03-workflows/Concurrent/Concurrent",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show results from concurrent agent processing.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Concurrent_MapReduce",
|
||||
ProjectPath = "samples/03-workflows/Concurrent/MapReduce",
|
||||
RequiredEnvironmentVariables = [],
|
||||
MustContain =
|
||||
[
|
||||
"=== RUNNING WORKFLOW ===",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// ConditionalEdges
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_ConditionalEdges_01_EdgeCondition",
|
||||
ProjectPath = "samples/03-workflows/ConditionalEdges/01_EdgeCondition",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show an email being classified as spam or not spam and processed accordingly.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_ConditionalEdges_02_SwitchCase",
|
||||
ProjectPath = "samples/03-workflows/ConditionalEdges/02_SwitchCase",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show an ambiguous email being classified as spam, not spam, or uncertain.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_ConditionalEdges_03_MultiSelection",
|
||||
ProjectPath = "samples/03-workflows/ConditionalEdges/03_MultiSelection",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show an email being classified and potentially routed to multiple handlers.",
|
||||
"The output should not contain error messages or stack traces.",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// HumanInTheLoop
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_HumanInTheLoop_Basic",
|
||||
ProjectPath = "samples/03-workflows/HumanInTheLoop/HumanInTheLoopBasic",
|
||||
RequiredEnvironmentVariables = [],
|
||||
Inputs = ["50", "25", "40", "45", "42"],
|
||||
InputDelayMs = 1000,
|
||||
MustContain = ["found in"],
|
||||
ExpectedOutputDescription =
|
||||
[
|
||||
"The output should show a number guessing game with higher/lower hints that eventually reaches the correct number 42.",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Loop
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Loop",
|
||||
ProjectPath = "samples/03-workflows/Loop",
|
||||
RequiredEnvironmentVariables = [],
|
||||
MustContain = ["Result:"],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// SharedStates
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_SharedStates",
|
||||
ProjectPath = "samples/03-workflows/SharedStates",
|
||||
RequiredEnvironmentVariables = [],
|
||||
IsDeterministic = true,
|
||||
MustContain =
|
||||
[
|
||||
"Total Paragraphs:",
|
||||
"Total Words:",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Visualization
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Visualization",
|
||||
ProjectPath = "samples/03-workflows/Visualization",
|
||||
RequiredEnvironmentVariables = [],
|
||||
IsDeterministic = true,
|
||||
MustContain =
|
||||
[
|
||||
"Generating workflow visualization...",
|
||||
"Mermaid string:",
|
||||
"DiGraph string:",
|
||||
],
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Observability
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Observability_ApplicationInsights",
|
||||
ProjectPath = "samples/03-workflows/Observability/ApplicationInsights",
|
||||
RequiredEnvironmentVariables = ["APPLICATIONINSIGHTS_CONNECTION_STRING"],
|
||||
SkipReason = "Requires Application Insights connection string.",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Observability_AspireDashboard",
|
||||
ProjectPath = "samples/03-workflows/Observability/AspireDashboard",
|
||||
RequiredEnvironmentVariables = [],
|
||||
SkipReason = "Requires Aspire Dashboard / OTLP endpoint.",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Observability_WorkflowAsAnAgent",
|
||||
ProjectPath = "samples/03-workflows/Observability/WorkflowAsAnAgent",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
SkipReason = "Interactive console with ReadLine loop; requires OTLP endpoint.",
|
||||
},
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Declarative
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_ConfirmInput",
|
||||
ProjectPath = "samples/03-workflows/Declarative/ConfirmInput",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
Inputs = ["hello", "hello"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a confirmation prompt and a user response."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_CustomerSupport",
|
||||
ProjectPath = "samples/03-workflows/Declarative/CustomerSupport",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["My laptop won't start", "The laptop is now working, thank you!"],
|
||||
InputDelayMs = 5000,
|
||||
ExpectedOutputDescription = ["The output should show a customer support workflow processing a laptop issue, with agent responses providing troubleshooting or support."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_DeepResearch",
|
||||
ProjectPath = "samples/03-workflows/Declarative/DeepResearch",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
SkipReason = "Requires external weather API (wttr.in).",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_ExecuteWorkflow",
|
||||
ProjectPath = "samples/03-workflows/Declarative/ExecuteWorkflow",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
SkipReason = "Requires a workflow file path as a CLI argument.",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_FunctionTools",
|
||||
ProjectPath = "samples/03-workflows/Declarative/FunctionTools",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What are today's specials?", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow calling function tools (e.g. a menu plugin) to answer a question about restaurant specials."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_HostedWorkflow",
|
||||
ProjectPath = "samples/03-workflows/Declarative/HostedWorkflow",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
SkipReason = "Hosts a persistent workflow server that does not exit.",
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_InputArguments",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InputArguments",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["I'd like to visit Seattle", "Seattle, WA", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow capturing location input and providing travel-related information about Seattle."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_InvokeFunctionTool",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InvokeFunctionTool",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What's the soup of the day?", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow invoking a function tool (e.g. a menu plugin) to answer a question about the soup of the day."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_InvokeFoundryToolboxMcp",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InvokeFoundryToolboxMcp",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL", "FOUNDRY_TOOLBOX_NAME", "FOUNDRY_AGENT_TOOLSET_API_VERSION"],
|
||||
Inputs = ["How do I use Azure OpenAI with my data?"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow using Foundry Toolbox MCP tools to search Microsoft Learn documentation and web search to provide a summary of results."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_InvokeMcpTool",
|
||||
ProjectPath = "samples/03-workflows/Declarative/InvokeMcpTool",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["Search for .NET tutorials on Microsoft Learn"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow using MCP tools to search Microsoft Learn documentation and provide a summary of results."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_Marketing",
|
||||
ProjectPath = "samples/03-workflows/Declarative/Marketing",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["A smart water bottle that tracks hydration"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a marketing workflow generating content about a smart water bottle product."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_StudentTeacher",
|
||||
ProjectPath = "samples/03-workflows/Declarative/StudentTeacher",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["What is 18 + 27?"],
|
||||
InputDelayMs = 3000,
|
||||
ExpectedOutputDescription = ["The output should show a student-teacher workflow where a student asks a math question and a teacher provides the answer."],
|
||||
},
|
||||
|
||||
new SampleDefinition
|
||||
{
|
||||
Name = "Workflow_Declarative_ToolApproval",
|
||||
ProjectPath = "samples/03-workflows/Declarative/ToolApproval",
|
||||
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
|
||||
Inputs = ["Search for .NET tutorials", "EXIT"],
|
||||
InputDelayMs = 8000,
|
||||
ExpectedOutputDescription = ["The output should show a workflow using an MCP tool with approval to search Microsoft Learn, followed by an exit from the input loop."],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsAotCompatible>false</IsAotCompatible>
|
||||
<!-- This is a top-level console app; ConfigureAwait is unnecessary -->
|
||||
<NoWarn>$(NoWarn);CA2007</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.301",
|
||||
"rollForward": "minor",
|
||||
"allowPrerelease": false
|
||||
},
|
||||
"test": {
|
||||
"runner": "Microsoft.Testing.Platform"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
<packageSource key="nuget.org">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
@@ -0,0 +1,21 @@
|
||||
# About Microsoft Agent Framework
|
||||
|
||||
Microsoft Agent Framework is a comprehensive .NET library for building, orchestrating, and deploying AI agents and multi-agent workflows. The framework provides everything from simple chat agents to complex multi-agent systems with graph-based orchestration capabilities.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Multi-Agent Orchestration**: Coordinate multiple agents using sequential, concurrent, group chat, and handoff patterns
|
||||
- **Graph-based Workflows**: Connect agents and functions with streaming, checkpointing, and human-in-the-loop capabilities, with both imperative or declarative workflow support
|
||||
- **Multiple Provider Support**: Seamlessly integrate with various LLM providers with more being added continuously
|
||||
- **Extensible Middleware**: Flexible request/response processing with custom pipelines and exception handling
|
||||
- **Built-in Observability**: OpenTelemetry integration for distributed tracing, monitoring, and debugging
|
||||
- **Cross-Platform**: Compatible with .NET 8.0, .NET Standard 2.0, and .NET Framework for broad deployment options
|
||||
|
||||
Whether you're building simple AI assistants or complex multi-agent systems, Microsoft Agent Framework provides the tools and abstractions needed to create robust, scalable AI applications in .NET.
|
||||
|
||||
# Getting Started ⚡
|
||||
|
||||
- Learn more at the [documentation site](https://learn.microsoft.com/agent-framework/overview/agent-framework-overview).
|
||||
- Join the [Discord community](https://discord.gg/b5zjErwbQM).
|
||||
- Follow the team on [Semantic Kernel blog](https://devblogs.microsoft.com/semantic-kernel/).
|
||||
- Check out the [GitHub repository](https://github.com/microsoft/agent-framework) for the latest updates.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
@@ -0,0 +1,78 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.13.0</VersionPrefix>
|
||||
<RCNumber>1</RCNumber>
|
||||
<DateSuffix>260703</DateSuffix>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).$(DateSuffix).1</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.$(DateSuffix).1</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleased)' == 'true'">$(VersionPrefix)</PackageVersion>
|
||||
<GitTag>1.13.0</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
<!-- Package validation. Baseline Version should be the latest version available on NuGet. -->
|
||||
<PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
|
||||
<!-- Enable validation for GA packages -->
|
||||
<EnablePackageValidation Condition="'$(IsReleased)' == 'true'">true</EnablePackageValidation>
|
||||
<!-- Validate assembly attributes only for Publish builds -->
|
||||
<NoWarn Condition="'$(Configuration)' != 'Publish'">$(NoWarn);CP0003</NoWarn>
|
||||
<!-- Do not validate reference assemblies -->
|
||||
<NoWarn>$(NoWarn);CP1002</NoWarn>
|
||||
|
||||
<!-- Enable NuGet package auditing -->
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
|
||||
<!-- Audit direct and transitive packages -->
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
|
||||
<!-- Report low, moderate, high and critical advisories -->
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
|
||||
|
||||
<!-- Default description and tags. Packages can override. -->
|
||||
<Authors>Microsoft</Authors>
|
||||
<Company>Microsoft</Company>
|
||||
<Product>Microsoft Agent Framework</Product>
|
||||
<Description>Microsoft Agent Framework is a comprehensive .NET library for building, orchestrating, and deploying AI agents and multi-agent workflows. The framework provides everything from simple chat agents to complex multi-agent systems with graph-based orchestration capabilities.</Description>
|
||||
<PackageTags>AI, Artificial Intelligence, Agent, SDK, Framework</PackageTags>
|
||||
<PackageId>$(AssemblyName)</PackageId>
|
||||
|
||||
<!-- Required license, copyright, and repo information. Packages can override. -->
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
|
||||
<PackageProjectUrl>https://learn.microsoft.com/agent-framework/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/microsoft/agent-framework</RepositoryUrl>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
||||
<!-- Use icon and NUGET readme from dotnet/nuget folder -->
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageIconUrl>icon.png</PackageIconUrl>
|
||||
<PackageReadmeFile>NUGET.md</PackageReadmeFile>
|
||||
|
||||
<!-- Build symbol package (.snupkg) to distribute the PDB containing Source Link -->
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
|
||||
<!-- Include the XML documentation file in the NuGet package. -->
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- SourceLink allows step-through debugging for source hosted on GitHub. -->
|
||||
<!-- https://github.com/dotnet/sourcelink -->
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Include icon.png and NUGET.md in the project. -->
|
||||
<None Include="$(RepoRoot)/dotnet/nuget/icon.png" Link="icon.png" Pack="true" PackagePath="." />
|
||||
<None Include="$(RepoRoot)/dotnet/nuget/NUGET.md" Link="NUGET.md" Pack="true" PackagePath="." />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
# Suppressing errors for Sample projects under dotnet/samples folder
|
||||
[*.cs]
|
||||
dotnet_diagnostic.CA1716.severity = none # Add summary to documentation comment.
|
||||
dotnet_diagnostic.CA1873.severity = none # Evaluation of logging arguments may be expensive
|
||||
dotnet_diagnostic.CA2000.severity = none # Call System.IDisposable.Dispose on object before all references to it are out of scope
|
||||
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
|
||||
|
||||
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
|
||||
|
||||
dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations
|
||||
|
||||
dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave
|
||||
dotnet_diagnostic.VSTHRD200.severity = none # Use Async suffix for async methods
|
||||
|
||||
dotnet_diagnostic.MEAI001.severity = none # [Experimental] APIs in Microsoft.Extensions.AI
|
||||
dotnet_diagnostic.OPENAI001.severity = none # [Experimental] APIs in OpenAI
|
||||
dotnet_diagnostic.SKEXP0110.severity = none # [Experimental] APIs in Microsoft.SemanticKernel
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with AIProjectClient as the backend.
|
||||
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate."))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use an AIProjectClient agent with function tools.
|
||||
// It shows both non-streaming and streaming agent interactions using weather tools.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
[Description("Get the weather for a given location.")]
|
||||
static string GetWeather([Description("The location to get the weather for.")] string location)
|
||||
=> $"The weather in {location} is cloudy with a high of 15°C.";
|
||||
|
||||
// Create the agent and provide the function tool to the agent.
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(model: model, instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
|
||||
|
||||
// Non-streaming agent interaction with function tools.
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));
|
||||
|
||||
// Streaming agent interaction with function tools.
|
||||
await foreach (var update in agent.RunStreamingAsync("What is the weather like in Amsterdam?"))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with a multi-turn conversation.
|
||||
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent with a multi-turn conversation, where the context is preserved in the session object.
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session));
|
||||
|
||||
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the session object.
|
||||
session = await agent.CreateSessionAsync();
|
||||
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate.", session))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
await foreach (var update in agent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to add a basic custom memory component to an agent.
|
||||
// The memory component subscribes to all messages added to the conversation and
|
||||
// extracts the user's name and age if provided.
|
||||
// The component adds a prompt to ask for this information if it is not already known
|
||||
// and provides it to the model before each invocation if known.
|
||||
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using SampleApp;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
var projectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// Create a separate IChatClient for the memory component to use for structured extraction.
|
||||
// The memory component calls the model with a ResponseFormat (JSON schema) to extract user info.
|
||||
// Using a dedicated client here avoids mixing side-channel extraction calls with the agent's
|
||||
// conversation history, and avoids the chicken-and-egg problem of needing an IChatClient
|
||||
// before the main agent is constructed.
|
||||
IChatClient extractionClient =
|
||||
new AIProjectClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetProjectOpenAIClient()
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient(model);
|
||||
|
||||
// Create the agent with instructions and the custom memory context provider.
|
||||
// The memory component is attached to all sessions created by the agent. Here each new memory
|
||||
// component will have its own user info object, so each session will have its own memory.
|
||||
// In real world applications/services, where the user info would be persisted in a database,
|
||||
// and preferably shared between multiple sessions used by the same user, ensure that the
|
||||
// factory reads the user id from the current context and scopes the memory component
|
||||
// and its storage to that user id.
|
||||
AIAgent agent = projectClient.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
ModelId = model,
|
||||
Instructions = "You are a friendly assistant. Always address the user by their name.",
|
||||
},
|
||||
AIContextProviders = [new UserInfoMemory(extractionClient)]
|
||||
});
|
||||
|
||||
// Create a new session for the conversation.
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
|
||||
Console.WriteLine(">> Use session with blank memory\n");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Hello, what is the square root of 9?", session));
|
||||
Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", session));
|
||||
Console.WriteLine(await agent.RunAsync("I am 20 years old", session));
|
||||
|
||||
// We can serialize the session. The serialized state will include the state of the memory component.
|
||||
JsonElement sessionElement = await agent.SerializeSessionAsync(session);
|
||||
|
||||
Console.WriteLine("\n>> Use deserialized session with previously created memories\n");
|
||||
|
||||
// Later we can deserialize the session and continue the conversation with the previous memory component state.
|
||||
var deserializedSession = await agent.DeserializeSessionAsync(sessionElement);
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", deserializedSession));
|
||||
|
||||
Console.WriteLine("\n>> Read memories using memory component\n");
|
||||
|
||||
// It's possible to access the memory component via the agent's GetService method.
|
||||
var userInfo = agent.GetService<UserInfoMemory>()?.GetUserInfo(deserializedSession);
|
||||
|
||||
// Output the user info that was captured by the memory component.
|
||||
Console.WriteLine($"MEMORY - User Name: {userInfo?.UserName}");
|
||||
Console.WriteLine($"MEMORY - User Age: {userInfo?.UserAge}");
|
||||
|
||||
Console.WriteLine("\n>> Use new session with previously created memories\n");
|
||||
|
||||
// It is also possible to set the memories using a memory component on an individual session.
|
||||
// This is useful if we want to start a new session, but have it share the same memories as a previous session.
|
||||
var newSession = await agent.CreateSessionAsync();
|
||||
if (userInfo is not null && agent.GetService<UserInfoMemory>() is UserInfoMemory newSessionMemory)
|
||||
{
|
||||
newSessionMemory.SetUserInfo(newSession, userInfo);
|
||||
}
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
// This time the agent should remember the user's name and use it in the response.
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", newSession));
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample memory component that can remember a user's name and age.
|
||||
/// </summary>
|
||||
internal sealed class UserInfoMemory : AIContextProvider
|
||||
{
|
||||
private readonly ProviderSessionState<UserInfo> _sessionState;
|
||||
private IReadOnlyList<string>? _stateKeys;
|
||||
private readonly IChatClient _chatClient;
|
||||
|
||||
public UserInfoMemory(IChatClient chatClient, Func<AgentSession?, UserInfo>? stateInitializer = null)
|
||||
{
|
||||
this._sessionState = new ProviderSessionState<UserInfo>(
|
||||
stateInitializer ?? (_ => new UserInfo()),
|
||||
this.GetType().Name);
|
||||
this._chatClient = chatClient;
|
||||
}
|
||||
|
||||
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
|
||||
|
||||
public UserInfo GetUserInfo(AgentSession session)
|
||||
=> this._sessionState.GetOrInitializeState(session);
|
||||
|
||||
public void SetUserInfo(AgentSession session, UserInfo userInfo)
|
||||
=> this._sessionState.SaveState(session, userInfo);
|
||||
|
||||
protected override async ValueTask StoreAIContextAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var userInfo = this._sessionState.GetOrInitializeState(context.Session);
|
||||
|
||||
// Try and extract the user name and age from the message if we don't have it already and it's a user message.
|
||||
if ((userInfo.UserName is null || userInfo.UserAge is null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
|
||||
{
|
||||
// The Foundry Responses API requires the model name in the request body.
|
||||
// Retrieve it from the client's metadata so callers don't need to pass it separately.
|
||||
var modelId = this._chatClient.GetService<ChatClientMetadata>()?.DefaultModelId
|
||||
?? throw new InvalidOperationException(
|
||||
"Could not retrieve DefaultModelId from the extraction IChatClient. " +
|
||||
"Ensure the client was created with a model ID (e.g., via projectClient.AsAIAgent(...)).");
|
||||
var result = await this._chatClient.GetResponseAsync<UserInfo>(
|
||||
context.RequestMessages,
|
||||
new ChatOptions()
|
||||
{
|
||||
ModelId = modelId,
|
||||
Instructions = "Extract the user's name and age from the message if present. If not present return nulls."
|
||||
},
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
userInfo.UserName ??= result.Result.UserName;
|
||||
userInfo.UserAge ??= result.Result.UserAge;
|
||||
}
|
||||
|
||||
this._sessionState.SaveState(context.Session, userInfo);
|
||||
}
|
||||
|
||||
protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var userInfo = this._sessionState.GetOrInitializeState(context.Session);
|
||||
|
||||
StringBuilder instructions = new();
|
||||
|
||||
// If we don't already know the user's name and age, add instructions to ask for them, otherwise just provide what we have to the context.
|
||||
instructions
|
||||
.AppendLine(
|
||||
userInfo.UserName is null ?
|
||||
"Ask the user for their name and politely decline to answer any questions until they provide it." :
|
||||
$"The user's name is {userInfo.UserName}.")
|
||||
.AppendLine(
|
||||
userInfo.UserAge is null ?
|
||||
"Ask the user for their age and politely decline to answer any questions until they provide it." :
|
||||
$"The user's age is {userInfo.UserAge}.");
|
||||
|
||||
return new ValueTask<AIContext>(new AIContext
|
||||
{
|
||||
Instructions = instructions.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UserInfo
|
||||
{
|
||||
public string? UserName { get; set; }
|
||||
public int? UserAge { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
|
||||
namespace WorkflowExecutorsAndEdgesSample;
|
||||
|
||||
/// <summary>
|
||||
/// This sample introduces the concepts of executors and edges in a workflow.
|
||||
///
|
||||
/// Workflows are built from executors (processing units) connected by edges (data flow paths).
|
||||
/// In this example, we create a simple text processing pipeline that:
|
||||
/// 1. Takes input text and converts it to uppercase using an UppercaseExecutor
|
||||
/// 2. Takes the uppercase text and reverses it using a ReverseTextExecutor
|
||||
///
|
||||
/// The executors are connected sequentially, so data flows from one to the next in order.
|
||||
/// For input "Hello, World!", the workflow produces "!DLROW ,OLLEH".
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the executors
|
||||
Func<string, string> uppercaseFunc = s => s.ToUpperInvariant();
|
||||
var uppercase = uppercaseFunc.BindAsExecutor("UppercaseExecutor");
|
||||
|
||||
ReverseTextExecutor reverse = new();
|
||||
|
||||
// Build the workflow by connecting executors sequentially
|
||||
WorkflowBuilder builder = new(uppercase);
|
||||
builder.AddEdge(uppercase, reverse).WithOutputFrom(reverse);
|
||||
var workflow = builder.Build();
|
||||
|
||||
// Execute the workflow with input data
|
||||
await using Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
|
||||
foreach (WorkflowEvent evt in run.NewEvents)
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorComplete)
|
||||
{
|
||||
Console.WriteLine($"{executorComplete.ExecutorId}: {executorComplete.Data}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second executor: reverses the input text and completes the workflow.
|
||||
/// </summary>
|
||||
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by reversing the text.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to reverse</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text reversed</returns>
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
|
||||
return ValueTask.FromResult(string.Concat(message.Reverse()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- The Functions build tools don't like namespaces that start with a number -->
|
||||
<AssemblyName>HostedAgent</AssemblyName>
|
||||
<RootNamespace>HostedAgent</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<!-- Azure Functions packages -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to host an AI agent with Azure Functions (DurableAgents).
|
||||
//
|
||||
// Prerequisites:
|
||||
// - Azure Functions Core Tools
|
||||
// - Foundry project endpoint and credentials
|
||||
//
|
||||
// Environment variables:
|
||||
// FOUNDRY_PROJECT_ENDPOINT
|
||||
// FOUNDRY_MODEL (defaults to "gpt-5.4-mini")
|
||||
//
|
||||
// Run with: func start
|
||||
// Then call: POST http://localhost:7071/api/agents/HostedAgent/run
|
||||
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
using Microsoft.Azure.Functions.Worker.Builder;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
|
||||
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
|
||||
// Set up an AI agent following the standard Microsoft Agent Framework pattern.
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
|
||||
.AsAIAgent(model: model, instructions: "You are a helpful assistant hosted in Azure Functions.", name: "HostedAgent");
|
||||
|
||||
// Configure the function app to host the AI agent.
|
||||
// This will automatically generate HTTP API endpoints for the agent.
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
.ConfigureFunctionsWebApplication()
|
||||
.ConfigureDurableAgents(options => options.AddAIAgent(agent, timeToLive: TimeSpan.FromHours(1)))
|
||||
.Build();
|
||||
app.Run();
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to represent an A2A agent as a set of function tools, where each function tool
|
||||
// corresponds to a skill of the A2A agent, and register these function tools with another AI agent so
|
||||
// it can leverage the A2A agent's skills.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using A2A;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
|
||||
|
||||
// Initialize an A2ACardResolver to get an A2A agent card.
|
||||
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
|
||||
// Get the agent card
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent a2aAgent = agentCard.AsAIAgent();
|
||||
|
||||
// Create the main agent, and provide the a2a agent skills as a function tools.
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(
|
||||
instructions: "You are a helpful assistant that helps people with travel planning.",
|
||||
tools: [.. CreateFunctionTools(a2aAgent, agentCard)]
|
||||
);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Plan a route from '1600 Amphitheatre Parkway, Mountain View, CA' to 'San Francisco International Airport' avoiding tolls"));
|
||||
|
||||
static IEnumerable<AIFunction> CreateFunctionTools(AIAgent a2aAgent, AgentCard agentCard)
|
||||
{
|
||||
foreach (var skill in agentCard.Skills)
|
||||
{
|
||||
// A2A agent skills don't have schemas describing the expected shape of their inputs and outputs.
|
||||
// Schemas can be beneficial for AI models to better understand the skill's contract, generate
|
||||
// the skill's input accordingly and to know what to expect in the skill's output.
|
||||
// However, the A2A specification defines properties such as name, description, tags, examples,
|
||||
// inputModes, and outputModes to provide context about the skill's purpose, capabilities, usage,
|
||||
// and supported MIME types. These properties are added to the function tool description to help
|
||||
// the model determine the appropriate shape of the skill's input and output.
|
||||
AIFunctionFactoryOptions options = new()
|
||||
{
|
||||
Name = FunctionNameSanitizer.Sanitize(skill.Name),
|
||||
Description = $$"""
|
||||
{
|
||||
"description": "{{skill.Description}}",
|
||||
"tags": "[{{string.Join(", ", skill.Tags ?? [])}}]",
|
||||
"examples": "[{{string.Join(", ", skill.Examples ?? [])}}]",
|
||||
"inputModes": "[{{string.Join(", ", skill.InputModes ?? [])}}]",
|
||||
"outputModes": "[{{string.Join(", ", skill.OutputModes ?? [])}}]"
|
||||
}
|
||||
""",
|
||||
};
|
||||
|
||||
yield return AIFunctionFactory.Create(RunAgentAsync, options);
|
||||
}
|
||||
|
||||
async Task<string> RunAgentAsync(string input, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await a2aAgent.RunAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response.Text;
|
||||
}
|
||||
}
|
||||
|
||||
internal static partial class FunctionNameSanitizer
|
||||
{
|
||||
public static string Sanitize(string name)
|
||||
{
|
||||
return InvalidNameCharsRegex().Replace(name, "_");
|
||||
}
|
||||
|
||||
[GeneratedRegex("[^0-9A-Za-z]+")]
|
||||
private static partial Regex InvalidNameCharsRegex();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# A2A Agent as Function Tools
|
||||
|
||||
This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent,
|
||||
and register these function tools with another AI agent so it can leverage the A2A agent's skills.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10 SDK or later
|
||||
- Access to the A2A agent host service
|
||||
|
||||
**Note**: These samples need to be run against a valid A2A server. If no A2A server is available, they can be run against the echo-agent that can be
|
||||
spun up locally by following the guidelines at: https://github.com/a2aproject/a2a-dotnet/blob/main/samples/AgentServer/README.md
|
||||
|
||||
Set the following environment variables:
|
||||
|
||||
```powershell
|
||||
$env:A2A_AGENT_HOST="https://your-a2a-agent-host" # Replace with your A2A agent host endpoint
|
||||
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
|
||||
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
|
||||
```
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A AI agent.
|
||||
|
||||
using A2A;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
|
||||
|
||||
// Initialize an A2ACardResolver to get an A2A agent card.
|
||||
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
|
||||
// Get the agent card
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent agent = agentCard.AsAIAgent();
|
||||
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
|
||||
// AllowBackgroundResponses must be true so the server returns immediately with a continuation token
|
||||
// instead of blocking until the task is complete.
|
||||
AgentRunOptions options = new() { AllowBackgroundResponses = true };
|
||||
|
||||
// Start the initial run with a long-running task.
|
||||
AgentResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", session, options: options);
|
||||
|
||||
// Poll until the response is complete.
|
||||
while (response.ContinuationToken is { } token)
|
||||
{
|
||||
// Wait before polling again.
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
|
||||
// Continue with the token.
|
||||
response = await agent.RunAsync(session, options: new AgentRunOptions { ContinuationToken = token });
|
||||
}
|
||||
|
||||
// Display the result
|
||||
Console.WriteLine(response);
|
||||
@@ -0,0 +1,25 @@
|
||||
# Polling for A2A Agent Task Completion
|
||||
|
||||
This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A AI agent, following the background responses pattern.
|
||||
|
||||
The sample:
|
||||
|
||||
- Connects to an A2A agent server specified in the `A2A_AGENT_HOST` environment variable
|
||||
- Sends a request to the agent that may take time to complete
|
||||
- Polls the agent at regular intervals using continuation tokens until a final response is received
|
||||
- Displays the final result
|
||||
|
||||
This pattern is useful when an AI model cannot complete a complex task in a single response and needs multiple rounds of processing.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10.0 SDK or later
|
||||
- An A2A agent server running and accessible via HTTP
|
||||
|
||||
Set the following environment variable:
|
||||
|
||||
```powershell
|
||||
$env:A2A_AGENT_HOST="http://localhost:5000" # Replace with your A2A agent server host
|
||||
```
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to select the A2A protocol binding (HTTP+JSON vs JSON-RPC) when
|
||||
// creating an AIAgent from an A2A agent card using A2AClientOptions.PreferredBindings.
|
||||
|
||||
using A2A;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
|
||||
|
||||
// Initialize an A2ACardResolver to get an A2A agent card.
|
||||
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
|
||||
// Get the agent card
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Use A2AClientOptions to explicitly select the HTTP+JSON protocol binding.
|
||||
// This tells the A2A client factory to prefer the HTTP+JSON interface when the agent card
|
||||
// advertises multiple supported interfaces.
|
||||
A2AClientOptions options = new()
|
||||
{
|
||||
PreferredBindings = [ProtocolBindingNames.HttpJson]
|
||||
};
|
||||
|
||||
// To prefer JSON-RPC instead, use:
|
||||
// A2AClientOptions options = new()
|
||||
// {
|
||||
// PreferredBindings = [ProtocolBindingNames.JsonRpc]
|
||||
// };
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent, using the specified protocol binding.
|
||||
AIAgent agent = agentCard.AsAIAgent(options: options);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
AgentResponse response = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||
Console.WriteLine(response);
|
||||
@@ -0,0 +1,27 @@
|
||||
# A2A Agent Protocol Selection
|
||||
|
||||
This sample demonstrates how to select the A2A protocol binding when creating an `AIAgent` from an A2A agent card.
|
||||
|
||||
A2A agents can expose multiple interfaces with different protocol bindings (e.g., HTTP+JSON, JSON-RPC). By default, `AsAIAgent()` prefers HTTP+JSON with JSON-RPC as a fallback. This sample shows how to use `A2AClientOptions.PreferredBindings` to explicitly control which protocol binding is used.
|
||||
|
||||
The sample:
|
||||
|
||||
- Connects to an A2A agent server specified in the `A2A_AGENT_HOST` environment variable
|
||||
- Configures `A2AClientOptions` to prefer the HTTP+JSON protocol binding
|
||||
- Creates an `AIAgent` from the resolved agent card using the specified binding
|
||||
- Sends a message to the agent and displays the response
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10.0 SDK or later
|
||||
- An A2A agent server running and accessible via HTTP
|
||||
|
||||
**Note**: These samples need to be run against a valid A2A server. If no A2A server is available, they can be run against the echo-agent that can be spun up locally by following the guidelines at: https://github.com/a2aproject/a2a-dotnet/blob/main/samples/AgentServer/README.md
|
||||
|
||||
Set the following environment variable:
|
||||
|
||||
```powershell
|
||||
$env:A2A_AGENT_HOST="http://localhost:5000" # Replace with your A2A agent server host
|
||||
```
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to reconnect to an A2A agent's streaming response using continuation tokens,
|
||||
// allowing recovery from stream interruptions without losing progress.
|
||||
|
||||
using A2A;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
|
||||
|
||||
// Initialize an A2ACardResolver to get an A2A agent card.
|
||||
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
|
||||
// Get the agent card
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent agent = agentCard.AsAIAgent();
|
||||
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
|
||||
ResponseContinuationToken? continuationToken = null;
|
||||
|
||||
await foreach (var update in agent.RunStreamingAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", session))
|
||||
{
|
||||
// Saving the continuation token to be able to reconnect to the same response stream later.
|
||||
// Note: Continuation tokens are only returned for long-running tasks. If the underlying A2A agent
|
||||
// returns a message instead of a task, the continuation token will not be initialized.
|
||||
// A2A agents do not support stream resumption from a specific point in the stream,
|
||||
// but only reconnection to obtain the same response stream from the beginning.
|
||||
// So, A2A agents will return an initialized continuation token in the first update
|
||||
// representing the beginning of the stream, and it will be null in all subsequent updates.
|
||||
if (update.ContinuationToken is { } token)
|
||||
{
|
||||
continuationToken = token;
|
||||
}
|
||||
|
||||
// Imitating stream interruption
|
||||
break;
|
||||
}
|
||||
|
||||
// Reconnect to the same response stream using the continuation token obtained from the previous run.
|
||||
// As a first update, the agent will return an update representing the current state of the response at the moment of calling
|
||||
// RunStreamingAsync with the same continuation token, followed by other updates until the end of the stream is reached.
|
||||
if (continuationToken is not null)
|
||||
{
|
||||
await foreach (var update in agent.RunStreamingAsync(session, options: new() { ContinuationToken = continuationToken }))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
Console.WriteLine(update.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# A2A Agent Stream Reconnection
|
||||
|
||||
This sample demonstrates how to reconnect to an A2A agent's streaming response using continuation tokens, allowing recovery from stream interruptions without losing progress.
|
||||
|
||||
The sample:
|
||||
|
||||
- Connects to an A2A agent server specified in the `A2A_AGENT_HOST` environment variable
|
||||
- Sends a request to the agent and begins streaming the response
|
||||
- Captures a continuation token from the stream for later reconnection
|
||||
- Simulates a stream interruption by breaking out of the streaming loop
|
||||
- Reconnects to the same response stream using the captured continuation token
|
||||
- Displays the response received after reconnection
|
||||
|
||||
This pattern is useful when network interruptions or other failures may disrupt an ongoing streaming response, and you need to recover and continue processing.
|
||||
|
||||
> **Note:** Continuation tokens are only available when the underlying A2A agent returns a task. If the agent returns a message instead, the continuation token will not be initialized and stream reconnection is not applicable.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10.0 SDK or later
|
||||
- An A2A agent server running and accessible via HTTP
|
||||
|
||||
Set the following environment variable:
|
||||
|
||||
```powershell
|
||||
$env:A2A_AGENT_HOST="http://localhost:5000" # Replace with your A2A agent server host
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
# Agent-to-Agent (A2A) Samples
|
||||
|
||||
These samples demonstrate how to work with Agent-to-Agent (A2A) specific features in the Agent Framework.
|
||||
|
||||
For other samples that demonstrate how to use AIAgent instances,
|
||||
see the [Getting Started With Agents](../Agents/README.md) samples.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
See the README.md for each sample for the prerequisites for that sample.
|
||||
|
||||
## Samples
|
||||
|
||||
|Sample|Description|
|
||||
|---|---|
|
||||
|[A2A Agent As Function Tools](./A2AAgent_AsFunctionTools/)|This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent, and register these function tools with another AI agent so it can leverage the A2A agent's skills.|
|
||||
|[A2A Agent Polling For Task Completion](./A2AAgent_PollingForTaskCompletion/)|This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A agent.|
|
||||
|[A2A Agent Stream Reconnection](./A2AAgent_StreamReconnection/)|This sample demonstrates how to reconnect to an A2A agent's streaming response using continuation tokens, allowing recovery from stream interruptions.|
|
||||
|[A2A Agent Protocol Selection](./A2AAgent_ProtocolSelection/)|This sample demonstrates how to select the A2A protocol binding (HTTP+JSON vs JSON-RPC) when creating an AIAgent from an A2A agent card using A2AClientOptions.|
|
||||
|
||||
## Running the samples from the console
|
||||
|
||||
To run the samples, navigate to the desired sample directory, e.g.
|
||||
|
||||
```powershell
|
||||
cd A2AAgent_AsFunctionTools
|
||||
```
|
||||
|
||||
Set the required environment variables as documented in the sample readme.
|
||||
If the variables are not set, you will be prompted for the values when running the samples.
|
||||
Execute the following command to build the sample:
|
||||
|
||||
```powershell
|
||||
dotnet build
|
||||
```
|
||||
|
||||
Execute the following command to run the sample:
|
||||
|
||||
```powershell
|
||||
dotnet run --no-build
|
||||
```
|
||||
|
||||
Or just build and run in one step:
|
||||
|
||||
```powershell
|
||||
dotnet run
|
||||
```
|
||||
|
||||
## Running the samples from Visual Studio
|
||||
|
||||
Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`.
|
||||
|
||||
You will be prompted for any required environment variables if they are not already set.
|
||||
@@ -0,0 +1,310 @@
|
||||
# AG-UI Getting Started Samples
|
||||
|
||||
This directory contains samples that demonstrate how to build AG-UI (Agent UI Protocol) servers and clients using the Microsoft Agent Framework.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- .NET 9.0 or later
|
||||
- Azure OpenAI service endpoint and deployment configured
|
||||
- Azure CLI installed and authenticated (`az login`)
|
||||
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource
|
||||
|
||||
## Environment Variables
|
||||
|
||||
All samples require the following environment variables:
|
||||
|
||||
```bash
|
||||
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
|
||||
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
|
||||
```
|
||||
|
||||
For the client samples, you can optionally set:
|
||||
|
||||
```bash
|
||||
export AGUI_SERVER_URL="http://localhost:8888"
|
||||
```
|
||||
|
||||
## Samples
|
||||
|
||||
### Step01_GettingStarted
|
||||
|
||||
A basic AG-UI server and client that demonstrate the foundational concepts.
|
||||
|
||||
#### Server (`Step01_GettingStarted/Server`)
|
||||
|
||||
A basic AG-UI server that hosts an AI agent accessible via HTTP. Demonstrates:
|
||||
|
||||
- Creating an ASP.NET Core web application
|
||||
- Setting up an AG-UI server endpoint with `MapAGUIServer`
|
||||
- Creating an AI agent from an Azure OpenAI chat client
|
||||
- Streaming responses via Server-Sent Events (SSE)
|
||||
|
||||
**Run the server:**
|
||||
|
||||
```bash
|
||||
cd Step01_GettingStarted/Server
|
||||
dotnet run --urls http://localhost:8888
|
||||
```
|
||||
|
||||
#### Client (`Step01_GettingStarted/Client`)
|
||||
|
||||
An interactive console client that connects to an AG-UI server. Demonstrates:
|
||||
|
||||
- Creating an AG-UI client with `AGUIChatClient`
|
||||
- Managing conversation threads
|
||||
- Streaming responses with `RunStreamingAsync`
|
||||
- Displaying colored console output for different content types
|
||||
- Supporting both interactive and automated modes
|
||||
|
||||
**Prerequisites:** The Step01_GettingStarted server (or any AG-UI server) must be running.
|
||||
|
||||
**Run the client:**
|
||||
|
||||
```bash
|
||||
cd Step01_GettingStarted/Client
|
||||
dotnet run
|
||||
```
|
||||
|
||||
Type messages and press Enter to interact with the agent. Type `:q` or `quit` to exit.
|
||||
|
||||
### Step02_BackendTools
|
||||
|
||||
An AG-UI server with function tools that execute on the backend.
|
||||
|
||||
#### Server (`Step02_BackendTools/Server`)
|
||||
|
||||
Demonstrates:
|
||||
|
||||
- Creating function tools using `AIFunctionFactory.Create`
|
||||
- Using `[Description]` attributes for tool documentation
|
||||
- Defining explicit request/response types for type safety
|
||||
- Setting up JSON serialization contexts for source generation
|
||||
- Backend tool rendering (tools execute on the server)
|
||||
|
||||
**Run the server:**
|
||||
|
||||
```bash
|
||||
cd Step02_BackendTools/Server
|
||||
dotnet run --urls http://localhost:8888
|
||||
```
|
||||
|
||||
#### Client (`Step02_BackendTools/Client`)
|
||||
|
||||
A client that works with the backend tools server. Try asking: "Find Italian restaurants in Seattle" or "Search for Mexican food in Portland".
|
||||
|
||||
**Run the client:**
|
||||
|
||||
```bash
|
||||
cd Step02_BackendTools/Client
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Step03_FrontendTools
|
||||
|
||||
Demonstrates frontend tool rendering (tools defined on client, executed on server).
|
||||
|
||||
#### Server (`Step03_FrontendTools/Server`)
|
||||
|
||||
A basic AG-UI server that accepts tool definitions from the client.
|
||||
|
||||
**Run the server:**
|
||||
|
||||
```bash
|
||||
cd Step03_FrontendTools/Server
|
||||
dotnet run --urls http://localhost:8888
|
||||
```
|
||||
|
||||
#### Client (`Step03_FrontendTools/Client`)
|
||||
|
||||
A client that defines and sends tools to the server for execution.
|
||||
|
||||
**Run the client:**
|
||||
|
||||
```bash
|
||||
cd Step03_FrontendTools/Client
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Step04_HumanInLoop
|
||||
|
||||
Demonstrates human-in-the-loop approval workflows for sensitive operations. This sample includes both a server and client component.
|
||||
|
||||
#### Server (`Step04_HumanInLoop/Server`)
|
||||
|
||||
An AG-UI server that implements approval workflows. Demonstrates:
|
||||
|
||||
- Wrapping tools with `ApprovalRequiredAIFunction`
|
||||
- Converting `FunctionApprovalRequestContent` to approval requests
|
||||
- Middleware pattern with `ServerFunctionApprovalServerAgent`
|
||||
- Complete function call capture and restoration
|
||||
|
||||
**Run the server:**
|
||||
|
||||
```bash
|
||||
cd Step04_HumanInLoop/Server
|
||||
dotnet run --urls http://localhost:8888
|
||||
```
|
||||
|
||||
#### Client (`Step04_HumanInLoop/Client`)
|
||||
|
||||
An interactive client that handles approval requests from the server. Demonstrates:
|
||||
|
||||
- Using `ServerFunctionApprovalClientAgent` middleware
|
||||
- Detecting `FunctionApprovalRequestContent`
|
||||
- Displaying approval details to users
|
||||
- Prompting for approval/rejection
|
||||
- Sending approval responses with `FunctionApprovalResponseContent`
|
||||
- Resuming conversation after approval
|
||||
|
||||
**Run the client:**
|
||||
|
||||
```bash
|
||||
cd Step04_HumanInLoop/Client
|
||||
dotnet run
|
||||
```
|
||||
|
||||
Try asking the agent to perform sensitive operations like "Approve expense report EXP-12345".
|
||||
|
||||
### Step05_StateManagement
|
||||
|
||||
An AG-UI server and client that demonstrate state management with predictive updates.
|
||||
|
||||
#### Server (`Step05_StateManagement/Server`)
|
||||
|
||||
Demonstrates:
|
||||
|
||||
- Defining state schemas using C# records
|
||||
- Using `SharedStateAgent` middleware for state management
|
||||
- Streaming predictive state updates with `AgentState` content
|
||||
- Managing shared state between client and server
|
||||
- Using JSON serialization contexts for state types
|
||||
|
||||
**Run the server:**
|
||||
|
||||
```bash
|
||||
cd Step05_StateManagement/Server
|
||||
dotnet run
|
||||
```
|
||||
|
||||
The server runs on port 8888 by default.
|
||||
|
||||
#### Client (`Step05_StateManagement/Client`)
|
||||
|
||||
A client that displays and updates shared state from the server. Try asking: "Create a recipe for chocolate chip cookies" or "Suggest a pasta dish".
|
||||
|
||||
**Run the client:**
|
||||
|
||||
```bash
|
||||
cd Step05_StateManagement/Client
|
||||
dotnet run
|
||||
```
|
||||
|
||||
## How AG-UI Works
|
||||
|
||||
### Server-Side
|
||||
|
||||
1. Client sends HTTP POST request with messages
|
||||
2. ASP.NET Core endpoint receives the request via `MapAGUIServer`
|
||||
3. Agent processes messages using Agent Framework
|
||||
4. Responses are streamed back as Server-Sent Events (SSE)
|
||||
|
||||
### Client-Side
|
||||
|
||||
1. `AGUIAgent` sends HTTP POST request to server
|
||||
2. Server responds with SSE stream
|
||||
3. Client parses events into `AgentResponseUpdate` objects
|
||||
4. Updates are displayed based on content type
|
||||
5. The client sends the full message history each turn (the stateless AG-UI client does not rely on a server-assigned `ConversationId`)
|
||||
|
||||
### Protocol Features
|
||||
|
||||
- **HTTP POST** for requests
|
||||
- **Server-Sent Events (SSE)** for streaming responses
|
||||
- **JSON** for event serialization
|
||||
- **Thread IDs** (read from the `RUN_STARTED` event's raw representation) for conversation context. `AGUIChatClient` is stateless and intentionally does not surface a `ConversationId`.
|
||||
- **Run IDs** (as `ResponseId`) for tracking individual executions
|
||||
|
||||
## Security considerations
|
||||
|
||||
`ConversationId` keeps request/response continuity. It is not proof that the caller owns that conversation. In multi-user deployments, authenticate each AG-UI request and authorize conversation access using your application's real boundary, such as the authenticated user, tenant, or workspace.
|
||||
|
||||
If your ASP.NET Core host shares session storage across users, pair `MapAGUI` with an isolation strategy such as `UseClaimsBasedSessionIsolation(...)` so the storage key includes a principal-specific dimension instead of relying on the conversation identifier alone.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Refused
|
||||
|
||||
Ensure the server is running before starting the client:
|
||||
|
||||
```bash
|
||||
# Terminal 1
|
||||
cd AGUI_Step01_ServerBasic
|
||||
dotnet run --urls http://localhost:8888
|
||||
|
||||
# Terminal 2 (after server starts)
|
||||
cd AGUI_Step02_ClientBasic
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
If port 8888 is already in use, choose a different port:
|
||||
|
||||
```bash
|
||||
# Server
|
||||
dotnet run --urls http://localhost:8889
|
||||
|
||||
# Client (set environment variable)
|
||||
export AGUI_SERVER_URL="http://localhost:8889"
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Authentication Errors
|
||||
|
||||
Make sure you're authenticated with Azure:
|
||||
|
||||
```bash
|
||||
az login
|
||||
```
|
||||
|
||||
Verify you have the `Cognitive Services OpenAI Contributor` role on the Azure OpenAI resource.
|
||||
|
||||
### Missing Environment Variables
|
||||
|
||||
If you see "AZURE_OPENAI_ENDPOINT is not set" errors, ensure environment variables are set in your current shell session before running the samples.
|
||||
|
||||
### Streaming Not Working
|
||||
|
||||
Check that the client timeout is sufficient (default is 60 seconds). For long-running operations, you may need to increase the timeout in the client code.
|
||||
|
||||
## Next Steps
|
||||
|
||||
After completing these samples, explore more AG-UI capabilities:
|
||||
|
||||
### Currently Available in C#
|
||||
|
||||
The samples above demonstrate the AG-UI features currently available in C#:
|
||||
|
||||
- ✅ **Basic Server and Client**: Setting up AG-UI communication
|
||||
- ✅ **Backend Tool Rendering**: Function tools that execute on the server
|
||||
- ✅ **Streaming Responses**: Real-time Server-Sent Events
|
||||
- ✅ **State Management**: State schemas with predictive updates
|
||||
- ✅ **Human-in-the-Loop**: Approval workflows for sensitive operations
|
||||
|
||||
### Coming Soon to C#
|
||||
|
||||
The following advanced AG-UI features are available in the Python implementation and are planned for future C# releases:
|
||||
|
||||
- ⏳ **Generative UI**: Custom UI component generation
|
||||
- ⏳ **Advanced State Patterns**: Complex state synchronization scenarios
|
||||
|
||||
For the most up-to-date AG-UI features, see the [Python samples](../../../../python/samples/) for working examples.
|
||||
|
||||
### Related Documentation
|
||||
|
||||
- [AG-UI Overview](https://learn.microsoft.com/agent-framework/integrations/ag-ui/) - Complete AG-UI documentation
|
||||
- [Getting Started Tutorial](https://learn.microsoft.com/agent-framework/integrations/ag-ui/getting-started) - Step-by-step walkthrough
|
||||
- [Backend Tool Rendering](https://learn.microsoft.com/agent-framework/integrations/ag-ui/backend-tool-rendering) - Function tools tutorial
|
||||
- [Human-in-the-Loop](https://learn.microsoft.com/agent-framework/integrations/ag-ui/human-in-the-loop) - Approval workflows tutorial
|
||||
- [State Management](https://learn.microsoft.com/agent-framework/integrations/ag-ui/state-management) - State management tutorial
|
||||
- [Agent Framework Overview](https://learn.microsoft.com/agent-framework/overview/agent-framework-overview) - Core framework concepts
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
<PackageReference Include="AGUI.Client" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using AGUI.Abstractions;
|
||||
using AGUI.Client;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:8888";
|
||||
|
||||
Console.WriteLine($"Connecting to AG-UI server at: {serverUrl}\n");
|
||||
|
||||
// Create the AG-UI client agent
|
||||
using HttpClient httpClient = new()
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(60)
|
||||
};
|
||||
|
||||
AGUIChatClient chatClient = new(new(httpClient, serverUrl));
|
||||
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent");
|
||||
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Get user input
|
||||
Console.Write("\nUser (:q or quit to exit): ");
|
||||
string? message = Console.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
Console.WriteLine("Request cannot be empty.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message is ":q" or "quit")
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
messages.Add(new ChatMessage(ChatRole.User, message));
|
||||
|
||||
// Stream the response
|
||||
bool isFirstUpdate = true;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
{
|
||||
// AGUIChatClient is stateless and never surfaces a ConversationId; the thread
|
||||
// id is carried on the AG-UI RUN_STARTED event's raw representation.
|
||||
threadId = (chatUpdate.RawRepresentation as RunStartedEvent)?.ThreadId;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
|
||||
// Display streaming text content
|
||||
foreach (AIContent content in update.Contents)
|
||||
{
|
||||
if (content is TextContent textContent)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write(textContent.Text);
|
||||
Console.ResetColor();
|
||||
}
|
||||
else if (content is ErrorContent errorContent)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"\n[Error: {errorContent.Message}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\nAn error occurred: {ex.Message}");
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
using OpenAI.Chat;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.AddAGUIServer();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
|
||||
|
||||
// Create the AI agent
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
ChatClient chatClient = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
// Map the AG-UI agent endpoint
|
||||
app.MapAGUIServer("/", agent);
|
||||
|
||||
await app.RunAsync();
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5253",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7047;http://localhost:5253",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
<PackageReference Include="AGUI.Client" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using AGUI.Abstractions;
|
||||
using AGUI.Client;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:8888";
|
||||
|
||||
Console.WriteLine($"Connecting to AG-UI server at: {serverUrl}\n");
|
||||
|
||||
// Create the AG-UI client agent
|
||||
using HttpClient httpClient = new()
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(60)
|
||||
};
|
||||
|
||||
AGUIChatClient chatClient = new(new(httpClient, serverUrl));
|
||||
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent");
|
||||
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Get user input
|
||||
Console.Write("\nUser (:q or quit to exit): ");
|
||||
string? message = Console.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
Console.WriteLine("Request cannot be empty.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message is ":q" or "quit")
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
messages.Add(new ChatMessage(ChatRole.User, message));
|
||||
|
||||
// Stream the response
|
||||
bool isFirstUpdate = true;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
{
|
||||
// AGUIChatClient is stateless and never surfaces a ConversationId; the thread
|
||||
// id is carried on the AG-UI RUN_STARTED event's raw representation.
|
||||
threadId = (chatUpdate.RawRepresentation as RunStartedEvent)?.ThreadId;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
|
||||
// Display streaming content
|
||||
foreach (AIContent content in update.Contents)
|
||||
{
|
||||
switch (content)
|
||||
{
|
||||
case TextContent textContent:
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write(textContent.Text);
|
||||
Console.ResetColor();
|
||||
break;
|
||||
|
||||
case FunctionCallContent functionCallContent:
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Function Call - Name: {functionCallContent.Name}]");
|
||||
|
||||
// Display individual parameters
|
||||
if (functionCallContent.Arguments != null)
|
||||
{
|
||||
foreach (var kvp in functionCallContent.Arguments)
|
||||
{
|
||||
Console.WriteLine($" Parameter: {kvp.Key} = {kvp.Value}");
|
||||
}
|
||||
}
|
||||
Console.ResetColor();
|
||||
break;
|
||||
|
||||
case FunctionResultContent functionResultContent:
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.WriteLine($"\n[Function Result - CallId: {functionResultContent.CallId}]");
|
||||
|
||||
if (functionResultContent.Exception != null)
|
||||
{
|
||||
Console.WriteLine($" Exception: {functionResultContent.Exception}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" Result: {functionResultContent.Result}");
|
||||
}
|
||||
Console.ResetColor();
|
||||
break;
|
||||
|
||||
case ErrorContent errorContent:
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"\n[Error: {errorContent.Message}]");
|
||||
Console.ResetColor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\nAn error occurred: {ex.Message}");
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenAI.Chat;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
options.SerializerOptions.TypeInfoResolverChain.Add(SampleJsonSerializerContext.Default));
|
||||
builder.Services.AddAGUIServer();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
|
||||
|
||||
// Define the function tool
|
||||
[Description("Search for restaurants in a location.")]
|
||||
static RestaurantSearchResponse SearchRestaurants(
|
||||
[Description("The restaurant search request")] RestaurantSearchRequest request)
|
||||
{
|
||||
// Simulated restaurant data
|
||||
string cuisine = request.Cuisine == "any" ? "Italian" : request.Cuisine;
|
||||
|
||||
return new RestaurantSearchResponse
|
||||
{
|
||||
Location = request.Location,
|
||||
Cuisine = request.Cuisine,
|
||||
Results =
|
||||
[
|
||||
new RestaurantInfo
|
||||
{
|
||||
Name = "The Golden Fork",
|
||||
Cuisine = cuisine,
|
||||
Rating = 4.5,
|
||||
Address = $"123 Main St, {request.Location}"
|
||||
},
|
||||
new RestaurantInfo
|
||||
{
|
||||
Name = "Spice Haven",
|
||||
Cuisine = cuisine == "Italian" ? "Indian" : cuisine,
|
||||
Rating = 4.7,
|
||||
Address = $"456 Oak Ave, {request.Location}"
|
||||
},
|
||||
new RestaurantInfo
|
||||
{
|
||||
Name = "Green Leaf",
|
||||
Cuisine = "Vegetarian",
|
||||
Rating = 4.3,
|
||||
Address = $"789 Elm Rd, {request.Location}"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Get JsonSerializerOptions from the configured HTTP JSON options
|
||||
Microsoft.AspNetCore.Http.Json.JsonOptions jsonOptions = app.Services.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>().Value;
|
||||
|
||||
// Create tool with serializer options
|
||||
AITool[] tools =
|
||||
[
|
||||
AIFunctionFactory.Create(
|
||||
SearchRestaurants,
|
||||
serializerOptions: jsonOptions.SerializerOptions)
|
||||
];
|
||||
|
||||
// Create the AI agent with tools
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
ChatClient chatClient = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
ChatClientAgent agent = chatClient.AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant with access to restaurant information.",
|
||||
tools: tools);
|
||||
|
||||
// Map the AG-UI agent endpoint
|
||||
app.MapAGUIServer("/", agent);
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
// Define request/response types for the tool
|
||||
internal sealed class RestaurantSearchRequest
|
||||
{
|
||||
public string Location { get; set; } = string.Empty;
|
||||
public string Cuisine { get; set; } = "any";
|
||||
}
|
||||
|
||||
internal sealed class RestaurantSearchResponse
|
||||
{
|
||||
public string Location { get; set; } = string.Empty;
|
||||
public string Cuisine { get; set; } = string.Empty;
|
||||
public RestaurantInfo[] Results { get; set; } = [];
|
||||
}
|
||||
|
||||
internal sealed class RestaurantInfo
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Cuisine { get; set; } = string.Empty;
|
||||
public double Rating { get; set; }
|
||||
public string Address { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
// JSON serialization context for source generation
|
||||
[JsonSerializable(typeof(RestaurantSearchRequest))]
|
||||
[JsonSerializable(typeof(RestaurantSearchResponse))]
|
||||
internal sealed partial class SampleJsonSerializerContext : JsonSerializerContext;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5253",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7047;http://localhost:5253",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
<PackageReference Include="AGUI.Client" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using AGUI.Abstractions;
|
||||
using AGUI.Client;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:8888";
|
||||
|
||||
Console.WriteLine($"Connecting to AG-UI server at: {serverUrl}\n");
|
||||
|
||||
// Define a frontend function tool
|
||||
[Description("Get the user's current location from GPS.")]
|
||||
static string GetUserLocation()
|
||||
{
|
||||
// Access client-side GPS
|
||||
return "Amsterdam, Netherlands (52.37°N, 4.90°E)";
|
||||
}
|
||||
|
||||
// Create frontend tools
|
||||
AITool[] frontendTools = [AIFunctionFactory.Create(GetUserLocation)];
|
||||
|
||||
// Create the AG-UI client agent with tools
|
||||
using HttpClient httpClient = new()
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(60)
|
||||
};
|
||||
|
||||
AGUIChatClient chatClient = new(new(httpClient, serverUrl));
|
||||
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent",
|
||||
tools: frontendTools);
|
||||
|
||||
AgentSession session = await agent.CreateSessionAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Get user input
|
||||
Console.Write("\nUser (:q or quit to exit): ");
|
||||
string? message = Console.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
Console.WriteLine("Request cannot be empty.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message is ":q" or "quit")
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
messages.Add(new ChatMessage(ChatRole.User, message));
|
||||
|
||||
// Stream the response
|
||||
bool isFirstUpdate = true;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
{
|
||||
// AGUIChatClient is stateless and never surfaces a ConversationId; the thread
|
||||
// id is carried on the AG-UI RUN_STARTED event's raw representation.
|
||||
threadId = (chatUpdate.RawRepresentation as RunStartedEvent)?.ThreadId;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
|
||||
// Display streaming content
|
||||
foreach (AIContent content in update.Contents)
|
||||
{
|
||||
if (content is TextContent textContent)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write(textContent.Text);
|
||||
Console.ResetColor();
|
||||
}
|
||||
else if (content is FunctionCallContent functionCallContent)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Client Tool Call - Name: {functionCallContent.Name}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
else if (content is FunctionResultContent functionResultContent)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.WriteLine($"[Client Tool Result: {functionResultContent.Result}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
else if (content is ErrorContent errorContent)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"\n[Error: {errorContent.Message}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\nAn error occurred: {ex.Message}");
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
using OpenAI.Chat;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.AddAGUIServer();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
|
||||
|
||||
// Create the AI agent
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
ChatClient chatClient = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
// Map the AG-UI agent endpoint
|
||||
app.MapAGUIServer("/", agent);
|
||||
|
||||
await app.RunAsync();
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5253",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7047;http://localhost:5253",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
<PackageReference Include="AGUI.Client" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using AGUI.Client;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:5100";
|
||||
|
||||
// Connect to the AG-UI server
|
||||
using HttpClient httpClient = new()
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(60)
|
||||
};
|
||||
|
||||
AGUIChatClient chatClient = new(new(httpClient, serverUrl));
|
||||
|
||||
// Create agent
|
||||
ChatClientAgent baseAgent = chatClient.AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
// Use default JSON serializer options
|
||||
JsonSerializerOptions jsonSerializerOptions = JsonSerializerOptions.Default;
|
||||
|
||||
// Wrap the agent with ServerFunctionApprovalClientAgent
|
||||
ServerFunctionApprovalClientAgent agent = new(baseAgent, jsonSerializerOptions);
|
||||
|
||||
List<ChatMessage> messages = [];
|
||||
AgentSession? session = null;
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("Ask a question (or type 'exit' to quit):");
|
||||
Console.ResetColor();
|
||||
|
||||
string? input;
|
||||
while ((input = Console.ReadLine()) != null && !input.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
messages.Add(new ChatMessage(ChatRole.User, input));
|
||||
Console.WriteLine();
|
||||
|
||||
#pragma warning disable MEAI001
|
||||
List<AIContent> approvalResponses = [];
|
||||
|
||||
do
|
||||
{
|
||||
approvalResponses.Clear();
|
||||
|
||||
List<AgentResponseUpdate> chatResponseUpdates = [];
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session, cancellationToken: default))
|
||||
{
|
||||
chatResponseUpdates.Add(update);
|
||||
foreach (AIContent content in update.Contents)
|
||||
{
|
||||
switch (content)
|
||||
{
|
||||
case ToolApprovalRequestContent approvalRequest when approvalRequest.ToolCall is FunctionCallContent fcc:
|
||||
DisplayApprovalRequest(approvalRequest, fcc);
|
||||
|
||||
Console.Write($"\nApprove '{fcc.Name}'? (yes/no): ");
|
||||
string? userInput = Console.ReadLine();
|
||||
bool approved = userInput?.ToUpperInvariant() is "YES" or "Y";
|
||||
|
||||
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved);
|
||||
|
||||
if (approvalRequest.AdditionalProperties != null)
|
||||
{
|
||||
approvalResponse.AdditionalProperties = [];
|
||||
foreach (var kvp in approvalRequest.AdditionalProperties)
|
||||
{
|
||||
approvalResponse.AdditionalProperties[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
approvalResponses.Add(approvalResponse);
|
||||
break;
|
||||
|
||||
case TextContent textContent:
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write(textContent.Text);
|
||||
Console.ResetColor();
|
||||
break;
|
||||
|
||||
case FunctionCallContent functionCall:
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"[Tool Call - Name: {functionCall.Name}]");
|
||||
if (functionCall.Arguments is { } arguments)
|
||||
{
|
||||
Console.WriteLine($" Parameters: {JsonSerializer.Serialize(arguments)}");
|
||||
}
|
||||
Console.ResetColor();
|
||||
break;
|
||||
|
||||
case FunctionResultContent functionResult:
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.WriteLine($"[Tool Result: {functionResult.Result}]");
|
||||
Console.ResetColor();
|
||||
break;
|
||||
|
||||
case ErrorContent error:
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"[Error: {error.Message}]");
|
||||
Console.ResetColor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AgentResponse response = chatResponseUpdates.ToAgentResponse();
|
||||
messages.AddRange(response.Messages);
|
||||
foreach (AIContent approvalResponse in approvalResponses)
|
||||
{
|
||||
messages.Add(new ChatMessage(ChatRole.Tool, [approvalResponse]));
|
||||
}
|
||||
}
|
||||
while (approvalResponses.Count > 0);
|
||||
#pragma warning restore MEAI001
|
||||
|
||||
Console.WriteLine("\n");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("Ask another question (or type 'exit' to quit):");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
#pragma warning disable MEAI001
|
||||
static void DisplayApprovalRequest(ToolApprovalRequestContent approvalRequest, FunctionCallContent fcc)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("============================================================");
|
||||
Console.WriteLine("APPROVAL REQUIRED");
|
||||
Console.WriteLine("============================================================");
|
||||
Console.WriteLine($"Function: {fcc.Name}");
|
||||
|
||||
if (fcc.Arguments != null)
|
||||
{
|
||||
Console.WriteLine("Arguments:");
|
||||
foreach (var arg in fcc.Arguments)
|
||||
{
|
||||
Console.WriteLine($" {arg.Key} = {arg.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("============================================================");
|
||||
Console.ResetColor();
|
||||
}
|
||||
#pragma warning restore MEAI001
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using ServerFunctionApproval;
|
||||
|
||||
/// <summary>
|
||||
/// A delegating agent that handles server function approval requests and responses.
|
||||
/// Transforms between ToolApprovalRequestContent/ToolApprovalResponseContent
|
||||
/// and the server's request_approval tool call pattern.
|
||||
/// </summary>
|
||||
internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
public ServerFunctionApprovalClientAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions)
|
||||
: base(innerAgent)
|
||||
{
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken)
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Process and transform approval messages, creating a new message list
|
||||
var processedMessages = ProcessOutgoingServerFunctionApprovals(messages.ToList(), this._jsonSerializerOptions);
|
||||
|
||||
// Run the inner agent and intercept any approval requests
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(
|
||||
processedMessages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return ProcessIncomingServerApprovalRequests(update, this._jsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable MEAI001 // Type is for evaluation purposes only
|
||||
private static FunctionResultContent ConvertApprovalResponseToToolResult(ToolApprovalResponseContent approvalResponse, JsonSerializerOptions jsonOptions)
|
||||
{
|
||||
return new FunctionResultContent(
|
||||
callId: approvalResponse.RequestId,
|
||||
result: JsonSerializer.SerializeToElement(
|
||||
new ApprovalResponse
|
||||
{
|
||||
ApprovalId = approvalResponse.RequestId,
|
||||
Approved = approvalResponse.Approved
|
||||
},
|
||||
jsonOptions));
|
||||
}
|
||||
|
||||
private static List<ChatMessage> CopyMessagesUpToIndex(List<ChatMessage> messages, int index)
|
||||
{
|
||||
var result = new List<ChatMessage>(index);
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
result.Add(messages[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<AIContent> CopyContentsUpToIndex(IList<AIContent> contents, int index)
|
||||
{
|
||||
var result = new List<AIContent>(index);
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
result.Add(contents[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<ChatMessage> ProcessOutgoingServerFunctionApprovals(
|
||||
List<ChatMessage> messages,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
List<ChatMessage>? result = null;
|
||||
|
||||
Dictionary<string, ToolApprovalRequestContent> approvalRequests = [];
|
||||
for (var messageIndex = 0; messageIndex < messages.Count; messageIndex++)
|
||||
{
|
||||
var message = messages[messageIndex];
|
||||
List<AIContent>? transformedContents = null;
|
||||
|
||||
// Process each content item in the message
|
||||
HashSet<string> approvalCalls = [];
|
||||
for (var contentIndex = 0; contentIndex < message.Contents.Count; contentIndex++)
|
||||
{
|
||||
var content = message.Contents[contentIndex];
|
||||
|
||||
// Handle pending approval requests (transform to tool call)
|
||||
if (content is ToolApprovalRequestContent approvalRequest &&
|
||||
approvalRequest.AdditionalProperties?.TryGetValue("original_function", out var originalFunction) == true &&
|
||||
originalFunction is FunctionCallContent original)
|
||||
{
|
||||
approvalRequests[approvalRequest.RequestId] = approvalRequest;
|
||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
|
||||
transformedContents.Add(original);
|
||||
}
|
||||
// Handle pending approval responses (transform to tool result)
|
||||
else if (content is ToolApprovalResponseContent approvalResponse &&
|
||||
approvalRequests.TryGetValue(approvalResponse.RequestId, out var correspondingRequest))
|
||||
{
|
||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
|
||||
transformedContents.Add(ConvertApprovalResponseToToolResult(approvalResponse, jsonSerializerOptions));
|
||||
approvalRequests.Remove(approvalResponse.RequestId);
|
||||
correspondingRequest.AdditionalProperties?.Remove("original_function");
|
||||
}
|
||||
// Skip historical approval content
|
||||
else if (content is FunctionCallContent { Name: "request_approval" } approvalCall)
|
||||
{
|
||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
|
||||
approvalCalls.Add(approvalCall.CallId);
|
||||
}
|
||||
else if (content is FunctionResultContent functionResult &&
|
||||
approvalCalls.Contains(functionResult.CallId))
|
||||
{
|
||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
|
||||
approvalCalls.Remove(functionResult.CallId);
|
||||
}
|
||||
else
|
||||
{
|
||||
transformedContents?.Add(content);
|
||||
}
|
||||
}
|
||||
|
||||
if (transformedContents?.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (transformedContents != null)
|
||||
{
|
||||
// We made changes to contents, so use transformedContents
|
||||
var newMessage = new ChatMessage(message.Role, transformedContents)
|
||||
{
|
||||
AuthorName = message.AuthorName,
|
||||
MessageId = message.MessageId,
|
||||
CreatedAt = message.CreatedAt,
|
||||
RawRepresentation = message.RawRepresentation,
|
||||
AdditionalProperties = message.AdditionalProperties
|
||||
};
|
||||
result ??= CopyMessagesUpToIndex(messages, messageIndex);
|
||||
result.Add(newMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're already copying messages, so copy this unchanged message too
|
||||
result?.Add(message);
|
||||
}
|
||||
// If result is null, we haven't made any changes yet, so keep processing
|
||||
}
|
||||
|
||||
return result ?? messages;
|
||||
}
|
||||
|
||||
private static AgentResponseUpdate ProcessIncomingServerApprovalRequests(
|
||||
AgentResponseUpdate update,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
IList<AIContent>? updatedContents = null;
|
||||
for (var i = 0; i < update.Contents.Count; i++)
|
||||
{
|
||||
var content = update.Contents[i];
|
||||
if (content is FunctionCallContent { Name: "request_approval" } request)
|
||||
{
|
||||
updatedContents ??= [.. update.Contents];
|
||||
|
||||
// Serialize the function arguments as JsonElement
|
||||
ApprovalRequest? approvalRequest;
|
||||
if (request.Arguments?.TryGetValue("request", out var reqObj) == true &&
|
||||
reqObj is JsonElement je)
|
||||
{
|
||||
approvalRequest = (ApprovalRequest?)je.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalRequest)));
|
||||
}
|
||||
else
|
||||
{
|
||||
approvalRequest = null;
|
||||
}
|
||||
|
||||
if (approvalRequest == null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to deserialize approval request.");
|
||||
}
|
||||
|
||||
var functionCallArgs = (Dictionary<string, object?>?)approvalRequest.FunctionArguments?
|
||||
.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(Dictionary<string, object?>)));
|
||||
|
||||
var approvalRequestContent = new ToolApprovalRequestContent(
|
||||
requestId: approvalRequest.ApprovalId,
|
||||
new FunctionCallContent(
|
||||
callId: approvalRequest.ApprovalId,
|
||||
name: approvalRequest.FunctionName,
|
||||
arguments: functionCallArgs));
|
||||
|
||||
approvalRequestContent.AdditionalProperties ??= [];
|
||||
approvalRequestContent.AdditionalProperties["original_function"] = content;
|
||||
|
||||
updatedContents[i] = approvalRequestContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedContents is not null)
|
||||
{
|
||||
var chatUpdate = update.AsChatResponseUpdate();
|
||||
return new AgentResponseUpdate(new ChatResponseUpdate()
|
||||
{
|
||||
Role = chatUpdate.Role,
|
||||
Contents = updatedContents,
|
||||
MessageId = chatUpdate.MessageId,
|
||||
AuthorName = chatUpdate.AuthorName,
|
||||
CreatedAt = chatUpdate.CreatedAt,
|
||||
RawRepresentation = chatUpdate.RawRepresentation,
|
||||
ResponseId = chatUpdate.ResponseId,
|
||||
AdditionalProperties = chatUpdate.AdditionalProperties
|
||||
})
|
||||
{
|
||||
AgentId = update.AgentId,
|
||||
ContinuationToken = update.ContinuationToken,
|
||||
};
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
}
|
||||
#pragma warning restore MEAI001
|
||||
|
||||
namespace ServerFunctionApproval
|
||||
{
|
||||
public sealed class ApprovalRequest
|
||||
{
|
||||
[JsonPropertyName("approval_id")]
|
||||
public required string ApprovalId { get; init; }
|
||||
|
||||
[JsonPropertyName("function_name")]
|
||||
public required string FunctionName { get; init; }
|
||||
|
||||
[JsonPropertyName("function_arguments")]
|
||||
public JsonElement? FunctionArguments { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; init; }
|
||||
}
|
||||
|
||||
public sealed class ApprovalResponse
|
||||
{
|
||||
[JsonPropertyName("approval_id")]
|
||||
public required string ApprovalId { get; init; }
|
||||
|
||||
[JsonPropertyName("approved")]
|
||||
public required bool Approved { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
using Microsoft.AspNetCore.Http.Json;
|
||||
using Microsoft.AspNetCore.HttpLogging;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenAI.Chat;
|
||||
using ServerFunctionApproval;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddHttpLogging(logging =>
|
||||
{
|
||||
logging.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders | HttpLoggingFields.RequestBody
|
||||
| HttpLoggingFields.ResponsePropertiesAndHeaders | HttpLoggingFields.ResponseBody;
|
||||
logging.RequestBodyLogLimit = int.MaxValue;
|
||||
logging.ResponseBodyLogLimit = int.MaxValue;
|
||||
});
|
||||
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
options.SerializerOptions.TypeInfoResolverChain.Add(ApprovalJsonContext.Default));
|
||||
builder.Services.AddAGUIServer();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
app.UseHttpLogging();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
|
||||
|
||||
// Define approval-required tool
|
||||
[Description("Approve the expense report.")]
|
||||
static string ApproveExpenseReport(string expenseReportId)
|
||||
{
|
||||
return $"Expense report {expenseReportId} approved";
|
||||
}
|
||||
|
||||
// Get JsonSerializerOptions
|
||||
var jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>().Value;
|
||||
|
||||
// Create approval-required tool
|
||||
#pragma warning disable MEAI001 // Type is for evaluation purposes only
|
||||
AITool[] tools = [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(ApproveExpenseReport))];
|
||||
#pragma warning restore MEAI001
|
||||
|
||||
// Create base agent
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
ChatClient openAIChatClient = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
ChatClientAgent baseAgent = openAIChatClient.AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant in charge of approving expenses",
|
||||
tools: tools);
|
||||
|
||||
// Wrap with ServerFunctionApprovalAgent
|
||||
var agent = new ServerFunctionApprovalAgent(baseAgent, jsonOptions.SerializerOptions);
|
||||
|
||||
app.MapAGUIServer("/", agent);
|
||||
await app.RunAsync();
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5100",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7047;http://localhost:5100",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using ServerFunctionApproval;
|
||||
|
||||
/// <summary>
|
||||
/// A delegating agent that handles function approval requests on the server side.
|
||||
/// Transforms between ToolApprovalRequestContent/ToolApprovalResponseContent
|
||||
/// and the request_approval tool call pattern for client communication.
|
||||
/// </summary>
|
||||
internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
public ServerFunctionApprovalAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions)
|
||||
: base(innerAgent)
|
||||
{
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken)
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Process and transform incoming approval responses from client, creating a new message list
|
||||
var processedMessages = ProcessIncomingFunctionApprovals(messages.ToList(), this._jsonSerializerOptions);
|
||||
|
||||
// Run the inner agent and intercept any approval requests
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(
|
||||
processedMessages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return ProcessOutgoingApprovalRequests(update, this._jsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable MEAI001 // Type is for evaluation purposes only
|
||||
private static ToolApprovalRequestContent ConvertToolCallToApprovalRequest(FunctionCallContent toolCall, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
if (toolCall.Name != "request_approval" || toolCall.Arguments == null)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid request_approval tool call");
|
||||
}
|
||||
|
||||
var request = (toolCall.Arguments.TryGetValue("request", out var reqObj) &&
|
||||
reqObj is JsonElement argsElement &&
|
||||
argsElement.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalRequest))) is ApprovalRequest approvalRequest &&
|
||||
approvalRequest != null ? approvalRequest : null) ?? throw new InvalidOperationException("Failed to deserialize approval request from tool call");
|
||||
return new ToolApprovalRequestContent(
|
||||
requestId: request.ApprovalId,
|
||||
new FunctionCallContent(
|
||||
callId: request.ApprovalId,
|
||||
name: request.FunctionName,
|
||||
arguments: request.FunctionArguments));
|
||||
}
|
||||
|
||||
private static ToolApprovalResponseContent ConvertToolResultToApprovalResponse(FunctionResultContent result, ToolApprovalRequestContent approval, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
var approvalResponse = (result.Result is JsonElement je ?
|
||||
(ApprovalResponse?)je.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalResponse))) :
|
||||
result.Result is string str ?
|
||||
(ApprovalResponse?)JsonSerializer.Deserialize(str, jsonSerializerOptions.GetTypeInfo(typeof(ApprovalResponse))) :
|
||||
result.Result as ApprovalResponse) ?? throw new InvalidOperationException("Failed to deserialize approval response from tool result");
|
||||
return approval.CreateResponse(approvalResponse.Approved);
|
||||
}
|
||||
#pragma warning restore MEAI001
|
||||
|
||||
private static List<ChatMessage> CopyMessagesUpToIndex(List<ChatMessage> messages, int index)
|
||||
{
|
||||
var result = new List<ChatMessage>(index);
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
result.Add(messages[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<AIContent> CopyContentsUpToIndex(IList<AIContent> contents, int index)
|
||||
{
|
||||
var result = new List<AIContent>(index);
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
result.Add(contents[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<ChatMessage> ProcessIncomingFunctionApprovals(
|
||||
List<ChatMessage> messages,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
List<ChatMessage>? result = null;
|
||||
|
||||
// Track approval ID to original call ID mapping
|
||||
_ = new Dictionary<string, string>();
|
||||
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
Dictionary<string, ToolApprovalRequestContent> trackedRequestApprovalToolCalls = []; // Remote approvals
|
||||
for (int messageIndex = 0; messageIndex < messages.Count; messageIndex++)
|
||||
{
|
||||
var message = messages[messageIndex];
|
||||
List<AIContent>? transformedContents = null;
|
||||
for (int j = 0; j < message.Contents.Count; j++)
|
||||
{
|
||||
var content = message.Contents[j];
|
||||
if (content is FunctionCallContent { Name: "request_approval" } toolCall)
|
||||
{
|
||||
result ??= CopyMessagesUpToIndex(messages, messageIndex);
|
||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, j);
|
||||
var approvalRequest = ConvertToolCallToApprovalRequest(toolCall, jsonSerializerOptions);
|
||||
transformedContents.Add(approvalRequest);
|
||||
trackedRequestApprovalToolCalls[toolCall.CallId] = approvalRequest;
|
||||
result.Add(new ChatMessage(message.Role, transformedContents)
|
||||
{
|
||||
AuthorName = message.AuthorName,
|
||||
MessageId = message.MessageId,
|
||||
CreatedAt = message.CreatedAt,
|
||||
RawRepresentation = message.RawRepresentation,
|
||||
AdditionalProperties = message.AdditionalProperties
|
||||
});
|
||||
}
|
||||
else if (content is FunctionResultContent toolResult &&
|
||||
trackedRequestApprovalToolCalls.TryGetValue(toolResult.CallId, out var approval))
|
||||
{
|
||||
result ??= CopyMessagesUpToIndex(messages, messageIndex);
|
||||
transformedContents ??= CopyContentsUpToIndex(message.Contents, j);
|
||||
var approvalResponse = ConvertToolResultToApprovalResponse(toolResult, approval, jsonSerializerOptions);
|
||||
transformedContents.Add(approvalResponse);
|
||||
result.Add(new ChatMessage(message.Role, transformedContents)
|
||||
{
|
||||
AuthorName = message.AuthorName,
|
||||
MessageId = message.MessageId,
|
||||
CreatedAt = message.CreatedAt,
|
||||
RawRepresentation = message.RawRepresentation,
|
||||
AdditionalProperties = message.AdditionalProperties
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
result?.Add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore MEAI001
|
||||
|
||||
return result ?? messages;
|
||||
}
|
||||
|
||||
private static AgentResponseUpdate ProcessOutgoingApprovalRequests(
|
||||
AgentResponseUpdate update,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
IList<AIContent>? updatedContents = null;
|
||||
for (var i = 0; i < update.Contents.Count; i++)
|
||||
{
|
||||
var content = update.Contents[i];
|
||||
#pragma warning disable MEAI001 // Type is for evaluation purposes only
|
||||
if (content is ToolApprovalRequestContent request && request.ToolCall is FunctionCallContent functionCall)
|
||||
{
|
||||
updatedContents ??= [.. update.Contents];
|
||||
var approvalId = request.RequestId;
|
||||
|
||||
var approvalData = new ApprovalRequest
|
||||
{
|
||||
ApprovalId = approvalId,
|
||||
FunctionName = functionCall.Name,
|
||||
FunctionArguments = functionCall.Arguments,
|
||||
Message = $"Approve execution of '{functionCall.Name}'?"
|
||||
};
|
||||
|
||||
updatedContents[i] = new FunctionCallContent(
|
||||
callId: approvalId,
|
||||
name: "request_approval",
|
||||
arguments: new Dictionary<string, object?> { ["request"] = approvalData });
|
||||
}
|
||||
#pragma warning restore MEAI001
|
||||
}
|
||||
|
||||
if (updatedContents is not null)
|
||||
{
|
||||
var chatUpdate = update.AsChatResponseUpdate();
|
||||
// Yield a tool call update that represents the approval request
|
||||
return new AgentResponseUpdate(new ChatResponseUpdate()
|
||||
{
|
||||
Role = chatUpdate.Role,
|
||||
Contents = updatedContents,
|
||||
MessageId = chatUpdate.MessageId,
|
||||
AuthorName = chatUpdate.AuthorName,
|
||||
CreatedAt = chatUpdate.CreatedAt,
|
||||
RawRepresentation = chatUpdate.RawRepresentation,
|
||||
ResponseId = chatUpdate.ResponseId,
|
||||
AdditionalProperties = chatUpdate.AdditionalProperties
|
||||
})
|
||||
{
|
||||
AgentId = update.AgentId,
|
||||
ContinuationToken = update.ContinuationToken
|
||||
};
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ServerFunctionApproval
|
||||
{
|
||||
// Define approval models
|
||||
public sealed class ApprovalRequest
|
||||
{
|
||||
[JsonPropertyName("approval_id")]
|
||||
public required string ApprovalId { get; init; }
|
||||
|
||||
[JsonPropertyName("function_name")]
|
||||
public required string FunctionName { get; init; }
|
||||
|
||||
[JsonPropertyName("function_arguments")]
|
||||
public IDictionary<string, object?>? FunctionArguments { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; init; }
|
||||
}
|
||||
|
||||
public sealed class ApprovalResponse
|
||||
{
|
||||
[JsonPropertyName("approval_id")]
|
||||
public required string ApprovalId { get; init; }
|
||||
|
||||
[JsonPropertyName("approved")]
|
||||
public required bool Approved { get; init; }
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(ApprovalRequest))]
|
||||
[JsonSerializable(typeof(ApprovalResponse))]
|
||||
[JsonSerializable(typeof(Dictionary<string, object?>))]
|
||||
public sealed partial class ApprovalJsonContext : JsonSerializerContext;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
<PackageReference Include="AGUI.Client" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user