chore: import upstream snapshot with attribution
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) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.Agents.ObjectModel;
using Microsoft.Agents.ObjectModel.Abstractions;
using Microsoft.Agents.ObjectModel.Yaml;
using Microsoft.Extensions.Configuration;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
/// <summary>
/// Helper methods for creating <see cref="BotElement"/> from YAML.
/// </summary>
internal static class AgentBotElementYaml
{
/// <summary>
/// Convert the given YAML text to a <see cref="GptComponentMetadata"/> model.
/// </summary>
/// <param name="text">YAML representation of the <see cref="BotElement"/> to use to create the prompt function.</param>
/// <param name="configuration">Optional <see cref="IConfiguration"/> instance which provides environment variables to the template.</param>
[RequiresDynamicCode("Calls YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()")]
public static GptComponentMetadata FromYaml(string text, IConfiguration? configuration = null)
{
Throw.IfNullOrEmpty(text);
using var yamlReader = new StringReader(text);
BotElement rootElement = YamlSerializer.Deserialize<BotElement>(yamlReader) ?? throw new InvalidDataException("Text does not contain a valid agent definition.");
if (rootElement is not GptComponentMetadata promptAgent)
{
throw new InvalidDataException($"Unsupported root element: {rootElement.GetType().Name}. Expected an {nameof(GptComponentMetadata)}.");
}
var botDefinition = WrapPromptAgentWithBot(promptAgent, configuration);
return botDefinition.Descendants().OfType<GptComponentMetadata>().First();
}
#region private
private sealed class AgentFeatureConfiguration : IFeatureConfiguration
{
public long GetInt64Value(string settingName, long defaultValue) => defaultValue;
public string GetStringValue(string settingName, string defaultValue) => defaultValue;
public bool IsEnvironmentFeatureEnabled(string featureName, bool defaultValue) => true;
public bool IsTenantFeatureEnabled(string featureName, bool defaultValue) => defaultValue;
}
public static BotDefinition WrapPromptAgentWithBot(this GptComponentMetadata element, IConfiguration? configuration = null)
{
var botBuilder =
new BotDefinition.Builder
{
Components =
{
new GptComponent.Builder
{
SchemaName = "default-schema",
Metadata = element.ToBuilder(),
}
}
};
if (configuration is not null)
{
foreach (var kvp in configuration.AsEnumerable().Where(kvp => kvp.Value is not null))
{
botBuilder.EnvironmentVariables.Add(new EnvironmentVariableDefinition.Builder()
{
SchemaName = kvp.Key,
Id = Guid.NewGuid(),
DisplayName = kvp.Key,
ValueComponent = new EnvironmentVariableValue.Builder()
{
Id = Guid.NewGuid(),
Value = kvp.Value!,
},
});
}
}
return botBuilder.Build();
}
#endregion
}
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.ObjectModel;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
/// <summary>
/// Provides a <see cref="PromptAgentFactory"/> which aggregates multiple agent factories.
/// </summary>
public sealed class AggregatorPromptAgentFactory : PromptAgentFactory
{
private readonly PromptAgentFactory[] _agentFactories;
/// <summary>Initializes the instance.</summary>
/// <param name="agentFactories">Ordered <see cref="PromptAgentFactory"/> instances to aggregate.</param>
/// <remarks>
/// Where multiple <see cref="PromptAgentFactory"/> instances are provided, the first factory that supports the <see cref="GptComponentMetadata"/> will be used.
/// </remarks>
public AggregatorPromptAgentFactory(params PromptAgentFactory[] agentFactories)
{
Throw.IfNullOrEmpty(agentFactories);
foreach (PromptAgentFactory agentFactory in agentFactories)
{
Throw.IfNull(agentFactory, nameof(agentFactories));
}
this._agentFactories = agentFactories;
}
/// <inheritdoc/>
public override async Task<AIAgent?> TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
{
Throw.IfNull(promptAgent);
foreach (var agentFactory in this._agentFactories)
{
var agent = await agentFactory.TryCreateAsync(promptAgent, cancellationToken).ConfigureAwait(false);
if (agent is not null)
{
return agent;
}
}
return null;
}
}
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.ObjectModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.PowerFx;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
/// <summary>
/// Provides an <see cref="PromptAgentFactory"/> which creates instances of <see cref="ChatClientAgent"/>.
/// </summary>
public sealed class ChatClientPromptAgentFactory : PromptAgentFactory
{
/// <summary>
/// Creates a new instance of the <see cref="ChatClientPromptAgentFactory"/> class.
/// </summary>
public ChatClientPromptAgentFactory(IChatClient chatClient, IList<AIFunction>? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration)
{
Throw.IfNull(chatClient);
this._chatClient = chatClient;
this._functions = functions;
this._loggerFactory = loggerFactory;
}
/// <inheritdoc/>
public override Task<AIAgent?> TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
{
Throw.IfNull(promptAgent);
var options = new ChatClientAgentOptions()
{
Name = promptAgent.Name,
Description = promptAgent.Description,
ChatOptions = promptAgent.GetChatOptions(this.Engine, this._functions),
};
var agent = new ChatClientAgent(this._chatClient, options, this._loggerFactory);
return Task.FromResult<AIAgent?>(agent);
}
#region private
private readonly IChatClient _chatClient;
private readonly IList<AIFunction>? _functions;
private readonly ILoggerFactory? _loggerFactory;
#endregion
}
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.PowerFx;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="BoolExpression"/>.
/// </summary>
internal static class BoolExpressionExtensions
{
/// <summary>
/// Evaluates the given <see cref="BoolExpression"/> using the provided <see cref="RecalcEngine"/>.
/// </summary>
/// <param name="expression">Expression to evaluate.</param>
/// <param name="engine">Recalc engine to use for evaluation.</param>
/// <returns>The evaluated boolean value, or null if the expression is null or cannot be evaluated.</returns>
internal static bool? Eval(this BoolExpression? expression, RecalcEngine? engine)
{
if (expression is null)
{
return null;
}
if (expression.IsLiteral)
{
return expression.LiteralValue;
}
if (engine is null)
{
return null;
}
if (expression.IsExpression)
{
return engine.Eval(expression.ExpressionText!).AsBoolean();
}
else if (expression.IsVariableReference)
{
var formulaValue = engine.Eval(expression.VariableReference!.VariableName);
if (formulaValue is BooleanValue booleanValue)
{
return booleanValue.Value;
}
if (formulaValue is StringValue stringValue && bool.TryParse(stringValue.Value, out bool result))
{
return result;
}
}
return null;
}
}
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="CodeInterpreterTool"/>.
/// </summary>
internal static class CodeInterpreterToolExtensions
{
/// <summary>
/// Creates a <see cref="HostedCodeInterpreterTool"/> from a <see cref="CodeInterpreterTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="CodeInterpreterTool"/></param>
internal static HostedCodeInterpreterTool AsCodeInterpreterTool(this CodeInterpreterTool tool)
{
Throw.IfNull(tool);
return new HostedCodeInterpreterTool();
}
}
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="FileSearchTool"/>.
/// </summary>
internal static class FileSearchToolExtensions
{
/// <summary>
/// Create a <see cref="HostedFileSearchTool"/> from a <see cref="FileSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="FileSearchTool"/></param>
internal static HostedFileSearchTool CreateFileSearchTool(this FileSearchTool tool)
{
Throw.IfNull(tool);
return new HostedFileSearchTool()
{
MaximumResultCount = (int?)tool.MaximumResultCount?.LiteralValue,
Inputs = tool.VectorStoreIds?.LiteralValue.Select(id => (AIContent)new HostedVectorStoreContent(id)).ToList(),
};
}
}
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="InvokeClientTaskAction"/>.
/// </summary>
internal static class FunctionToolExtensions
{
/// <summary>
/// Creates a <see cref="AIFunctionDeclaration"/> from a <see cref="InvokeClientTaskAction"/>.
/// </summary>
/// <remarks>
/// If a matching function already exists in the provided list, it will be returned.
/// Otherwise, a new function declaration will be created.
/// </remarks>
/// <param name="tool">Instance of <see cref="InvokeClientTaskAction"/></param>
/// <param name="functions">Instance of <see cref="IList{AIFunction}"/></param>
internal static AITool CreateOrGetAITool(this InvokeClientTaskAction tool, IList<AIFunction>? functions)
{
Throw.IfNull(tool);
Throw.IfNull(tool.Name);
// use the tool from the provided list if it exists
if (functions is not null)
{
var function = functions.FirstOrDefault(f => tool.Matches(f));
if (function is not null)
{
return function;
}
}
return AIFunctionFactory.CreateDeclaration(
name: tool.Name,
description: tool.Description,
jsonSchema: tool.ClientActionInputSchema?.GetSchema() ?? s_defaultSchema);
}
/// <summary>
/// Checks if a <see cref="InvokeClientTaskAction"/> matches an <see cref="AITool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="InvokeClientTaskAction"/></param>
/// <param name="aiFunc">Instance of <see cref="AIFunction"/></param>
internal static bool Matches(this InvokeClientTaskAction tool, AIFunction aiFunc)
{
Throw.IfNull(tool);
Throw.IfNull(aiFunc);
return tool.Name == aiFunc.Name;
}
private static readonly JsonElement s_defaultSchema = JsonDocument.Parse("{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false}").RootElement;
}
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Globalization;
using Microsoft.PowerFx;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="IntExpression"/>.
/// </summary>
internal static class IntExpressionExtensions
{
/// <summary>
/// Evaluates the given <see cref="IntExpression"/> using the provided <see cref="RecalcEngine"/>.
/// </summary>
/// <param name="expression">Expression to evaluate.</param>
/// <param name="engine">Recalc engine to use for evaluation.</param>
/// <returns>The evaluated integer value, or null if the expression is null or cannot be evaluated.</returns>
internal static long? Eval(this IntExpression? expression, RecalcEngine? engine)
{
if (expression is null)
{
return null;
}
if (expression.IsLiteral)
{
return expression.LiteralValue;
}
if (engine is null)
{
return null;
}
if (expression.IsExpression)
{
return (long)engine.Eval(expression.ExpressionText!).AsDouble();
}
else if (expression.IsVariableReference)
{
var formulaValue = engine.Eval(expression.VariableReference!.VariableName);
if (formulaValue is NumberValue numberValue)
{
return (long)numberValue.Value;
}
if (formulaValue is StringValue stringValue && int.TryParse(stringValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result))
{
return result;
}
}
return null;
}
}
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="McpServerToolApprovalMode"/>.
/// </summary>
internal static class McpServerToolApprovalModeExtensions
{
/// <summary>
/// Converts a <see cref="McpServerToolApprovalMode"/> to a <see cref="HostedMcpServerToolApprovalMode"/>.
/// </summary>
/// <param name="mode">Instance of <see cref="McpServerToolApprovalMode"/></param>
internal static HostedMcpServerToolApprovalMode AsHostedMcpServerToolApprovalMode(this McpServerToolApprovalMode mode)
{
return mode switch
{
McpServerToolNeverRequireApprovalMode => HostedMcpServerToolApprovalMode.NeverRequire,
McpServerToolAlwaysRequireApprovalMode => HostedMcpServerToolApprovalMode.AlwaysRequire,
McpServerToolRequireSpecificApprovalMode specificMode =>
HostedMcpServerToolApprovalMode.RequireSpecific(
specificMode?.AlwaysRequireApprovalToolNames?.LiteralValue ?? [],
specificMode?.NeverRequireApprovalToolNames?.LiteralValue ?? []
),
_ => HostedMcpServerToolApprovalMode.AlwaysRequire,
};
}
}
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="McpServerTool"/>.
/// </summary>
internal static class McpServerToolExtensions
{
/// <summary>
/// Creates a <see cref="HostedMcpServerTool"/> from a <see cref="McpServerTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="McpServerTool"/></param>
internal static HostedMcpServerTool CreateHostedMcpTool(this McpServerTool tool)
{
Throw.IfNull(tool);
Throw.IfNull(tool.ServerName?.LiteralValue);
Throw.IfNull(tool.Connection);
var connection = tool.Connection as AnonymousConnection ?? throw new ArgumentException("Only AnonymousConnection is supported for MCP Server Tool connections.", nameof(tool));
var serverUrl = connection.Endpoint?.LiteralValue;
Throw.IfNullOrEmpty(serverUrl, nameof(connection.Endpoint));
return new HostedMcpServerTool(tool.ServerName.LiteralValue, serverUrl)
{
ServerDescription = tool.ServerDescription?.LiteralValue,
AllowedTools = tool.AllowedTools?.LiteralValue,
ApprovalMode = tool.ApprovalMode?.AsHostedMcpServerToolApprovalMode(),
};
}
}
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="ModelOptions"/>.
/// </summary>
internal static class ModelOptionsExtensions
{
/// <summary>
/// Converts the 'chatToolMode' property from a <see cref="ModelOptions"/> to a <see cref="ChatToolMode"/>.
/// </summary>
/// <param name="modelOptions">Instance of <see cref="ModelOptions"/></param>
internal static ChatToolMode? AsChatToolMode(this ModelOptions modelOptions)
{
Throw.IfNull(modelOptions);
var mode = modelOptions.ExtensionData?.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("chatToolMode"))?.Value;
if (mode is null)
{
return null;
}
return mode switch
{
"auto" => ChatToolMode.Auto,
"none" => ChatToolMode.None,
"require_any" => ChatToolMode.RequireAny,
_ => ChatToolMode.RequireSpecific(mode),
};
}
/// <summary>
/// Retrieves the 'additional_properties' property from a <see cref="ModelOptions"/>.
/// </summary>
/// <param name="modelOptions">Instance of <see cref="ModelOptions"/></param>
/// <param name="excludedProperties">List of properties which should not be included in additional properties.</param>
internal static AdditionalPropertiesDictionary? GetAdditionalProperties(this ModelOptions modelOptions, string[] excludedProperties)
{
Throw.IfNull(modelOptions);
var options = modelOptions.ExtensionData;
if (options is null || options.Properties.Count == 0)
{
return null;
}
var additionalProperties = options.Properties
.Where(kvp => !excludedProperties.Contains(kvp.Key))
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToObject());
if (additionalProperties is null || additionalProperties.Count == 0)
{
return null;
}
return new AdditionalPropertiesDictionary(additionalProperties);
}
}
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Globalization;
using Microsoft.PowerFx;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="NumberExpression"/>.
/// </summary>
internal static class NumberExpressionExtensions
{
/// <summary>
/// Evaluates the given <see cref="NumberExpression"/> using the provided <see cref="RecalcEngine"/>.
/// </summary>
/// <param name="expression">Expression to evaluate.</param>
/// <param name="engine">Recalc engine to use for evaluation.</param>
/// <returns>The evaluated number value, or null if the expression is null or cannot be evaluated.</returns>
internal static double? Eval(this NumberExpression? expression, RecalcEngine? engine)
{
if (expression is null)
{
return null;
}
if (expression.IsLiteral)
{
return expression.LiteralValue;
}
if (engine is null)
{
return null;
}
if (expression.IsExpression)
{
return engine.Eval(expression.ExpressionText!).AsDouble();
}
else if (expression.IsVariableReference)
{
var formulaValue = engine.Eval(expression.VariableReference!.VariableName);
if (formulaValue is NumberValue numberValue)
{
return numberValue.Value;
}
if (formulaValue is StringValue stringValue && double.TryParse(stringValue.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double result))
{
return result;
}
}
return null;
}
}
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.PowerFx;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="GptComponentMetadata"/>.
/// </summary>
public static class PromptAgentExtensions
{
/// <summary>
/// Retrieves the 'options' property from a <see cref="GptComponentMetadata"/> as a <see cref="ChatOptions"/> instance.
/// </summary>
/// <param name="promptAgent">Instance of <see cref="GptComponentMetadata"/></param>
/// <param name="engine">Instance of <see cref="RecalcEngine"/></param>
/// <param name="functions">Instance of <see cref="IList{AIFunction}"/></param>
public static ChatOptions? GetChatOptions(this GptComponentMetadata promptAgent, RecalcEngine? engine, IList<AIFunction>? functions)
{
Throw.IfNull(promptAgent);
var outputSchema = promptAgent.OutputType;
var modelOptions = promptAgent.Model?.Options;
var tools = promptAgent.GetAITools(functions);
if (modelOptions is null && tools is null)
{
return null;
}
return new ChatOptions()
{
Instructions = promptAgent.Instructions?.ToTemplateString(),
Temperature = (float?)modelOptions?.Temperature?.Eval(engine),
MaxOutputTokens = (int?)modelOptions?.MaxOutputTokens?.Eval(engine),
TopP = (float?)modelOptions?.TopP?.Eval(engine),
TopK = (int?)modelOptions?.TopK?.Eval(engine),
FrequencyPenalty = (float?)modelOptions?.FrequencyPenalty?.Eval(engine),
PresencePenalty = (float?)modelOptions?.PresencePenalty?.Eval(engine),
Seed = modelOptions?.Seed?.Eval(engine),
ResponseFormat = outputSchema?.AsChatResponseFormat(),
ModelId = promptAgent.Model?.ModelNameHint,
StopSequences = modelOptions?.StopSequences,
AllowMultipleToolCalls = modelOptions?.AllowMultipleToolCalls?.Eval(engine),
ToolMode = modelOptions?.AsChatToolMode(),
Tools = tools,
AdditionalProperties = modelOptions?.GetAdditionalProperties(s_chatOptionProperties),
};
}
/// <summary>
/// Retrieves the 'tools' property from a <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Instance of <see cref="GptComponentMetadata"/></param>
/// <param name="functions">Instance of <see cref="IList{AIFunction}"/></param>
internal static List<AITool>? GetAITools(this GptComponentMetadata promptAgent, IList<AIFunction>? functions)
{
return promptAgent.Tools.Select(tool =>
{
return tool switch
{
CodeInterpreterTool => ((CodeInterpreterTool)tool).AsCodeInterpreterTool(),
InvokeClientTaskAction => ((InvokeClientTaskAction)tool).CreateOrGetAITool(functions),
McpServerTool => ((McpServerTool)tool).CreateHostedMcpTool(),
FileSearchTool => ((FileSearchTool)tool).CreateFileSearchTool(),
WebSearchTool => ((WebSearchTool)tool).CreateWebSearchTool(),
_ => throw new NotSupportedException($"Unable to create tool definition because of unsupported tool type: {tool.Kind}, supported tool types are: {string.Join(",", s_validToolKinds)}"),
};
}).ToList() ?? [];
}
#region private
private const string CodeInterpreterKind = "codeInterpreter";
private const string FileSearchKind = "fileSearch";
private const string FunctionKind = "function";
private const string WebSearchKind = "webSearch";
private const string McpKind = "mcp";
private static readonly string[] s_validToolKinds =
[
CodeInterpreterKind,
FileSearchKind,
FunctionKind,
WebSearchKind,
McpKind
];
private static readonly string[] s_chatOptionProperties =
[
"allowMultipleToolCalls",
"conversationId",
"chatToolMode",
"frequencyPenalty",
"additionalInstructions",
"maxOutputTokens",
"modelId",
"presencePenalty",
"responseFormat",
"seed",
"stopSequences",
"temperature",
"topK",
"topP",
"toolMode",
"tools",
];
#endregion
}
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Text.Json;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="PropertyInfo"/>.
/// </summary>
public static class PropertyInfoExtensions
{
/// <summary>
/// Creates a <see cref="Dictionary{TKey, TValue}"/> of <see cref="string"/> and <see cref="object"/>
/// from an <see cref="IReadOnlyDictionary{TKey, TValue}"/> of <see cref="string"/> and <see cref="PropertyInfo"/>.
/// </summary>
/// <param name="properties">A read-only dictionary of property names and their corresponding <see cref="PropertyInfo"/> objects.</param>
public static Dictionary<string, object> AsObjectDictionary(this IReadOnlyDictionary<string, PropertyInfo> properties)
{
var result = new Dictionary<string, object>();
foreach (var property in properties)
{
result[property.Key] = BuildPropertySchema(property.Value);
}
return result;
}
#region private
private static Dictionary<string, object> BuildPropertySchema(PropertyInfo propertyInfo)
{
var propertySchema = new Dictionary<string, object>();
// Map the DataType to JSON schema type and add type-specific properties
switch (propertyInfo.Type)
{
case StringDataType:
propertySchema["type"] = "string";
break;
case NumberDataType:
propertySchema["type"] = "number";
break;
case BooleanDataType:
propertySchema["type"] = "boolean";
break;
case DateTimeDataType:
propertySchema["type"] = "string";
propertySchema["format"] = "date-time";
break;
case DateDataType:
propertySchema["type"] = "string";
propertySchema["format"] = "date";
break;
case TimeDataType:
propertySchema["type"] = "string";
propertySchema["format"] = "time";
break;
case RecordDataType nestedRecordType:
#pragma warning disable IL2026, IL3050
// For nested records, recursively build the schema
var nestedSchema = nestedRecordType.GetSchema();
var nestedJson = JsonSerializer.Serialize(nestedSchema, ElementSerializer.CreateOptions());
var nestedDict = JsonSerializer.Deserialize<Dictionary<string, object>>(nestedJson, ElementSerializer.CreateOptions());
#pragma warning restore IL2026, IL3050
if (nestedDict != null)
{
return nestedDict;
}
propertySchema["type"] = "object";
break;
case TableDataType tableType:
propertySchema["type"] = "array";
// TableDataType has Properties like RecordDataType
propertySchema["items"] = new Dictionary<string, object>
{
["type"] = "object",
["properties"] = AsObjectDictionary(tableType.Properties),
["additionalProperties"] = false
};
break;
default:
propertySchema["type"] = "string";
break;
}
// Add description if available
if (!string.IsNullOrEmpty(propertyInfo.Description))
{
propertySchema["description"] = propertyInfo.Description;
}
return propertySchema;
}
#endregion
}
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="RecordDataType"/>.
/// </summary>
public static class RecordDataTypeExtensions
{
/// <summary>
/// Creates a <see cref="ChatResponseFormat"/> from a <see cref="RecordDataType"/>.
/// </summary>
/// <param name="recordDataType">Instance of <see cref="RecordDataType"/></param>
internal static ChatResponseFormat? AsChatResponseFormat(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
if (recordDataType.Properties.Count == 0)
{
return null;
}
// TODO: Consider adding schemaName and schemaDescription parameters to this method.
return ChatResponseFormat.ForJsonSchema(
schema: recordDataType.GetSchema(),
schemaName: recordDataType.GetSchemaName(),
schemaDescription: recordDataType.GetSchemaDescription());
}
/// <summary>
/// Converts a <see cref="RecordDataType"/> to a <see cref="JsonElement"/>.
/// </summary>
/// <param name="recordDataType">Instance of <see cref="RecordDataType"/></param>
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
public static JsonElement GetSchema(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
var schemaObject = new Dictionary<string, object>
{
["type"] = "object",
["properties"] = recordDataType.Properties.AsObjectDictionary(),
["additionalProperties"] = false
};
var json = JsonSerializer.Serialize(schemaObject, ElementSerializer.CreateOptions());
return JsonSerializer.Deserialize<JsonElement>(json);
}
#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
/// <summary>
/// Retrieves the 'schemaName' property from a <see cref="RecordDataType"/>.
/// </summary>
private static string? GetSchemaName(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
return recordDataType.ExtensionData?.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("schemaName"))?.Value;
}
/// <summary>
/// Retrieves the 'schemaDescription' property from a <see cref="RecordDataType"/>.
/// </summary>
private static string? GetSchemaDescription(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
return recordDataType.ExtensionData?.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("schemaDescription"))?.Value;
}
}
@@ -0,0 +1,107 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="RecordDataValue"/>.
/// </summary>
public static class RecordDataValueExtensions
{
/// <summary>
/// Retrieves a 'number' property from a <see cref="RecordDataValue"/>
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
/// <param name="propertyPath">Path of the property to retrieve</param>
public static decimal? GetNumber(this RecordDataValue recordData, string propertyPath)
{
Throw.IfNull(recordData);
var numberValue = recordData.GetPropertyOrNull<NumberDataValue>(InitializablePropertyPath.Create(propertyPath));
return numberValue?.Value;
}
/// <summary>
/// Retrieves a nullable boolean value from the specified property path within the given record data.
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
/// <param name="propertyPath">Path of the property to retrieve</param>
public static bool? GetBoolean(this RecordDataValue recordData, string propertyPath)
{
Throw.IfNull(recordData);
var booleanValue = recordData.GetPropertyOrNull<BooleanDataValue>(InitializablePropertyPath.Create(propertyPath));
return booleanValue?.Value;
}
/// <summary>
/// Converts a <see cref="RecordDataValue"/> to a <see cref="IReadOnlyDictionary{TKey, TValue}"/>.
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
public static IReadOnlyDictionary<string, string> ToDictionary(this RecordDataValue recordData)
{
Throw.IfNull(recordData);
return recordData.Properties.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty
);
}
/// <summary>
/// Retrieves the 'schema' property from a <see cref="RecordDataValue"/>.
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
public static JsonElement? GetSchema(this RecordDataValue recordData)
{
Throw.IfNull(recordData);
try
{
var schemaStr = recordData.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("json_schema.schema"));
if (schemaStr?.Value is not null)
{
return JsonSerializer.Deserialize<JsonElement>(schemaStr.Value);
}
}
catch (InvalidCastException)
{
// Ignore and try next
}
var responseFormRec = recordData.GetPropertyOrNull<RecordDataValue>(InitializablePropertyPath.Create("json_schema.schema"));
if (responseFormRec is not null)
{
var json = JsonSerializer.Serialize(responseFormRec, ElementSerializer.CreateOptions());
return JsonSerializer.Deserialize<JsonElement>(json);
}
return null;
}
#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
internal static object? ToObject(this DataValue? value)
{
if (value is null)
{
return null;
}
return value switch
{
StringDataValue s => s.Value,
NumberDataValue n => n.Value,
BooleanDataValue b => b.Value,
TableDataValue t => t.Values.Select(v => v.ToObject()).ToList(),
RecordDataValue r => r.Properties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToObject()),
_ => throw new NotSupportedException($"Unsupported DataValue type: {value.GetType().FullName}"),
};
}
}
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.PowerFx;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="StringExpression"/>.
/// </summary>
public static class StringExpressionExtensions
{
/// <summary>
/// Evaluates the given <see cref="StringExpression"/> using the provided <see cref="RecalcEngine"/>.
/// </summary>
/// <param name="expression">Expression to evaluate.</param>
/// <param name="engine">Recalc engine to use for evaluation.</param>
/// <returns>The evaluated string value, or null if the expression is null or cannot be evaluated.</returns>
public static string? Eval(this StringExpression? expression, RecalcEngine? engine)
{
if (expression is null)
{
return null;
}
if (expression.IsLiteral)
{
return expression.LiteralValue?.ToString();
}
if (engine is null)
{
return null;
}
if (expression.IsExpression)
{
return engine.Eval(expression.ExpressionText!).ToString();
}
else if (expression.IsVariableReference)
{
var stringValue = engine.Eval(expression.VariableReference!.VariableName) as StringValue;
return stringValue?.Value;
}
return null;
}
}
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.ObjectModel;
/// <summary>
/// Extension methods for <see cref="WebSearchTool"/>.
/// </summary>
internal static class WebSearchToolExtensions
{
/// <summary>
/// Create a <see cref="HostedWebSearchTool"/> from a <see cref="WebSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="WebSearchTool"/></param>
internal static HostedWebSearchTool CreateWebSearchTool(this WebSearchTool tool)
{
Throw.IfNull(tool);
return new HostedWebSearchTool();
}
}
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
/// <summary>
/// Extension methods for <see cref="PromptAgentFactory"/> to support YAML based agent definitions.
/// </summary>
public static class YamlAgentFactoryExtensions
{
/// <summary>
/// Create a <see cref="AIAgent"/> from the given agent YAML.
/// </summary>
/// <param name="agentFactory"><see cref="PromptAgentFactory"/> which will be used to create the agent.</param>
/// <param name="agentYaml">Text string containing the YAML representation of an <see cref="AIAgent" />.</param>
/// <param name="cancellationToken">Optional cancellation token</param>
[RequiresDynamicCode("Calls YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()")]
public static Task<AIAgent> CreateFromYamlAsync(this PromptAgentFactory agentFactory, string agentYaml, CancellationToken cancellationToken = default)
{
Throw.IfNull(agentFactory);
Throw.IfNullOrEmpty(agentYaml);
var agentDefinition = AgentBotElementYaml.FromYaml(agentYaml);
return agentFactory.CreateAsync(
agentDefinition,
cancellationToken);
}
}
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsReleaseCandidate>true</IsReleaseCandidate>
<NoWarn>$(NoWarn);MEAI001</NoWarn>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<InjectSharedThrow>true</InjectSharedThrow>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<PropertyGroup>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework Declarative Agents</Title>
<Description>Provides Microsoft Agent Framework support for declarative agents.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.ObjectModel" />
<PackageReference Include="Microsoft.Agents.ObjectModel.Json" />
<PackageReference Include="Microsoft.Agents.ObjectModel.PowerFx" />
<PackageReference Include="Microsoft.PowerFx.Interpreter" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Logging" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.Declarative.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Agents.AI.Declarative.IntegrationTests" />
</ItemGroup>
</Project>
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.ObjectModel;
using Microsoft.Extensions.Configuration;
using Microsoft.PowerFx;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
/// <summary>
/// Represents a factory for creating <see cref="AIAgent"/> instances.
/// </summary>
public abstract class PromptAgentFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="PromptAgentFactory"/> class.
/// </summary>
/// <param name="engine">Optional <see cref="RecalcEngine"/>, if none is provided a default instance will be created.</param>
/// <param name="configuration">Optional configuration to be added as variables to the <see cref="RecalcEngine"/>.</param>
protected PromptAgentFactory(RecalcEngine? engine = null, IConfiguration? configuration = null)
{
this.Engine = engine ?? new RecalcEngine();
if (configuration is not null)
{
foreach (var kvp in configuration.AsEnumerable())
{
this.Engine.UpdateVariable(kvp.Key, kvp.Value ?? string.Empty);
}
}
}
/// <summary>
/// Gets the Power Fx recalculation engine used to evaluate expressions in agent definitions.
/// This engine is configured with variables from the <see cref="IConfiguration"/> provided during construction.
/// </summary>
protected RecalcEngine Engine { get; }
/// <summary>
/// Create a <see cref="AIAgent"/> from the specified <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Definition of the agent to create.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <return>The created <see cref="AIAgent"/>, if null the agent type is not supported.</return>
public async Task<AIAgent> CreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
{
Throw.IfNull(promptAgent);
var agent = await this.TryCreateAsync(promptAgent, cancellationToken).ConfigureAwait(false);
return agent ?? throw new NotSupportedException($"Agent type {promptAgent.Kind} is not supported.");
}
/// <summary>
/// Tries to create a <see cref="AIAgent"/> from the specified <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Definition of the agent to create.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <return>The created <see cref="AIAgent"/>, if null the agent type is not supported.</return>
public abstract Task<AIAgent?> TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default);
}