diff --git a/agent-samples/README.md b/agent-samples/README.md
new file mode 100644
index 0000000000..91e45605db
--- /dev/null
+++ b/agent-samples/README.md
@@ -0,0 +1,3 @@
+# Declarative Agents
+
+This folder contains sample agent definitions than be ran using the [Declarative Agents](../dotnet/samples/GettingStarted/DeclarativeAgents) demo.
diff --git a/agent-samples/azure/AzureOpenAIAssistants.yaml b/agent-samples/azure/AzureOpenAIAssistants.yaml
new file mode 100644
index 0000000000..cf934ded5f
--- /dev/null
+++ b/agent-samples/azure/AzureOpenAIAssistants.yaml
@@ -0,0 +1,25 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
+model:
+ id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
+ provider: AzureOpenAI
+ apiType: Assistants
+ options:
+ temperature: 0.9
+ topP: 0.95
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/agent-samples/azure/AzureOpenAIChat.yaml b/agent-samples/azure/AzureOpenAIChat.yaml
new file mode 100644
index 0000000000..8272b908ad
--- /dev/null
+++ b/agent-samples/azure/AzureOpenAIChat.yaml
@@ -0,0 +1,25 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Chat as the type in your response.
+model:
+ id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
+ provider: AzureOpenAI
+ apiType: Chat
+ options:
+ temperature: 0.9
+ topP: 0.95
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/agent-samples/azure/AzureOpenAIResponses.yaml b/agent-samples/azure/AzureOpenAIResponses.yaml
new file mode 100644
index 0000000000..f29a7b7bbb
--- /dev/null
+++ b/agent-samples/azure/AzureOpenAIResponses.yaml
@@ -0,0 +1,25 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
+model:
+ id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
+ provider: AzureOpenAI
+ apiType: Responses
+ options:
+ temperature: 0.9
+ topP: 0.95
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/agent-samples/chatclient/Assistant.yaml b/agent-samples/chatclient/Assistant.yaml
new file mode 100644
index 0000000000..3332d54540
--- /dev/null
+++ b/agent-samples/chatclient/Assistant.yaml
@@ -0,0 +1,18 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
+model:
+ options:
+ temperature: 0.9
+ topP: 0.95
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
diff --git a/agent-samples/chatclient/GetWeather.yaml b/agent-samples/chatclient/GetWeather.yaml
new file mode 100644
index 0000000000..798d2e4245
--- /dev/null
+++ b/agent-samples/chatclient/GetWeather.yaml
@@ -0,0 +1,26 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions using the tools provided.
+model:
+ options:
+ temperature: 0.9
+ topP: 0.95
+ allowMultipleToolCalls: true
+ chatToolMode: auto
+tools:
+ - kind: function
+ name: GetWeather
+ description: Get the weather for a given location.
+ parameters:
+ - name: location
+ type: string
+ description: The city and state, e.g. San Francisco, CA
+ required: true
+ - name: unit
+ type: string
+ description: The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.
+ required: false
+ enum:
+ - celsius
+ - fahrenheit
diff --git a/agent-samples/foundry/PersistentAgent.yaml b/agent-samples/foundry/PersistentAgent.yaml
new file mode 100644
index 0000000000..5ff4514dd8
--- /dev/null
+++ b/agent-samples/foundry/PersistentAgent.yaml
@@ -0,0 +1,22 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
+model:
+ id: =Env.AZURE_FOUNDRY_PROJECT_MODEL_ID
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: Remote
+ endpoint: =Env.AZURE_FOUNDRY_PROJECT_ENDPOINT
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
diff --git a/agent-samples/openai/OpenAIAssistants.yaml b/agent-samples/openai/OpenAIAssistants.yaml
new file mode 100644
index 0000000000..867639aa22
--- /dev/null
+++ b/agent-samples/openai/OpenAIAssistants.yaml
@@ -0,0 +1,28 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
+model:
+ id: =Env.OPENAI_MODEL
+ provider: OpenAI
+ apiType: Assistants
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: ApiKey
+ key: =Env.OPENAI_APIKEY
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/agent-samples/openai/OpenAIChat.yaml b/agent-samples/openai/OpenAIChat.yaml
new file mode 100644
index 0000000000..135bf8602b
--- /dev/null
+++ b/agent-samples/openai/OpenAIChat.yaml
@@ -0,0 +1,28 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Chat as the type in your response.
+model:
+ id: =Env.OPENAI_MODEL
+ provider: OpenAI
+ apiType: Chat
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: ApiKey
+ key: =Env.OPENAI_APIKEY
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/agent-samples/openai/OpenAIResponses.yaml b/agent-samples/openai/OpenAIResponses.yaml
new file mode 100644
index 0000000000..78f331eea2
--- /dev/null
+++ b/agent-samples/openai/OpenAIResponses.yaml
@@ -0,0 +1,28 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
+model:
+ id: =Env.OPENAI_MODEL
+ provider: OpenAI
+ apiType: Responses
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: ApiKey
+ key: =Env.OPENAI_APIKEY
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props
index 6b61196bbd..afb2250939 100644
--- a/dotnet/Directory.Build.props
+++ b/dotnet/Directory.Build.props
@@ -8,7 +8,7 @@
true
13
enable
- $(NoWarn);NU5128
+ $(NoWarn);NU5128;NU1900;NU1603
true
net9.0;net8.0
net9.0
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 004fc10f75..5d34ebc2ad 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -64,6 +64,7 @@
+
@@ -80,6 +81,12 @@
+
+
+
+
+
+
@@ -296,6 +303,8 @@
+
+
@@ -313,6 +322,7 @@
+
@@ -323,6 +333,7 @@
+
diff --git a/dotnet/nuget.config b/dotnet/nuget.config
index f7e74aa056..af03543b8f 100644
--- a/dotnet/nuget.config
+++ b/dotnet/nuget.config
@@ -13,4 +13,4 @@
-
\ No newline at end of file
+
diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props
index 65dbd78a29..099bb42dfb 100644
--- a/dotnet/nuget/nuget-package.props
+++ b/dotnet/nuget/nuget-package.props
@@ -12,7 +12,7 @@
0.0.1
- $(NoWarn);CP0003
+ $(NoWarn);CP0003;NU1900;NU1603
$(NoWarn);CP1002
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step18_Declarative/Agent_Step18_Declarative.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step18_Declarative/Agent_Step18_Declarative.csproj
new file mode 100644
index 0000000000..0bd9574dff
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step18_Declarative/Agent_Step18_Declarative.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net9.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step18_Declarative/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step18_Declarative/Program.cs
new file mode 100644
index 0000000000..6275b63b62
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step18_Declarative/Program.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to create an agent from a YAML based declarative representation.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+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";
+
+// Create the chat client
+IChatClient chatClient = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .AsIChatClient();
+
+// Define the agent using a YAML definition.
+var text =
+ """
+ kind: Prompt
+ name: Assistant
+ description: Helpful assistant
+ instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
+ model:
+ options:
+ temperature: 0.9
+ topP: 0.95
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+// Create the agent from the YAML definition.
+var agentFactory = new ChatClientAgentFactory(chatClient);
+var agent = await agentFactory.CreateFromYamlAsync(text);
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent!.RunAsync("Tell me a joke about a pirate in English."));
+
+// Invoke the agent with streaming support.
+await foreach (var update in agent!.RunStreamingAsync("Tell me a joke about a pirate in French."))
+{
+ Console.WriteLine(update);
+}
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/Azure/DeclarativeAzureAgents.csproj b/dotnet/samples/GettingStarted/DeclarativeAgents/Azure/DeclarativeAzureAgents.csproj
new file mode 100644
index 0000000000..e607b92fb7
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/Azure/DeclarativeAzureAgents.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net9.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/Azure/Program.cs b/dotnet/samples/GettingStarted/DeclarativeAgents/Azure/Program.cs
new file mode 100644
index 0000000000..213cbf7815
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/Azure/Program.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to load an AI agent from a YAML file and process a prompt using Azure OpenAI as the backend.
+
+using System.ComponentModel;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+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";
+
+// Read command-line arguments
+if (args.Length < 2)
+{
+ Console.WriteLine("Usage: DeclarativeAgents ");
+ Console.WriteLine(" : The path to the YAML file containing the agent definition");
+ Console.WriteLine(" : The prompt to send to the agent");
+ return;
+}
+
+var yamlFilePath = args[0];
+var prompt = args[1];
+
+// Verify the YAML file exists
+if (!File.Exists(yamlFilePath))
+{
+ Console.WriteLine($"Error: File not found: {yamlFilePath}");
+ return;
+}
+
+// Read the YAML content from the file
+var text = await File.ReadAllTextAsync(yamlFilePath);
+
+// TODO: Remove this workaround when the agent framework supports environment variable substitution in YAML files.
+text = text.Replace("=Env.AZURE_OPENAI_DEPLOYMENT_NAME", deploymentName, StringComparison.OrdinalIgnoreCase);
+
+var endpointUri = new Uri(endpoint);
+var tokenCredential = new AzureCliCredential();
+
+// Create the agent from the YAML definition.
+var agentFactory = new AggregatorAgentFactory(
+ [
+ new OpenAIChatAgentFactory(endpointUri, tokenCredential),
+ new OpenAIResponseAgentFactory(endpointUri, tokenCredential),
+ new OpenAIAssistantAgentFactory(endpointUri, tokenCredential)
+ ]);
+var agent = await agentFactory.CreateFromYamlAsync(text);
+
+// Example function tool that can be used by the agent.
+[Description("Get the weather for a given location.")]
+static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+// Create agent run options
+var options = new ChatClientAgentRunOptions(new()
+{
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+});
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent!.RunAsync(prompt, options: options));
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj
new file mode 100644
index 0000000000..6442fa3a7b
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net9.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs
new file mode 100644
index 0000000000..c3f998a836
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to load an AI agent from a YAML file and process a prompt using Azure OpenAI as the backend.
+
+using System.ComponentModel;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+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";
+
+// Create the chat client
+IChatClient chatClient = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .AsIChatClient();
+
+// Read command-line arguments
+if (args.Length < 2)
+{
+ Console.WriteLine("Usage: DeclarativeAgents ");
+ Console.WriteLine(" : The path to the YAML file containing the agent definition");
+ Console.WriteLine(" : The prompt to send to the agent");
+ return;
+}
+
+var yamlFilePath = args[0];
+var prompt = args[1];
+
+// Verify the YAML file exists
+if (!File.Exists(yamlFilePath))
+{
+ Console.WriteLine($"Error: File not found: {yamlFilePath}");
+ return;
+}
+
+// Read the YAML content from the file
+var text = await File.ReadAllTextAsync(yamlFilePath);
+
+// Example function tool that can be used by the agent.
+[Description("Get the weather for a given location.")]
+static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+// Create the agent from the YAML definition.
+var agentFactory = new ChatClientAgentFactory(chatClient, [AIFunctionFactory.Create(GetWeather, "GetWeather")]);
+var agent = await agentFactory.CreateFromYamlAsync(text);
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent!.RunAsync(prompt));
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/Foundry/DeclarativeFoundryAgents.csproj b/dotnet/samples/GettingStarted/DeclarativeAgents/Foundry/DeclarativeFoundryAgents.csproj
new file mode 100644
index 0000000000..e607b92fb7
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/Foundry/DeclarativeFoundryAgents.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net9.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/Foundry/Program.cs b/dotnet/samples/GettingStarted/DeclarativeAgents/Foundry/Program.cs
new file mode 100644
index 0000000000..68a78fe01e
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/Foundry/Program.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to load an AI agent from a YAML file and process a prompt using Foundry Agents as the backend.
+
+using System.ComponentModel;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
+var model = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_MODEL_ID") ?? "gpt-4.1-mini";
+
+// Read command-line arguments
+if (args.Length < 2)
+{
+ Console.WriteLine("Usage: DeclarativeAgents ");
+ Console.WriteLine(" : The path to the YAML file containing the agent definition");
+ Console.WriteLine(" : The prompt to send to the agent");
+ return;
+}
+
+var yamlFilePath = args[0];
+var prompt = args[1];
+
+// Verify the YAML file exists
+if (!File.Exists(yamlFilePath))
+{
+ Console.WriteLine($"Error: File not found: {yamlFilePath}");
+ return;
+}
+
+// Read the YAML content from the file
+var text = await File.ReadAllTextAsync(yamlFilePath);
+
+// TODO: Remove this workaround when the agent framework supports environment variable substitution in YAML files.
+text = text.Replace("=Env.AZURE_FOUNDRY_PROJECT_ENDPOINT", endpoint, StringComparison.OrdinalIgnoreCase);
+text = text.Replace("=Env.AZURE_FOUNDRY_PROJECT_MODEL_ID", model, StringComparison.OrdinalIgnoreCase);
+
+// Example function tool that can be used by the agent.
+[Description("Get the weather for a given location.")]
+static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+// Create the agent from the YAML definition.
+var agentFactory = new FoundryPersistentAgentFactory(new AzureCliCredential());
+var agent = await agentFactory.CreateFromYamlAsync(text);
+
+// Create agent run options
+var options = new ChatClientAgentRunOptions(new()
+{
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+});
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent!.RunAsync(prompt, options: options));
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj
new file mode 100644
index 0000000000..e607b92fb7
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net9.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Program.cs b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Program.cs
new file mode 100644
index 0000000000..5aca8ef7cf
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Program.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to load an AI agent from a YAML file and process a prompt using OpenAI as the backend.
+
+using System.ComponentModel;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var apiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY") ?? throw new InvalidOperationException("OPENAI_APIKEY is not set.");
+var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
+
+// Read command-line arguments
+if (args.Length < 2)
+{
+ Console.WriteLine("Usage: DeclarativeAgents ");
+ Console.WriteLine(" : The path to the YAML file containing the agent definition");
+ Console.WriteLine(" : The prompt to send to the agent");
+ return;
+}
+
+var yamlFilePath = args[0];
+var prompt = args[1];
+
+// Verify the YAML file exists
+if (!File.Exists(yamlFilePath))
+{
+ Console.WriteLine($"Error: File not found: {yamlFilePath}");
+ return;
+}
+
+// Read the YAML content from the file
+var text = await File.ReadAllTextAsync(yamlFilePath);
+
+// TODO: Remove this workaround when the agent framework supports environment variable substitution in YAML files.
+text = text.Replace("=Env.OPENAI_APIKEY", apiKey, StringComparison.OrdinalIgnoreCase);
+text = text.Replace("=Env.OPENAI_MODEL", model, StringComparison.OrdinalIgnoreCase);
+
+// Create the agent from the YAML definition.
+var agentFactory = new AggregatorAgentFactory(
+ [
+ new OpenAIChatAgentFactory(),
+ new OpenAIResponseAgentFactory(),
+ new OpenAIAssistantAgentFactory()
+ ]);
+var agent = await agentFactory.CreateFromYamlAsync(text);
+
+// Example function tool that can be used by the agent.
+[Description("Get the weather for a given location.")]
+static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+// Create agent run options
+var options = new ChatClientAgentRunOptions(new()
+{
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+});
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent!.RunAsync(prompt, options: options));
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/CodeInterpreterToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/CodeInterpreterToolExtensions.cs
new file mode 100644
index 0000000000..45308f6bae
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/CodeInterpreterToolExtensions.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class CodeInterpreterToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static CodeInterpreterToolDefinition CreateCodeInterpreterToolDefinition(this CodeInterpreterTool tool)
+ {
+ Throw.IfNull(tool);
+
+ return new CodeInterpreterToolDefinition();
+ }
+
+ ///
+ /// Collects the file IDs from the extension data of a .
+ ///
+ /// Instance of
+ internal static List? GetFileIds(this CodeInterpreterTool tool)
+ {
+ var fileIds = tool.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("fileIds"));
+ return fileIds is not null
+ ? [.. fileIds.Values.Select(fileId => fileId.GetPropertyOrNull(InitializablePropertyPath.Create("value"))?.Value)]
+ : null;
+ }
+
+ ///
+ /// Collects the data sources from the extension data of a .
+ ///
+ /// Instance of
+ internal static List? GetDataSources(this CodeInterpreterTool tool)
+ {
+ var dataSources = tool.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("dataSources"));
+ return dataSources is not null
+ ? dataSources.Values.Select(dataSource => dataSource.CreateDataSource()).ToList()
+ : null;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/FileSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/FileSearchToolExtensions.cs
new file mode 100644
index 0000000000..52d1e69c04
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/FileSearchToolExtensions.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class FileSearchToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static FileSearchToolDefinition CreateFileSearchToolDefinition(this FileSearchTool tool)
+ {
+ Throw.IfNull(tool);
+
+ // TODO: Add support for FileSearchToolDefinitionDetails.
+
+ return new FileSearchToolDefinition();
+ }
+
+ ///
+ /// Get the vector store IDs for the specified .
+ ///
+ /// Instance of
+ internal static List? GetVectorStoreIds(this FileSearchTool tool)
+ {
+ return tool.VectorStoreIds?.LiteralValue.ToList();
+ }
+
+ internal static IList? GetVectorStoreConfigurations(this FileSearchTool tool)
+ {
+ var dataSources = tool.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("options.configurations"));
+ return dataSources?.Values.Select(value => value.CreateVectorStoreConfiguration()).ToList();
+ }
+
+ internal static VectorStoreConfigurations CreateVectorStoreConfiguration(this RecordDataValue value)
+ {
+ Throw.IfNull(value);
+
+ var storeName = value.GetPropertyOrNull(InitializablePropertyPath.Create("storeName"))?.Value;
+ Throw.IfNullOrEmpty(storeName);
+
+ var dataSources = value.GetDataSources();
+ Throw.IfNull(dataSources);
+
+ return new VectorStoreConfigurations(storeName, new VectorStoreConfiguration(dataSources));
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/FunctionToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/FunctionToolExtensions.cs
new file mode 100644
index 0000000000..97281b79b7
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/FunctionToolExtensions.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class FunctionToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static FunctionToolDefinition CreateFunctionToolDefinition(this InvokeClientTaskAction tool)
+ {
+ Throw.IfNull(tool);
+ Throw.IfNull(tool.Name);
+
+ BinaryData parameters = tool.GetParameters();
+
+ return new FunctionToolDefinition(
+ name: tool.Name,
+ description: tool.Description,
+ parameters: parameters);
+ }
+
+ ///
+ /// Creates the parameters schema for a .
+ ///
+ /// Instance of
+ internal static BinaryData GetParameters(this InvokeClientTaskAction tool)
+ {
+ Throw.IfNull(tool);
+
+ var parameters = tool.ClientActionInputSchema?.GetSchema().ToString() ?? DefaultSchema;
+
+ return new BinaryData(parameters);
+ }
+
+ private const string DefaultSchema = "{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false}";
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedCodeInterpreterToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedCodeInterpreterToolExtensions.cs
new file mode 100644
index 0000000000..da769856dd
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedCodeInterpreterToolExtensions.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.Agents.Persistent;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Extensions.AI;
+
+///
+/// Extension methods for .
+///
+internal static class HostedCodeInterpreterToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static CodeInterpreterToolDefinition CreateHostedCodeInterpreterToolDefinition(this HostedCodeInterpreterTool tool)
+ {
+ Throw.IfNull(tool);
+
+ return new CodeInterpreterToolDefinition();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedFileSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedFileSearchToolExtensions.cs
new file mode 100644
index 0000000000..846e28f226
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedFileSearchToolExtensions.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Extensions.AI;
+
+///
+/// Extension methods for .
+///
+internal static class HostedFileSearchToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static FileSearchToolDefinition CreateFileSearchToolDefinition(this HostedFileSearchTool tool)
+ {
+ Throw.IfNull(tool);
+
+ // TODO: Add support for FileSearchToolDefinitionDetails.
+
+ return new FileSearchToolDefinition();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedMcpServerToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedMcpServerToolExtensions.cs
new file mode 100644
index 0000000000..a879a105bc
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedMcpServerToolExtensions.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Linq;
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Extensions.AI;
+
+///
+/// Extension methods for .
+///
+internal static class HostedMcpServerToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static MCPToolDefinition CreateMcpToolDefinition(this HostedMcpServerTool tool)
+ {
+ Throw.IfNull(tool);
+ Throw.IfNull(tool.ServerName);
+ Throw.IfNull(tool.ServerAddress);
+
+ var definition = new MCPToolDefinition(tool.ServerName, tool.ServerAddress);
+ tool.AllowedTools?.ToList().ForEach(definition.AllowedTools.Add);
+ return definition;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedWebSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedWebSearchToolExtensions.cs
new file mode 100644
index 0000000000..f13c0ec2d4
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/HostedWebSearchToolExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Extensions.AI;
+
+///
+/// Extension methods for .
+///
+internal static class HostedWebSearchToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static BingGroundingToolDefinition CreateBingGroundingToolDefinition(this HostedWebSearchTool tool)
+ {
+ Throw.IfNull(tool);
+
+ // TODO: Add support for BingGroundingSearchToolParameters.
+ var parameters = new BingGroundingSearchToolParameters([]);
+
+ return new BingGroundingToolDefinition(parameters);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/McpServerToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/McpServerToolExtensions.cs
new file mode 100644
index 0000000000..1e0668f846
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/McpServerToolExtensions.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class McpServerToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static MCPToolDefinition CreateMcpToolDefinition(this McpServerTool tool)
+ {
+ Throw.IfNull(tool);
+ Throw.IfNull(tool.ServerName?.LiteralValue);
+ Throw.IfNull(tool.Connection);
+
+ // TODO: Add support for additional properties
+
+ 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 MCPToolDefinition(tool.ServerName?.LiteralValue, serverUrl);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/PromptAgentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/PromptAgentExtensions.cs
new file mode 100644
index 0000000000..899fb26c5b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/PromptAgentExtensions.cs
@@ -0,0 +1,114 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class PromptAgentExtensions
+{
+ ///
+ /// Return the Foundry tool definitions which corresponds with the provided .
+ ///
+ /// Instance of
+ internal static IEnumerable GetToolDefinitions(this GptComponentMetadata promptAgent)
+ {
+ Throw.IfNull(promptAgent);
+
+ return promptAgent.Tools.Select(tool =>
+ {
+ return tool switch
+ {
+ CodeInterpreterTool => ((CodeInterpreterTool)tool).CreateCodeInterpreterToolDefinition(),
+ InvokeClientTaskAction => ((InvokeClientTaskAction)tool).CreateFunctionToolDefinition(),
+ FileSearchTool => ((FileSearchTool)tool).CreateFileSearchToolDefinition(),
+ WebSearchTool => ((WebSearchTool)tool).CreateBingGroundingToolDefinition(),
+ McpServerTool => ((McpServerTool)tool).CreateMcpToolDefinition(),
+ // TODO: Add other tool types as custom tools
+ // AzureAISearch
+ // AzureFunction
+ // OpenApi
+ _ => throw new NotSupportedException($"Unable to create tool definition because of unsupported tool type: {tool.Kind}"),
+ };
+ }).ToList();
+ }
+
+ ///
+ /// Return the Foundry tool resources which corresponds with the provided .
+ ///
+ /// Instance of
+ internal static ToolResources GetToolResources(this GptComponentMetadata promptAgent)
+ {
+ Throw.IfNull(promptAgent);
+
+ var toolResources = new ToolResources();
+
+ var codeInterpreter = promptAgent.GetCodeInterpreterToolResource();
+ if (codeInterpreter is not null)
+ {
+ toolResources.CodeInterpreter = codeInterpreter;
+ }
+
+ var fileSearch = promptAgent.GetFileSearchToolResource();
+ if (fileSearch is not null)
+ {
+ toolResources.FileSearch = fileSearch;
+ }
+
+ // TODO Handle MCP tool resources
+
+ return toolResources;
+ }
+
+ #region private
+ private static CodeInterpreterToolResource? GetCodeInterpreterToolResource(this GptComponentMetadata promptAgent)
+ {
+ Throw.IfNull(promptAgent);
+
+ CodeInterpreterToolResource? resource = null;
+
+ var codeInterpreter = (CodeInterpreterTool?)promptAgent.GetFirstAgentTool();
+ if (codeInterpreter is not null)
+ {
+ var fileIds = codeInterpreter.GetFileIds();
+ var dataSources = codeInterpreter.GetDataSources();
+ if (fileIds is not null || dataSources is not null)
+ {
+ resource = new CodeInterpreterToolResource();
+ fileIds?.ForEach(id => resource.FileIds.Add(id));
+ dataSources?.ForEach(ds => resource.DataSources.Add(ds));
+ }
+ }
+
+ return resource;
+ }
+
+ private static FileSearchToolResource? GetFileSearchToolResource(this GptComponentMetadata promptAgent)
+ {
+ Throw.IfNull(promptAgent);
+
+ var fileSearch = (FileSearchTool?)promptAgent.GetFirstAgentTool();
+ if (fileSearch is not null)
+ {
+ var vectorStoreIds = fileSearch.GetVectorStoreIds();
+ var vectorStores = fileSearch.GetVectorStoreConfigurations();
+ if (vectorStoreIds is not null || vectorStores is not null)
+ {
+ return new FileSearchToolResource(vectorStoreIds, vectorStores);
+ }
+ }
+
+ return null;
+ }
+
+ private static TaskAction? GetFirstAgentTool(this GptComponentMetadata promptAgent)
+ {
+ return promptAgent.Tools.FirstOrDefault(tool => tool is T);
+ }
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/RecordDataTypeExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/RecordDataTypeExtensions.cs
new file mode 100644
index 0000000000..24c172c888
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/RecordDataTypeExtensions.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class RecordDataTypeExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+#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.
+ internal static BinaryData? AsBinaryData(this RecordDataType recordDataType)
+ {
+ Throw.IfNull(recordDataType);
+
+ if (recordDataType.Properties.Count == 0)
+ {
+ return null;
+ }
+
+ return BinaryData.FromObjectAsJson(
+ new
+ {
+ type = "json_schema",
+ schema =
+ new
+ {
+ type = "object",
+ properties = recordDataType.Properties.AsObjectDictionary(),
+ additionalProperties = false
+ }
+ }
+ );
+ }
+#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
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/RecordDataValueExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/RecordDataValueExtensions.cs
new file mode 100644
index 0000000000..a2c4d490d6
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/RecordDataValueExtensions.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class RecordDataValueExtensions
+{
+ ///
+ /// Gets the data sources from the specified .
+ ///
+ internal static List? GetDataSources(this RecordDataValue value)
+ {
+ var dataSources = value.GetPropertyOrNull(InitializablePropertyPath.Create("options.data_sources"));
+ return dataSources?.Values.Select(dataSource => dataSource.CreateDataSource()).ToList();
+ }
+
+ ///
+ /// Creates a new instance of using the specified .
+ ///
+ internal static VectorStoreDataSource CreateDataSource(this RecordDataValue value)
+ {
+ Throw.IfNull(value);
+
+ string? assetIdentifier = value.GetPropertyOrNull(InitializablePropertyPath.Create("assetIdentifier"))?.Value;
+ Throw.IfNullOrEmpty(assetIdentifier);
+
+ string? assetType = value.GetPropertyOrNull(InitializablePropertyPath.Create("assetType"))?.Value;
+ Throw.IfNullOrEmpty(assetType);
+
+ return new VectorStoreDataSource(assetIdentifier, new VectorStoreDataSourceAssetType(assetType));
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/WebSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/WebSearchToolExtensions.cs
new file mode 100644
index 0000000000..f4643f2ca1
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Extensions/WebSearchToolExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.Agents.Persistent;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class WebSearchToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static BingGroundingToolDefinition CreateBingGroundingToolDefinition(this WebSearchTool tool)
+ {
+ Throw.IfNull(tool);
+
+ // TODO: Add support for BingGroundingSearchToolParameters.
+ var parameters = new BingGroundingSearchToolParameters([]);
+
+ return new BingGroundingToolDefinition(parameters);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/FoundryPersistentAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/FoundryPersistentAgentFactory.cs
new file mode 100644
index 0000000000..90e7953926
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/FoundryPersistentAgentFactory.cs
@@ -0,0 +1,89 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.AI.Agents.Persistent;
+using Azure.Core;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides an which creates instances of using a .
+///
+public sealed class FoundryPersistentAgentFactory : AgentFactory
+{
+ private readonly PersistentAgentsClient? _agentClient;
+ private readonly TokenCredential? _tokenCredential;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public FoundryPersistentAgentFactory(PersistentAgentsClient agentClient)
+ {
+ Throw.IfNull(agentClient);
+
+ this._agentClient = agentClient;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public FoundryPersistentAgentFactory(TokenCredential tokenCredential)
+ {
+ Throw.IfNull(tokenCredential);
+
+ this._tokenCredential = tokenCredential;
+ }
+
+ ///
+ public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ var agentClient = this._agentClient ?? this.CreatePersistentAgentClient(promptAgent);
+
+ var modelId = promptAgent.Model?.ModelNameHint;
+ if (string.IsNullOrEmpty(modelId))
+ {
+ throw new InvalidOperationException("The model id must be specified in the agent definition model to create a foundry agent.");
+ }
+
+ //var outputSchema = promptAgent.OutputType; TODO: Fix converting RecordDataType to BinaryData
+ var modelOptions = promptAgent.Model?.Options;
+
+ return await agentClient.CreateAIAgentAsync(
+ model: modelId,
+ name: promptAgent.Name,
+ instructions: promptAgent.Instructions?.ToTemplateString(),
+ tools: promptAgent.GetToolDefinitions(),
+ toolResources: promptAgent.GetToolResources(),
+ temperature: (float?)modelOptions?.Temperature?.LiteralValue,
+ topP: (float?)modelOptions?.TopP?.LiteralValue,
+ //responseFormat: outputSchema.AsBinaryData(), TODO: Fix converting RecordDataType to BinaryData
+ metadata: promptAgent.Metadata?.ToDictionary(),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ private PersistentAgentsClient CreatePersistentAgentClient(GptComponentMetadata promptAgent)
+ {
+ var externalModel = promptAgent.Model as CurrentModels;
+ var connection = externalModel?.Connection as RemoteConnection;
+ if (connection is not null)
+ {
+ var endpoint = connection.Endpoint?.LiteralValue;
+ if (string.IsNullOrEmpty(endpoint))
+ {
+ throw new InvalidOperationException("The endpoint must be specified in the agent definition model connection to create an PersistentAgentsClient.");
+ }
+ if (this._tokenCredential is null)
+ {
+ throw new InvalidOperationException("A TokenCredential must be registered in the service provider to create an PersistentAgentsClient.");
+ }
+ return new PersistentAgentsClient(endpoint, this._tokenCredential);
+ }
+
+ throw new InvalidOperationException("A PersistentAgentsClient must be registered in the service provider or a FoundryConnection must be specified in the agent definition model connection to create an PersistentAgentsClient.");
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/JsonSchemaFunctionParameters.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/JsonSchemaFunctionParameters.cs
new file mode 100644
index 0000000000..c406825d5b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/JsonSchemaFunctionParameters.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Declarative.AzureAI;
+
+///
+/// A class to describe the parameters of an in a JSON Schema friendly way.
+///
+internal sealed class JsonSchemaFunctionParameters
+{
+ ///
+ /// The type of schema which is always "object" when describing function parameters.
+ ///
+ [JsonPropertyName("type")]
+ public string Type => "object";
+
+ ///
+ /// The list of required properties.
+ ///
+ [JsonPropertyName("required")]
+ public List Required { get; set; } = [];
+
+ ///
+ /// A dictionary of properties, keyed by name => JSON Schema.
+ ///
+ [JsonPropertyName("properties")]
+ public Dictionary Properties { get; set; } = [];
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj
new file mode 100644
index 0000000000..4f8874ef07
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj
@@ -0,0 +1,50 @@
+
+
+
+ $(ProjectsTargetFrameworks)
+ $(ProjectsDebugTargetFrameworks)
+ preview
+ $(NoWarn);MEAI001;OPENAI001
+
+
+
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+ Microsoft Agent Framework Declarative AzureAI
+ Provides Microsoft Agent Framework support for declarative AzureAI agents.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIAgentFactory.cs
new file mode 100644
index 0000000000..a729d1c025
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIAgentFactory.cs
@@ -0,0 +1,182 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.ClientModel;
+using Azure.AI.OpenAI;
+using Azure.Core;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+using OpenAI;
+using OpenAI.Assistants;
+using OpenAI.Chat;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides an abstract base class.
+///
+public abstract class OpenAIAgentFactory : AgentFactory
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ protected OpenAIAgentFactory(ILoggerFactory? loggerFactory)
+ {
+ this.LoggerFactory = loggerFactory;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ protected OpenAIAgentFactory(Uri endpoint, TokenCredential tokenCredential, ILoggerFactory? loggerFactory)
+ {
+ Throw.IfNull(endpoint);
+ Throw.IfNull(tokenCredential);
+
+ this._endpoint = endpoint;
+ this._tokenCredential = tokenCredential;
+ this.LoggerFactory = loggerFactory;
+ }
+
+ ///
+ /// Gets the instance used for creating loggers.
+ ///
+ protected ILoggerFactory? LoggerFactory { get; }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ protected ChatClient? CreateChatClient(GptComponentMetadata promptAgent)
+ {
+ var model = promptAgent.Model as CurrentModels;
+ var provider = model?.Provider?.Value ?? ModelProvider.OpenAI;
+ if (provider == ModelProvider.OpenAI)
+ {
+ return CreateOpenAIChatClient(promptAgent);
+ }
+ else if (provider == ModelProvider.AzureOpenAI)
+ {
+ Throw.IfNull(this._endpoint, "A endpoint must be specified to create an Azure OpenAI client");
+ Throw.IfNull(this._tokenCredential, "A token credential must be specified to create an Azure OpenAI client");
+ return CreateAzureOpenAIChatClient(promptAgent, this._endpoint, this._tokenCredential);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ protected AssistantClient? CreateAssistantClient(GptComponentMetadata promptAgent)
+ {
+ var model = promptAgent.Model as CurrentModels;
+ var provider = model?.Provider?.Value ?? ModelProvider.OpenAI;
+ if (provider == ModelProvider.OpenAI)
+ {
+ return CreateOpenAIAssistantClient(promptAgent);
+ }
+ else if (provider == ModelProvider.AzureOpenAI)
+ {
+ Throw.IfNull(this._endpoint, "The connection endpoint must be specified to create an Azure OpenAI client.");
+ Throw.IfNull(this._tokenCredential, "A token credential must be specified to create an Azure OpenAI client");
+ return CreateAzureOpenAIAssistantClient(promptAgent, this._endpoint, this._tokenCredential);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ protected OpenAIResponseClient? CreateResponseClient(GptComponentMetadata promptAgent)
+ {
+ var model = promptAgent.Model as CurrentModels;
+ var provider = model?.Provider?.Value ?? ModelProvider.OpenAI;
+ if (provider == ModelProvider.OpenAI)
+ {
+ return CreateOpenAIResponseClient(promptAgent);
+ }
+ else if (provider == ModelProvider.AzureOpenAI)
+ {
+ Throw.IfNull(this._endpoint, "The connection endpoint must be specified to create an Azure OpenAI client.");
+ Throw.IfNull(this._tokenCredential, "A token credential must be specified to create an Azure OpenAI client");
+ return CreateAzureOpenAIResponseClient(promptAgent, this._endpoint, this._tokenCredential);
+ }
+
+ return null;
+ }
+
+ #region private
+ private readonly Uri? _endpoint;
+ private readonly TokenCredential? _tokenCredential;
+
+ private static ChatClient CreateOpenAIChatClient(GptComponentMetadata promptAgent)
+ {
+ var modelId = promptAgent.Model?.ModelNameHint;
+ Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI agent.");
+
+ return CreateOpenAIClient(promptAgent).GetChatClient(modelId);
+ }
+
+ private static ChatClient CreateAzureOpenAIChatClient(GptComponentMetadata promptAgent, Uri endpoint, TokenCredential tokenCredential)
+ {
+ var deploymentName = promptAgent.Model?.ModelNameHint;
+ Throw.IfNullOrEmpty(deploymentName, "The deployment name (using model.id) must be specified in the agent definition to create an Azure OpenAI agent.");
+
+ return new AzureOpenAIClient(endpoint, tokenCredential).GetChatClient(deploymentName);
+ }
+
+ private static AssistantClient CreateOpenAIAssistantClient(GptComponentMetadata promptAgent)
+ {
+ var modelId = promptAgent.Model?.ModelNameHint;
+ Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI agent.");
+
+ return CreateOpenAIClient(promptAgent).GetAssistantClient();
+ }
+
+ private static AssistantClient CreateAzureOpenAIAssistantClient(GptComponentMetadata promptAgent, Uri endpoint, TokenCredential tokenCredential)
+ {
+ var deploymentName = promptAgent.Model?.ModelNameHint;
+ Throw.IfNullOrEmpty(deploymentName, "The deployment name (using model.id) must be specified in the agent definition to create an Azure OpenAI agent.");
+
+ return new AzureOpenAIClient(endpoint, tokenCredential).GetAssistantClient();
+ }
+
+ private static OpenAIResponseClient CreateOpenAIResponseClient(GptComponentMetadata promptAgent)
+ {
+ var modelId = promptAgent.Model?.ModelNameHint;
+ Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI agent.");
+
+ return CreateOpenAIClient(promptAgent).GetOpenAIResponseClient(modelId);
+ }
+
+ private static OpenAIResponseClient CreateAzureOpenAIResponseClient(GptComponentMetadata promptAgent, Uri endpoint, TokenCredential tokenCredential)
+ {
+ var deploymentName = promptAgent.Model?.ModelNameHint;
+ Throw.IfNullOrEmpty(deploymentName, "The deployment name (using model.id) must be specified in the agent definition to create an Azure OpenAI agent.");
+
+ return new AzureOpenAIClient(endpoint, tokenCredential).GetOpenAIResponseClient(deploymentName);
+ }
+
+ private static OpenAIClient CreateOpenAIClient(GptComponentMetadata promptAgent)
+ {
+ var model = promptAgent.Model as CurrentModels;
+
+ var keyConnection = model?.Connection as ApiKeyConnection;
+ Throw.IfNull(keyConnection, "A key connection must be specified when create an OpenAI client");
+
+ var apiKey = keyConnection.Key?.LiteralValue;
+ Throw.IfNullOrEmpty(apiKey, "The connection key must be specified in the agent definition to create an OpenAI client.");
+
+ var clientOptions = new OpenAIClientOptions();
+ var endpoint = keyConnection.Endpoint?.LiteralValue;
+ if (!string.IsNullOrEmpty(endpoint))
+ {
+ clientOptions.Endpoint = new Uri(endpoint);
+ }
+
+ return new OpenAIClient(new ApiKeyCredential(apiKey), clientOptions);
+ }
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIAssistantAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIAssistantAgentFactory.cs
new file mode 100644
index 0000000000..081a008881
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIAssistantAgentFactory.cs
@@ -0,0 +1,91 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.AI.Agents.Persistent;
+using Azure.Core;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+using OpenAI;
+using OpenAI.Assistants;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides an which creates instances of using a .
+///
+public sealed class OpenAIAssistantAgentFactory : OpenAIAgentFactory
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIAssistantAgentFactory(IList? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
+ {
+ this._functions = functions;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIAssistantAgentFactory(AssistantClient assistantClient, IList? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
+ {
+ Throw.IfNull(assistantClient);
+
+ this._assistantClient = assistantClient;
+ this._functions = functions;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIAssistantAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, loggerFactory)
+ {
+ this._functions = functions;
+ }
+
+ ///
+ public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ var model = promptAgent.Model as CurrentModels;
+ var apiType = model?.ApiType;
+ if (apiType?.IsUnknown() == false || apiType?.UnknownValue?.Equals(API_TYPE_ASSISTANTS, StringComparison.OrdinalIgnoreCase) == false)
+ {
+ return null;
+ }
+
+ var options = new ChatClientAgentOptions()
+ {
+ Name = promptAgent.Name,
+ Description = promptAgent.Description,
+ Instructions = promptAgent.Instructions?.ToTemplateString(),
+ ChatOptions = promptAgent.GetChatOptions(this._functions),
+ };
+
+ AssistantClient? assistantClient = this._assistantClient ?? this.CreateAssistantClient(promptAgent);
+ if (assistantClient is not null)
+ {
+ var modelId = promptAgent.Model?.ModelNameHint;
+ Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI Assistant.");
+ Throw.IfNullOrEmpty(promptAgent.Instructions?.ToTemplateString(), "The instructions must be specified in the agent definition to create an OpenAI Assistant.");
+
+ return await assistantClient.CreateAIAgentAsync(
+ modelId,
+ options
+ ).ConfigureAwait(false);
+ }
+
+ return null;
+ }
+
+ #region private
+ private readonly AssistantClient? _assistantClient;
+ private readonly IList? _functions;
+
+ private const string API_TYPE_ASSISTANTS = "ASSISTANTS";
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIChatAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIChatAgentFactory.cs
new file mode 100644
index 0000000000..587d39aae2
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIChatAgentFactory.cs
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.AI.Agents.Persistent;
+using Azure.Core;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+using OpenAI.Chat;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides an which creates instances of using a .
+///
+public sealed class OpenAIChatAgentFactory : OpenAIAgentFactory
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIChatAgentFactory(IList? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
+ {
+ this._functions = functions;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIChatAgentFactory(ChatClient chatClient, IList? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
+ {
+ Throw.IfNull(chatClient);
+
+ this._chatClient = chatClient;
+ this._functions = functions;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIChatAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, loggerFactory)
+ {
+ this._functions = functions;
+ }
+
+ ///
+ public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ var model = promptAgent.Model as CurrentModels;
+ var apiType = model?.ApiType;
+ if (apiType?.IsUnknown() == true || apiType?.Value != ModelApiType.Chat)
+ {
+ return null;
+ }
+
+ var options = new ChatClientAgentOptions()
+ {
+ Name = promptAgent.Name,
+ Description = promptAgent.Description,
+ Instructions = promptAgent.Instructions?.ToTemplateString(),
+ ChatOptions = promptAgent.GetChatOptions(this._functions),
+ };
+
+ ChatClient? chatClient = this._chatClient ?? this.CreateChatClient(promptAgent);
+ if (chatClient is not null)
+ {
+ return new ChatClientAgent(
+ chatClient.AsIChatClient(),
+ options,
+ this.LoggerFactory);
+ }
+
+ return null;
+ }
+
+ #region private
+ private readonly ChatClient? _chatClient;
+ private readonly IList? _functions;
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIResponseAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIResponseAgentFactory.cs
new file mode 100644
index 0000000000..1e5ddfc98b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIResponseAgentFactory.cs
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.AI.Agents.Persistent;
+using Azure.Core;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides an which creates instances of using a .
+///
+public sealed class OpenAIResponseAgentFactory : OpenAIAgentFactory
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIResponseAgentFactory(IList? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
+ {
+ this._functions = functions;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIResponseAgentFactory(OpenAIResponseClient responseClient, IList? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
+ {
+ Throw.IfNull(responseClient);
+
+ this._responseClient = responseClient;
+ this._functions = functions;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public OpenAIResponseAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, loggerFactory)
+ {
+ this._functions = functions;
+ }
+
+ ///
+ public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ var model = promptAgent.Model as CurrentModels;
+ var apiType = model?.ApiType;
+ if (apiType?.IsUnknown() == true || apiType?.Value != ModelApiType.Responses)
+ {
+ return null;
+ }
+
+ var options = new ChatClientAgentOptions()
+ {
+ Name = promptAgent.Name,
+ Description = promptAgent.Description,
+ Instructions = promptAgent.Instructions?.ToTemplateString(),
+ ChatOptions = promptAgent.GetChatOptions(this._functions),
+ };
+
+ var responseClient = this._responseClient ?? this.CreateResponseClient(promptAgent);
+ if (responseClient is not null)
+ {
+ return new ChatClientAgent(
+ responseClient.AsIChatClient(),
+ options,
+ this.LoggerFactory);
+ }
+
+ return null;
+ }
+
+ #region private
+ private readonly OpenAIResponseClient? _responseClient;
+ private readonly IList? _functions;
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/AgentBotElementYaml.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/AgentBotElementYaml.cs
new file mode 100644
index 0000000000..808bf76462
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/AgentBotElementYaml.cs
@@ -0,0 +1,91 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Bot.ObjectModel.Abstractions;
+using Microsoft.Bot.ObjectModel.Yaml;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Helper methods for creating from YAML.
+///
+internal static class AgentBotElementYaml
+{
+ ///
+ /// Convert the given YAML text to a model.
+ ///
+ /// YAML representation of the to use to create the prompt function.
+ /// Optional instance which provides environment variables to the template.
+ [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(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().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
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/AgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/AgentFactory.cs
new file mode 100644
index 0000000000..653c14bdf8
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/AgentFactory.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Represents a factory for creating instances.
+///
+public abstract class AgentFactory
+{
+ ///
+ /// Create a from the specified .
+ ///
+ /// Definition of the agent to create.
+ /// Optional cancellation token.
+ /// The created , if null the agent type is not supported.
+ public async Task 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.");
+ }
+
+ ///
+ /// Tries to create a from the specified .
+ ///
+ /// Definition of the agent to create.
+ /// Optional cancellation token.
+ /// The created , if null the agent type is not supported.
+ public abstract Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default);
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/AggregatorAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/AggregatorAgentFactory.cs
new file mode 100644
index 0000000000..e9ddcdd6d8
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/AggregatorAgentFactory.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides a which aggregates multiple agent factories.
+///
+public sealed class AggregatorAgentFactory : AgentFactory
+{
+ private readonly AgentFactory[] _agentFactories;
+
+ /// Initializes the instance.
+ /// Ordered instances to aggregate.
+ ///
+ /// Where multiple instances are provided, the first factory that supports the will be used.
+ ///
+ public AggregatorAgentFactory(params AgentFactory[] agentFactories)
+ {
+ Throw.IfNullOrEmpty(agentFactories);
+
+ foreach (AgentFactory agentFactory in agentFactories)
+ {
+ Throw.IfNull(agentFactory, nameof(agentFactories));
+ }
+
+ this._agentFactories = agentFactories;
+ }
+
+ ///
+ public override async Task 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;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/ChatClient/ChatClientAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/ChatClient/ChatClientAgentFactory.cs
new file mode 100644
index 0000000000..aa32da4112
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/ChatClient/ChatClientAgentFactory.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides an which creates instances of .
+///
+public sealed class ChatClientAgentFactory : AgentFactory
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public ChatClientAgentFactory(IChatClient chatClient, IList? functions = null, ILoggerFactory? loggerFactory = null)
+ {
+ Throw.IfNull(chatClient);
+
+ this._chatClient = chatClient;
+ this._functions = functions;
+ this._loggerFactory = loggerFactory;
+ }
+
+ ///
+ public override Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ var options = new ChatClientAgentOptions()
+ {
+ Name = promptAgent.Name,
+ Description = promptAgent.Description,
+ Instructions = promptAgent.Instructions?.ToTemplateString(),
+ ChatOptions = promptAgent.GetChatOptions(this._functions),
+ };
+
+ var agent = new ChatClientAgent(this._chatClient, options, this._loggerFactory);
+
+ return Task.FromResult(agent);
+ }
+
+ #region private
+ private readonly IChatClient _chatClient;
+ private readonly IList? _functions;
+ private readonly ILoggerFactory? _loggerFactory;
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/CodeInterpreterToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/CodeInterpreterToolExtensions.cs
new file mode 100644
index 0000000000..e6f13d5f54
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/CodeInterpreterToolExtensions.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class CodeInterpreterToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static HostedCodeInterpreterTool AsCodeInterpreterTool(this CodeInterpreterTool tool)
+ {
+ Throw.IfNull(tool);
+
+ return new HostedCodeInterpreterTool();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FileSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FileSearchToolExtensions.cs
new file mode 100644
index 0000000000..5e1cb1bb5f
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FileSearchToolExtensions.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Linq;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class FileSearchToolExtensions
+{
+ ///
+ /// Create a from a .
+ ///
+ /// Instance of
+ 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(),
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FunctionToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FunctionToolExtensions.cs
new file mode 100644
index 0000000000..2c54d7e749
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FunctionToolExtensions.cs
@@ -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.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class FunctionToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ ///
+ /// If a matching function already exists in the provided list, it will be returned.
+ /// Otherwise, a new function declaration will be created.
+ ///
+ /// Instance of
+ /// Instance of
+ internal static AITool CreateOrGetAITool(this InvokeClientTaskAction tool, IList? 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);
+ }
+
+ ///
+ /// Checks if a matches an .
+ ///
+ /// Instance of
+ /// Instance of
+ 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;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolApprovalModeExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolApprovalModeExtensions.cs
new file mode 100644
index 0000000000..ee5632368b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolApprovalModeExtensions.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class McpServerToolApprovalModeExtensions
+{
+ ///
+ /// Converts a to a .
+ ///
+ /// Instance of
+ 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,
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolExtensions.cs
new file mode 100644
index 0000000000..763e402625
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolExtensions.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class McpServerToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ 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(),
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/ModelOptionsExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/ModelOptionsExtensions.cs
new file mode 100644
index 0000000000..7ad4d26a6b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/ModelOptionsExtensions.cs
@@ -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.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class ModelOptionsExtensions
+{
+ ///
+ /// Converts the 'chatToolMode' property from a to a .
+ ///
+ /// Instance of
+ internal static ChatToolMode? AsChatToolMode(this ModelOptions modelOptions)
+ {
+ Throw.IfNull(modelOptions);
+
+ var mode = modelOptions.ExtensionData?.GetPropertyOrNull(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),
+ };
+ }
+
+ ///
+ /// Retrieves the 'additional_properties' property from a .
+ ///
+ /// Instance of
+ /// List of properties which should not be included in additional properties.
+ 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);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs
new file mode 100644
index 0000000000..dacc83bc63
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class PromptAgentExtensions
+{
+ ///
+ /// Retrieves the 'options' property from a as a instance.
+ ///
+ /// Instance of
+ /// Instance of
+ public static ChatOptions? GetChatOptions(this GptComponentMetadata promptAgent, IList? 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.ResponseInstructions?.ToTemplateString(),
+ Temperature = (float?)modelOptions?.Temperature?.LiteralValue,
+ MaxOutputTokens = (int?)modelOptions?.MaxOutputTokens?.LiteralValue,
+ TopP = (float?)modelOptions?.TopP?.LiteralValue,
+ TopK = (int?)modelOptions?.TopK?.LiteralValue,
+ FrequencyPenalty = (float?)modelOptions?.FrequencyPenalty?.LiteralValue,
+ PresencePenalty = (float?)modelOptions?.PresencePenalty?.LiteralValue,
+ Seed = modelOptions?.Seed?.LiteralValue,
+ ResponseFormat = outputSchema?.AsChatResponseFormat(),
+ ModelId = promptAgent.Model?.ModelNameHint,
+ StopSequences = modelOptions?.StopSequences,
+ AllowMultipleToolCalls = modelOptions?.AllowMultipleToolCalls?.LiteralValue,
+ ToolMode = modelOptions?.AsChatToolMode(),
+ Tools = tools,
+ AdditionalProperties = modelOptions?.GetAdditionalProperties(s_chatOptionProperties),
+ };
+ }
+
+ ///
+ /// Retrieves the 'tools' property from a .
+ ///
+ /// Instance of
+ /// Instance of
+ internal static List? GetAITools(this GptComponentMetadata promptAgent, IList? 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
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PropertyInfoExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PropertyInfoExtensions.cs
new file mode 100644
index 0000000000..a62fddec88
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PropertyInfoExtensions.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class PropertyInfoExtensions
+{
+ ///
+ /// Creates a of and
+ /// from an of and .
+ ///
+ /// A read-only dictionary of property names and their corresponding objects.
+ public static Dictionary AsObjectDictionary(this IReadOnlyDictionary properties)
+ {
+ var result = new Dictionary();
+
+ foreach (var property in properties)
+ {
+ result[property.Key] = BuildPropertySchema(property.Value);
+ }
+
+ return result;
+ }
+
+ #region private
+ private static Dictionary BuildPropertySchema(PropertyInfo propertyInfo)
+ {
+ var propertySchema = new Dictionary();
+
+ // 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>(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
+ {
+ ["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
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataTypeExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataTypeExtensions.cs
new file mode 100644
index 0000000000..e4b696d9cd
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataTypeExtensions.cs
@@ -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.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class RecordDataTypeExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ 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());
+ }
+
+ ///
+ /// Converts a to a .
+ ///
+ /// Instance of
+#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
+ {
+ ["type"] = "object",
+ ["properties"] = recordDataType.Properties.AsObjectDictionary(),
+ ["additionalProperties"] = false
+ };
+
+ var json = JsonSerializer.Serialize(schemaObject, ElementSerializer.CreateOptions());
+ return JsonSerializer.Deserialize(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
+
+ ///
+ /// Retrieves the 'schemaName' property from a .
+ ///
+ internal static string? GetSchemaName(this RecordDataType recordDataType)
+ {
+ Throw.IfNull(recordDataType);
+
+ return recordDataType.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("schemaName"))?.Value;
+ }
+
+ ///
+ /// Retrieves the 'schemaDescription' property from a .
+ ///
+ internal static string? GetSchemaDescription(this RecordDataType recordDataType)
+ {
+ Throw.IfNull(recordDataType);
+
+ return recordDataType.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("schemaDescription"))?.Value;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataValueExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataValueExtensions.cs
new file mode 100644
index 0000000000..6351b7badb
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataValueExtensions.cs
@@ -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.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class RecordDataValueExtensions
+{
+ ///
+ /// Retrieves a 'number' property from a
+ ///
+ /// Instance of
+ /// Path of the property to retrieve
+ public static decimal? GetNumber(this RecordDataValue recordData, string propertyPath)
+ {
+ Throw.IfNull(recordData);
+
+ var numberValue = recordData.GetPropertyOrNull(InitializablePropertyPath.Create(propertyPath));
+ return numberValue?.Value;
+ }
+
+ ///
+ /// Retrieves a nullable boolean value from the specified property path within the given record data.
+ ///
+ /// Instance of
+ /// Path of the property to retrieve
+ public static bool? GetBoolean(this RecordDataValue recordData, string propertyPath)
+ {
+ Throw.IfNull(recordData);
+
+ var booleanValue = recordData.GetPropertyOrNull(InitializablePropertyPath.Create(propertyPath));
+ return booleanValue?.Value;
+ }
+
+ ///
+ /// Converts a to a .
+ ///
+ /// Instance of
+ public static IReadOnlyDictionary ToDictionary(this RecordDataValue recordData)
+ {
+ Throw.IfNull(recordData);
+
+ return recordData.Properties.ToDictionary(
+ kvp => kvp.Key,
+ kvp => kvp.Value?.ToString() ?? string.Empty
+ );
+ }
+
+ ///
+ /// Retrieves the 'schema' property from a .
+ ///
+ /// Instance of
+#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(InitializablePropertyPath.Create("json_schema.schema"));
+ if (schemaStr?.Value is not null)
+ {
+ return JsonSerializer.Deserialize(schemaStr.Value);
+ }
+ }
+ catch (InvalidCastException)
+ {
+ // Ignore and try next
+ }
+
+ var responseFormRec = recordData.GetPropertyOrNull(InitializablePropertyPath.Create("json_schema.schema"));
+ if (responseFormRec is not null)
+ {
+ var json = JsonSerializer.Serialize(responseFormRec, ElementSerializer.CreateOptions());
+ return JsonSerializer.Deserialize(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}"),
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/WebSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/WebSearchToolExtensions.cs
new file mode 100644
index 0000000000..e6ee360308
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/WebSearchToolExtensions.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class WebSearchToolExtensions
+{
+ ///
+ /// Create a from a .
+ ///
+ /// Instance of
+ internal static HostedWebSearchTool CreateWebSearchTool(this WebSearchTool tool)
+ {
+ Throw.IfNull(tool);
+
+ return new HostedWebSearchTool();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/YamlAgentFactoryExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/YamlAgentFactoryExtensions.cs
new file mode 100644
index 0000000000..2bbda44a71
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/YamlAgentFactoryExtensions.cs
@@ -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;
+
+///
+/// Extension methods for to support YAML based agent definitions.
+///
+public static class YamlAgentFactoryExtensions
+{
+ ///
+ /// Create a from the given agent YAML.
+ ///
+ /// which will be used to create the agent.
+ /// Text string containing the YAML representation of an .
+ /// Optional cancellation token
+ [RequiresDynamicCode("Calls YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()")]
+ public static Task CreateFromYamlAsync(this AgentFactory agentFactory, string agentYaml, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(agentFactory);
+ Throw.IfNullOrEmpty(agentYaml);
+
+ var agentDefinition = AgentBotElementYaml.FromYaml(agentYaml);
+
+ return agentFactory.CreateAsync(
+ agentDefinition,
+ cancellationToken);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj b/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj
new file mode 100644
index 0000000000..72b2d0d743
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj
@@ -0,0 +1,43 @@
+
+
+
+ $(ProjectsTargetFrameworks)
+ $(ProjectsDebugTargetFrameworks)
+ preview
+ $(NoWarn);MEAI001
+
+
+
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ Microsoft Agent Framework Declarative
+ Provides Microsoft Agent Framework support for declarative agents.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs
index e20d1ab448..6520ff117f 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs
@@ -28,6 +28,8 @@ public static class AGUIEndpointRouteBuilderExtensions
/// The URL pattern for the endpoint.
/// The agent instance.
/// An for the mapped endpoint.
+ [RequiresUnreferencedCode("Dynamic code may be required for this endpoint.")]
+ [RequiresDynamicCode("Dynamic code may be required for this endpoint.")]
public static IEndpointConventionBuilder MapAGUI(
this IEndpointRouteBuilder endpoints,
[StringSyntax("route")] string pattern,
diff --git a/dotnet/src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj b/dotnet/src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj
index e3d7f00aa1..bf447db538 100644
--- a/dotnet/src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj
@@ -35,6 +35,7 @@
+
diff --git a/dotnet/src/Shared/IntegrationTests/FoundryProjectConfiguration.cs b/dotnet/src/Shared/IntegrationTests/FoundryProjectConfiguration.cs
new file mode 100644
index 0000000000..cce0d8afe1
--- /dev/null
+++ b/dotnet/src/Shared/IntegrationTests/FoundryProjectConfiguration.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Shared.IntegrationTests;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+#pragma warning disable CA1812 // Internal class that is apparently never instantiated.
+
+internal sealed class FoundryProjectConfiguration
+{
+ public string Endpoint { get; set; }
+
+ public string ModelId { get; set; }
+
+ public string BingConnectionId { get; set; }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/AzureOpenAIDeclarativeAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/AzureOpenAIDeclarativeAgentTests.cs
new file mode 100644
index 0000000000..86d6c91542
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/AzureOpenAIDeclarativeAgentTests.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Threading.Tasks;
+using Azure.Identity;
+using Microsoft.Extensions.AI;
+using Xunit.Abstractions;
+
+namespace Microsoft.Agents.AI.Declarative.IntegrationTests;
+
+///
+/// Tests for declarative agents created using .
+///
+public sealed class AzureOpenAIDeclarativeAgentTests(ITestOutputHelper output) : BaseIntegrationTest(output)
+{
+ [Fact]
+ public async Task CanCreateAndRunChatAgentAsync()
+ {
+ // Example function tool that can be used by the agent.
+ [Description("Get the weather for a given location.")]
+ static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+ // Arrange
+ var endpointUri = new Uri(this.FoundryConfiguration.Endpoint);
+ var tokenCredential = new AzureCliCredential();
+ var agentFactory = new AggregatorAgentFactory(
+ [
+ new OpenAIChatAgentFactory(endpointUri, tokenCredential),
+ new OpenAIResponseAgentFactory(endpointUri, tokenCredential),
+ new OpenAIAssistantAgentFactory(endpointUri, tokenCredential)
+ ]);
+ var agentYaml = File.ReadAllText("../../../../../../agent-samples/azure/AzureOpenAIChat.yaml");
+ agentYaml = agentYaml.Replace("=Env.AZURE_OPENAI_DEPLOYMENT_NAME", this.FoundryConfiguration.DeploymentName);
+
+ // Create agent run options
+ var options = new ChatClientAgentRunOptions(new()
+ {
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+ });
+
+ // Act
+ var agent = await agentFactory.CreateFromYamlAsync(agentYaml);
+ var response = await agent!.RunAsync("What is the weather in Cambridge, MA in °C?", options: options);
+ this.Output.WriteLine($"Agent Response: {response.Text}");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Text);
+ }
+
+ [Fact]
+ public async Task CanCreateAndRunResponsesAgentAsync()
+ {
+ // Example function tool that can be used by the agent.
+ [Description("Get the weather for a given location.")]
+ static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+ // Arrange
+ var endpointUri = new Uri(this.FoundryConfiguration.Endpoint);
+ var tokenCredential = new AzureCliCredential();
+ var agentFactory = new AggregatorAgentFactory(
+ [
+ new OpenAIChatAgentFactory(endpointUri, tokenCredential),
+ new OpenAIResponseAgentFactory(endpointUri, tokenCredential),
+ new OpenAIAssistantAgentFactory(endpointUri, tokenCredential)
+ ]);
+ var agentYaml = File.ReadAllText("../../../../../../agent-samples/azure/AzureOpenAIResponses.yaml");
+ agentYaml = agentYaml.Replace("=Env.AZURE_OPENAI_DEPLOYMENT_NAME", this.FoundryConfiguration.DeploymentName);
+
+ // Create agent run options
+ var options = new ChatClientAgentRunOptions(new()
+ {
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+ });
+
+ // Act
+ var agent = await agentFactory.CreateFromYamlAsync(agentYaml);
+ var response = await agent!.RunAsync("What is the weather in Cambridge, MA in °C?", options: options);
+ this.Output.WriteLine($"Agent Response: {response.Text}");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Text);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/BaseIntegrationTest.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/BaseIntegrationTest.cs
new file mode 100644
index 0000000000..68d7723676
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/BaseIntegrationTest.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Shared.IntegrationTests;
+using Xunit.Abstractions;
+
+namespace Microsoft.Agents.AI.Declarative.IntegrationTests;
+
+///
+/// Base class for integration tests.
+///
+public abstract class BaseIntegrationTest : IDisposable
+{
+ private IConfigurationRoot? _configuration;
+ private AzureAIConfiguration? _foundryConfiguration;
+ private OpenAIConfiguration? _openAIConfiguration;
+ private FoundryProjectConfiguration? _foundryProjectConfiguration;
+
+ protected IConfigurationRoot Configuration => this._configuration ??= InitializeConfig();
+
+ internal AzureAIConfiguration FoundryConfiguration
+ {
+ get
+ {
+ this._foundryConfiguration ??= this.Configuration.GetSection("AzureAI").Get();
+ Assert.NotNull(this._foundryConfiguration);
+ return this._foundryConfiguration;
+ }
+ }
+
+ internal OpenAIConfiguration OpenAIConfiguration
+ {
+ get
+ {
+ this._openAIConfiguration ??= this.Configuration.GetSection("OpenAI").Get();
+ Assert.NotNull(this._openAIConfiguration);
+ return this._openAIConfiguration;
+ }
+ }
+
+ internal FoundryProjectConfiguration FoundryProjectConfiguration
+ {
+ get
+ {
+ this._foundryProjectConfiguration ??= this.Configuration.GetSection("FoundryProject").Get();
+ Assert.NotNull(this._foundryProjectConfiguration);
+ return this._foundryProjectConfiguration;
+ }
+ }
+
+ public TestOutputAdapter Output { get; }
+
+ protected BaseIntegrationTest(ITestOutputHelper output)
+ {
+ this.Output = new TestOutputAdapter(output);
+ Console.SetOut(this.Output);
+ }
+
+ public void Dispose()
+ {
+ this.Dispose(isDisposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool isDisposing)
+ {
+ if (isDisposing)
+ {
+ this.Output.Dispose();
+ }
+ }
+
+ private static IConfigurationRoot InitializeConfig() =>
+ new ConfigurationBuilder()
+ .AddJsonFile("appsettings.Development.json", true)
+ .AddEnvironmentVariables()
+ .AddUserSecrets(Assembly.GetExecutingAssembly())
+ .Build();
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/ChatClientDeclarativeAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/ChatClientDeclarativeAgentTests.cs
new file mode 100644
index 0000000000..236d8ed52f
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/ChatClientDeclarativeAgentTests.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Threading.Tasks;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Extensions.AI;
+using Xunit.Abstractions;
+
+namespace Microsoft.Agents.AI.Declarative.IntegrationTests;
+
+///
+/// Tests for declarative agents created using .
+///
+public sealed class ChatClientDeclarativeAgentTests(ITestOutputHelper output) : BaseIntegrationTest(output)
+{
+ [Fact]
+ public async Task CanCreateAndRunAssistantAgentAsync()
+ {
+ // Arrange
+ var chatClient = this.CreateIChatClient();
+ var agentFactory = new ChatClientAgentFactory(chatClient);
+ var agentYaml = File.ReadAllText("../../../../../../agent-samples/chatclient/Assistant.yaml");
+
+ // Act
+ var agent = await agentFactory.CreateFromYamlAsync(agentYaml);
+ var response = await agent!.RunAsync("Tell me a joke about a pirate in Italian.");
+ this.Output.WriteLine($"Agent Response: {response.Text}");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Text);
+ }
+
+ [Fact]
+ public async Task CanCreateAndRunGetWeatherAgentAsync()
+ {
+ // Example function tool that can be used by the agent.
+ [Description("Get the weather for a given location.")]
+ static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+ // Arrange
+ var chatClient = this.CreateIChatClient();
+ var agentFactory = new ChatClientAgentFactory(chatClient, [AIFunctionFactory.Create(GetWeather, "GetWeather")]);
+ var agentYaml = File.ReadAllText("../../../../../../agent-samples/chatclient/GetWeather.yaml");
+
+ // Act
+ var agent = await agentFactory.CreateFromYamlAsync(agentYaml);
+ var response = await agent!.RunAsync("What is the weather in Cambridge, MA in °C?");
+ this.Output.WriteLine($"Agent Response: {response.Text}");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Text);
+ }
+
+ private IChatClient CreateIChatClient()
+ {
+ var endpoint = this.FoundryConfiguration.Endpoint;
+ var deploymentName = this.FoundryConfiguration.DeploymentName;
+
+ // Create the chat client
+ return new AzureOpenAIClient(
+ new Uri(endpoint),
+ new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .AsIChatClient();
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/FoundryDeclarativeAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/FoundryDeclarativeAgentTests.cs
new file mode 100644
index 0000000000..a345031f4d
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/FoundryDeclarativeAgentTests.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Threading.Tasks;
+using Azure.Identity;
+using Microsoft.Extensions.AI;
+using Xunit.Abstractions;
+
+namespace Microsoft.Agents.AI.Declarative.IntegrationTests;
+
+///
+/// Tests for declarative agents created using .
+///
+public sealed class FoundryDeclarativeAgentTests(ITestOutputHelper output) : BaseIntegrationTest(output)
+{
+ [Fact]
+ public async Task CanCreateAndRunPersistentAgentAsync()
+ {
+ // Example function tool that can be used by the agent.
+ [Description("Get the weather for a given location.")]
+ static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+ // Arrange
+ var agentFactory = new FoundryPersistentAgentFactory(new AzureCliCredential());
+ var agentYaml = File.ReadAllText("../../../../../../agent-samples/foundry/PersistentAgent.yaml");
+ agentYaml = agentYaml.Replace("=Env.AZURE_FOUNDRY_PROJECT_ENDPOINT", this.FoundryProjectConfiguration.Endpoint);
+ agentYaml = agentYaml.Replace("=Env.AZURE_FOUNDRY_PROJECT_MODEL_ID", this.FoundryProjectConfiguration.ModelId);
+
+ // Create agent run options
+ var options = new ChatClientAgentRunOptions(new()
+ {
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+ });
+
+ // Act
+ var agent = await agentFactory.CreateFromYamlAsync(agentYaml);
+ var response = await agent!.RunAsync("What is the weather in Cambridge, MA in °C?", options: options);
+ this.Output.WriteLine($"Agent Response: {response.Text}");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Text);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/Microsoft.Agents.AI.Declarative.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/Microsoft.Agents.AI.Declarative.IntegrationTests.csproj
new file mode 100644
index 0000000000..9b84b6e4c5
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/Microsoft.Agents.AI.Declarative.IntegrationTests.csproj
@@ -0,0 +1,36 @@
+
+
+
+ $(ProjectsTargetFrameworks)
+ $(ProjectsDebugTargetFrameworks)
+ True
+ true
+ $(NoWarn);CS1591
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/OpenAIDeclarativeAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/OpenAIDeclarativeAgentTests.cs
new file mode 100644
index 0000000000..a0b2548ea2
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/OpenAIDeclarativeAgentTests.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Xunit.Abstractions;
+
+namespace Microsoft.Agents.AI.Declarative.IntegrationTests;
+
+///
+/// Tests for declarative agents created using .
+///
+public sealed class OpenAIDeclarativeAgentTests(ITestOutputHelper output) : BaseIntegrationTest(output)
+{
+ [Fact]
+ public async Task CanCreateAndRunChatAgentAsync()
+ {
+ // Example function tool that can be used by the agent.
+ [Description("Get the weather for a given location.")]
+ static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+ // Arrange
+ var agentFactory = new AggregatorAgentFactory(
+ [
+ new OpenAIChatAgentFactory(),
+ new OpenAIResponseAgentFactory(),
+ new OpenAIAssistantAgentFactory()
+ ]);
+ var agentYaml = File.ReadAllText("../../../../../../agent-samples/openai/OpenAIChat.yaml");
+ agentYaml = agentYaml.Replace("=Env.OPENAI_APIKEY", this.OpenAIConfiguration.ApiKey);
+ agentYaml = agentYaml.Replace("=Env.OPENAI_MODEL", this.OpenAIConfiguration.ChatModelId);
+
+ // Create agent run options
+ var options = new ChatClientAgentRunOptions(new()
+ {
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+ });
+
+ // Act
+ var agent = await agentFactory.CreateFromYamlAsync(agentYaml);
+ var response = await agent!.RunAsync("What is the weather in Cambridge, MA in °C?", options: options);
+ this.Output.WriteLine($"Agent Response: {response.Text}");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Text);
+ }
+
+ [Fact]
+ public async Task CanCreateAndRunResponsesAgentAsync()
+ {
+ // Example function tool that can be used by the agent.
+ [Description("Get the weather for a given location.")]
+ static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+ // Arrange
+ var agentFactory = new AggregatorAgentFactory(
+ [
+ new OpenAIChatAgentFactory(),
+ new OpenAIResponseAgentFactory(),
+ new OpenAIAssistantAgentFactory()
+ ]);
+ var agentYaml = File.ReadAllText("../../../../../../agent-samples/openai/OpenAIResponses.yaml");
+ agentYaml = agentYaml.Replace("=Env.OPENAI_APIKEY", this.OpenAIConfiguration.ApiKey);
+ agentYaml = agentYaml.Replace("=Env.OPENAI_MODEL", this.OpenAIConfiguration.ChatModelId);
+
+ // Create agent run options
+ var options = new ChatClientAgentRunOptions(new()
+ {
+ Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
+ });
+
+ // Act
+ var agent = await agentFactory.CreateFromYamlAsync(agentYaml);
+ var response = await agent!.RunAsync("What is the weather in Cambridge, MA in °C?", options: options);
+ this.Output.WriteLine($"Agent Response: {response.Text}");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Text);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/TestOutputAdapter.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/TestOutputAdapter.cs
new file mode 100644
index 0000000000..d57d054aaf
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.IntegrationTests/TestOutputAdapter.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Microsoft.Extensions.Logging;
+using Xunit.Abstractions;
+
+namespace Microsoft.Agents.AI.Declarative.IntegrationTests;
+
+public sealed class TestOutputAdapter(ITestOutputHelper output) : TextWriter, ILogger, ILoggerFactory
+{
+ private readonly Stack _scopes = [];
+
+ public override Encoding Encoding { get; } = Encoding.UTF8;
+
+ public void AddProvider(ILoggerProvider provider) => throw new NotSupportedException();
+
+ public ILogger CreateLogger(string categoryName) => this;
+
+ public bool IsEnabled(LogLevel logLevel) => true;
+
+ public override void WriteLine(object? value) => this.SafeWrite($"{value}");
+
+ public override void WriteLine(string? format, params object?[] arg) => this.SafeWrite(string.Format(format ?? string.Empty, arg));
+
+ public override void WriteLine(string? value) => this.SafeWrite(value ?? string.Empty);
+
+ public override void Write(object? value) => this.SafeWrite($"{value}");
+
+ public override void Write(char[]? buffer) => this.SafeWrite(new string(buffer));
+
+ public IDisposable BeginScope(TState state) where TState : notnull
+ {
+ this._scopes.Push($"{state}");
+ return new LoggerScope(() => this._scopes.Pop());
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ string message = formatter(state, exception);
+ string scope = this._scopes.Count > 0 ? $"[{this._scopes.Peek()}] " : string.Empty;
+ output.WriteLine($"{scope}{message}");
+ }
+
+ private void SafeWrite(string value)
+ {
+ try
+ {
+ output.WriteLine(value ?? string.Empty);
+ }
+ catch (InvalidOperationException exception) when (exception.Message == "There is no currently active test.")
+ {
+ // This exception is thrown when the test output is accessed outside of a test context.
+ // We can ignore it since we are not in a test context.
+ }
+ }
+
+ private sealed class LoggerScope(Action action) : IDisposable
+ {
+ private bool _disposed;
+
+ public void Dispose()
+ {
+ if (!this._disposed)
+ {
+ action.Invoke();
+ this._disposed = true;
+ }
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AgentBotElementYamlTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AgentBotElementYamlTests.cs
new file mode 100644
index 0000000000..0d9774cd42
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AgentBotElementYamlTests.cs
@@ -0,0 +1,256 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text.Json.Serialization;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.Agents.AI.Declarative.UnitTests;
+
+///
+/// Unit tests for
+///
+public sealed class AgentBotElementYamlTests
+{
+ [Theory]
+ [InlineData(PromptAgents.AgentWithEverything)]
+ [InlineData(PromptAgents.AgentWithApiKeyConnection)]
+ [InlineData(PromptAgents.AgentWithEnvironmentVariables)]
+ [InlineData(PromptAgents.AgentWithOutputSchema)]
+ [InlineData(PromptAgents.OpenAIChatAgent)]
+ [InlineData(PromptAgents.AgentWithCurrentModels)]
+ [InlineData(PromptAgents.AgentWithRemoteConnection)]
+ public void FromYaml_DoesNotThrow(string text)
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(text);
+
+ // Assert
+ Assert.NotNull(agent);
+ }
+
+ [Fact]
+ public void FromYaml_Properties()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("AgentName", agent.Name);
+ Assert.Equal("Agent description", agent.Description);
+ Assert.Equal("You are a helpful assistant.", agent.Instructions?.ToTemplateString());
+ Assert.NotNull(agent.Model);
+ Assert.True(agent.Tools.Length > 0);
+ }
+
+ [Fact]
+ public void FromYaml_CurrentModels()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithCurrentModels);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ Assert.Equal("gpt-4o", agent.Model.ModelNameHint);
+ Assert.NotNull(agent.Model.Options);
+ Assert.Equal(0.7f, (float?)agent.Model.Options?.Temperature?.LiteralValue);
+ Assert.Equal(0.9f, (float?)agent.Model.Options?.TopP?.LiteralValue);
+
+ // Assert contents using extension methods
+ Assert.Equal(1024, agent.Model.Options?.MaxOutputTokens?.LiteralValue);
+ Assert.Equal(50, agent.Model.Options?.TopK?.LiteralValue);
+ Assert.Equal(0.7f, (float?)agent.Model.Options?.FrequencyPenalty?.LiteralValue);
+ Assert.Equal(0.7f, (float?)agent.Model.Options?.PresencePenalty?.LiteralValue);
+ Assert.Equal(42, agent.Model.Options?.Seed?.LiteralValue);
+ Assert.Equal(PromptAgents.s_stopSequences, agent.Model.Options?.StopSequences);
+ Assert.True(agent.Model.Options?.AllowMultipleToolCalls?.LiteralValue);
+ Assert.Equal(ChatToolMode.Auto, agent.Model.Options?.AsChatToolMode());
+ }
+
+ [Fact]
+ public void FromYaml_OutputSchema()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithOutputSchema);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.OutputType);
+ var responseFormat = agent.OutputType.AsChatResponseFormat() as ChatResponseFormatJson;
+ Assert.NotNull(responseFormat);
+ Assert.NotNull(responseFormat.Schema);
+ }
+
+ [Fact]
+ public void FromYaml_CodeInterpreter()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var codeInterpreterTools = tools.Where(t => t is CodeInterpreterTool).ToArray();
+ Assert.Single(codeInterpreterTools);
+ var codeInterpreterTool = codeInterpreterTools[0] as CodeInterpreterTool;
+ Assert.NotNull(codeInterpreterTool);
+ }
+
+ [Fact]
+ public void FromYaml_FunctionTool()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var functionTools = tools.Where(t => t is InvokeClientTaskAction).ToArray();
+ Assert.Single(functionTools);
+ var functionTool = functionTools[0] as InvokeClientTaskAction;
+ Assert.NotNull(functionTool);
+ Assert.Equal("GetWeather", functionTool.Name);
+ Assert.Equal("Get the weather for a given location.", functionTool.Description);
+ // TODO check schema
+ }
+
+ [Fact]
+ public void FromYaml_MCP()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var mcpTools = tools.Where(t => t is McpServerTool).ToArray();
+ Assert.Single(mcpTools);
+ var mcpTool = mcpTools[0] as McpServerTool;
+ Assert.NotNull(mcpTool);
+ Assert.Equal("PersonInfoTool", mcpTool.ServerName?.LiteralValue);
+ var connection = mcpTool.Connection as AnonymousConnection;
+ Assert.NotNull(connection);
+ Assert.Equal("https://my-mcp-endpoint.com/api", connection.Endpoint?.LiteralValue);
+ }
+
+ [Fact]
+ public void FromYaml_WebSearchTool()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var webSearchTools = tools.Where(t => t is WebSearchTool).ToArray();
+ Assert.Single(webSearchTools);
+ Assert.NotNull(webSearchTools[0] as WebSearchTool);
+ }
+
+ [Fact]
+ public void FromYaml_FileSearchTool()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var fileSearchTools = tools.Where(t => t is FileSearchTool).ToArray();
+ Assert.Single(fileSearchTools);
+ var fileSearchTool = fileSearchTools[0] as FileSearchTool;
+ Assert.NotNull(fileSearchTool);
+
+ // Verify vector store content property exists and has correct values
+ Assert.NotNull(fileSearchTool.VectorStoreIds);
+ Assert.Equal(3, fileSearchTool.VectorStoreIds.LiteralValue.Length);
+ Assert.Equal("1", fileSearchTool.VectorStoreIds.LiteralValue[0]);
+ Assert.Equal("2", fileSearchTool.VectorStoreIds.LiteralValue[1]);
+ Assert.Equal("3", fileSearchTool.VectorStoreIds.LiteralValue[2]);
+ }
+
+ [Fact]
+ public void FromYaml_ApiKeyConnection()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithApiKeyConnection);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ var model = agent.Model as CurrentModels;
+ Assert.NotNull(model);
+ Assert.NotNull(model.Connection);
+ Assert.IsType(model.Connection);
+ var connection = model.Connection as ApiKeyConnection;
+ Assert.NotNull(connection);
+ Assert.Equal("https://my-azure-openai-endpoint.openai.azure.com/", connection.Endpoint?.LiteralValue);
+ Assert.Equal("my-api-key", connection.Key?.LiteralValue);
+ }
+
+ [Fact]
+ public void FromYaml_RemoteConnection()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithRemoteConnection);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ var model = agent.Model as CurrentModels;
+ Assert.NotNull(model);
+ Assert.NotNull(model.Connection);
+ Assert.IsType(model.Connection);
+ var connection = model.Connection as RemoteConnection;
+ Assert.NotNull(connection);
+ Assert.Equal("https://my-azure-openai-endpoint.openai.azure.com/", connection.Endpoint?.LiteralValue);
+ }
+
+ [Fact]
+ public void FromYaml_WithEnvironmentVariables()
+ {
+ // Arrange
+ IConfiguration configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["OpenAIEndpoint"] = "endpoint",
+ ["OpenAIModelId"] = "modelId",
+ ["OpenAIApiKey"] = "apiKey"
+ })
+ .Build();
+
+ // Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEnvironmentVariables, configuration);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ var model = agent.Model as CurrentModels;
+ Assert.NotNull(model);
+ Assert.NotNull(model.Connection);
+ Assert.IsType(model.Connection);
+ //Assert.Equal("https://my-azure-openai-endpoint.openai.azure.com/", agent.Model.Connection.Endpoint?.LiteralValue);
+ //Assert.Equal("apiKey", connection.Key?.LiteralValue);
+ //Assert.Equal("modelId", model.Id);
+ }
+
+ ///
+ /// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
+ ///
+ [Description("Information about a person including their name, age, and occupation")]
+ public class PersonInfo
+ {
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("age")]
+ public int? Age { get; set; }
+
+ [JsonPropertyName("occupation")]
+ public string? Occupation { get; set; }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/ChatClient/ChatClientAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/ChatClient/ChatClientAgentFactoryTests.cs
new file mode 100644
index 0000000000..b9078ee0b0
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/ChatClient/ChatClientAgentFactoryTests.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Moq;
+
+namespace Microsoft.Agents.AI.Declarative.UnitTests.ChatClient;
+
+///
+/// Unit tests for .
+///
+public sealed class ChatClientAgentFactoryTests
+{
+ private readonly Mock _mockChatClient;
+
+ public ChatClientAgentFactoryTests()
+ {
+ this._mockChatClient = new();
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_WithChatClientInConstructor_CreatesAgentAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Test Description", agent.Description);
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_Creates_ChatClientAgentAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ var chatClientAgent = agent as ChatClientAgent;
+ Assert.NotNull(chatClientAgent);
+ Assert.Equal("You are a helpful assistant.", chatClientAgent.Instructions);
+ Assert.NotNull(chatClientAgent.ChatClient);
+ Assert.NotNull(chatClientAgent.ChatOptions);
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_Creates_ChatOptionsAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ var chatClientAgent = agent as ChatClientAgent;
+ Assert.NotNull(chatClientAgent?.ChatOptions);
+ Assert.Equal("Provide detailed and accurate responses.", chatClientAgent?.ChatOptions?.Instructions);
+ Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.Temperature);
+ Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.FrequencyPenalty);
+ Assert.Equal(1024, chatClientAgent?.ChatOptions?.MaxOutputTokens);
+ Assert.Equal(0.9F, chatClientAgent?.ChatOptions?.TopP);
+ Assert.Equal(50, chatClientAgent?.ChatOptions?.TopK);
+ Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.PresencePenalty);
+ Assert.Equal(42L, chatClientAgent?.ChatOptions?.Seed);
+ Assert.NotNull(chatClientAgent?.ChatOptions?.ResponseFormat);
+ Assert.Equal("gpt-4o", chatClientAgent?.ChatOptions?.ModelId);
+ Assert.Equal(["###", "END", "STOP"], chatClientAgent?.ChatOptions?.StopSequences);
+ Assert.True(chatClientAgent?.ChatOptions?.AllowMultipleToolCalls);
+ Assert.Equal(ChatToolMode.Auto, chatClientAgent?.ChatOptions?.ToolMode);
+ Assert.Equal("customValue", chatClientAgent?.ChatOptions?.AdditionalProperties?["customProperty"]);
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_Creates_ToolsAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ var chatClientAgent = agent as ChatClientAgent;
+ Assert.NotNull(chatClientAgent?.ChatOptions?.Tools);
+ var tools = chatClientAgent?.ChatOptions?.Tools;
+ Assert.Equal(5, tools?.Count);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj
new file mode 100644
index 0000000000..dcdded59ce
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ $(ProjectsTargetFrameworks)
+ $(NoWarn);IDE1006
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/PromptAgents.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/PromptAgents.cs
new file mode 100644
index 0000000000..075b134981
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/PromptAgents.cs
@@ -0,0 +1,326 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Bot.ObjectModel;
+
+namespace Microsoft.Agents.AI.Declarative.UnitTests;
+internal static class PromptAgents
+{
+ internal const string AgentWithEverything =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.7
+ maxOutputTokens: 1024
+ topP: 0.9
+ topK: 50
+ frequencyPenalty: 0.0
+ presencePenalty: 0.0
+ seed: 42
+ responseFormat: text
+ stopSequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allowMultipleToolCalls: true
+ tools:
+ - kind: codeInterpreter
+ inputs:
+ - kind: HostedFileContent
+ FileId: fileId123
+ - kind: function
+ name: GetWeather
+ description: Get the weather for a given location.
+ parameters:
+ - name: location
+ type: string
+ description: The city and state, e.g. San Francisco, CA
+ required: true
+ - name: unit
+ type: string
+ description: The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.
+ required: false
+ enum:
+ - celsius
+ - fahrenheit
+ - kind: mcp
+ serverName: PersonInfoTool
+ serverDescription: Get information about a person.
+ connection:
+ kind: AnonymousConnection
+ endpoint: https://my-mcp-endpoint.com/api
+ allowedTools:
+ - "GetPersonInfo"
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ approvalMode:
+ kind: HostedMcpServerToolRequireSpecificApprovalMode
+ AlwaysRequireApprovalToolNames:
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ NeverRequireApprovalToolNames:
+ - "GetPersonInfo"
+ - kind: webSearch
+ name: WebSearchTool
+ description: Search the web for information.
+ - kind: fileSearch
+ name: FileSearchTool
+ description: Search files for information.
+ ranker: default
+ scoreThreshold: 0.5
+ maxResults: 5
+ maxContentLength: 2000
+ vectorStoreIds:
+ - 1
+ - 2
+ - 3
+ """;
+
+ internal const string AgentWithOutputSchema =
+ """
+ kind: Prompt
+ name: Translation Assistant
+ description: A helpful assistant that translates text to a specified language.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.9
+ topP: 0.95
+ instructions: You are a helpful assistant. You answer questions in {language}. You return your answers in a JSON format.
+ additionalInstructions: You must always respond in the specified language.
+ tools:
+ - kind: codeInterpreter
+ template:
+ format: PowerFx # Mustache is the other option
+ parser: None # Prompty and XML are the other options
+ inputSchema:
+ properties:
+ language: string
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+ internal const string AgentWithApiKeyConnection =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ connection:
+ kind: ApiKey
+ endpoint: https://my-azure-openai-endpoint.openai.azure.com/
+ key: my-api-key
+ """;
+
+ internal const string AgentWithRemoteConnection =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ connection:
+ kind: Remote
+ endpoint: https://my-azure-openai-endpoint.openai.azure.com/
+ """;
+
+ internal const string AgentWithEnvironmentVariables =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: =Env.OpenAIModelId
+ connection:
+ kind: apiKey
+ endpoint: =Env.OpenAIEndpoint
+ key: =Env.OpenAIApiKey
+ """;
+
+ internal const string OpenAIChatAgent =
+ """
+ kind: Prompt
+ name: Assistant
+ description: Helpful assistant
+ instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
+ model:
+ id: =Env.OPENAI_MODEL
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: apiKey
+ key: =Env.OPENAI_APIKEY
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+ internal const string AgentWithCurrentModels =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.7
+ maxOutputTokens: 1024
+ topP: 0.9
+ topK: 50
+ frequencyPenalty: 0.7
+ presencePenalty: 0.7
+ seed: 42
+ responseFormat: text
+ stopSequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allowMultipleToolCalls: true
+ chatToolMode: auto
+ """;
+
+ internal const string AgentWithCurrentModelsSnakeCase =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.7
+ max_output_tokens: 1024
+ top_p: 0.9
+ top_k: 50
+ frequency_penalty: 0.7
+ presence_penalty: 0.7
+ seed: 42
+ response_format: text
+ stop_sequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allow_multiple_tool_calls: true
+ chat_tool_mode: auto
+ """;
+
+ internal static readonly string[] s_stopSequences = ["###", "END", "STOP"];
+
+ internal static GptComponentMetadata CreateTestPromptAgent(string? publisher = "OpenAI", string? apiType = "Chat")
+ {
+ string agentYaml =
+ $"""
+ kind: Prompt
+ name: Test Agent
+ description: Test Description
+ instructions: You are a helpful assistant.
+ additionalInstructions: Provide detailed and accurate responses.
+ model:
+ id: gpt-4o
+ publisher: {publisher}
+ apiType: {apiType}
+ options:
+ modelId: gpt-4o
+ temperature: 0.7
+ maxOutputTokens: 1024
+ topP: 0.9
+ topK: 50
+ frequencyPenalty: 0.7
+ presencePenalty: 0.7
+ seed: 42
+ responseFormat: text
+ stopSequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allowMultipleToolCalls: true
+ chatToolMode: auto
+ customProperty: customValue
+ connection:
+ kind: apiKey
+ endpoint: https://my-azure-openai-endpoint.openai.azure.com/
+ key: my-api-key
+ tools:
+ - kind: codeInterpreter
+ - kind: function
+ name: GetWeather
+ description: Get the weather for a given location.
+ parameters:
+ - name: location
+ type: string
+ description: The city and state, e.g. San Francisco, CA
+ required: true
+ - name: unit
+ type: string
+ description: The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.
+ required: false
+ enum:
+ - celsius
+ - fahrenheit
+ - kind: mcp
+ serverName: PersonInfoTool
+ serverDescription: Get information about a person.
+ allowedTools:
+ - "GetPersonInfo"
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ approvalMode:
+ kind: HostedMcpServerToolRequireSpecificApprovalMode
+ AlwaysRequireApprovalToolNames:
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ NeverRequireApprovalToolNames:
+ - "GetPersonInfo"
+ connection:
+ kind: AnonymousConnection
+ endpoint: https://my-mcp-endpoint.com/api
+ - kind: webSearch
+ name: WebSearchTool
+ description: Search the web for information.
+ - kind: fileSearch
+ name: FileSearchTool
+ description: Search files for information.
+ vectorStoreIds:
+ - 1
+ - 2
+ - 3
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+ return AgentBotElementYaml.FromYaml(agentYaml);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Properties/launchSettings.json b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Properties/launchSettings.json
new file mode 100644
index 0000000000..350fd25434
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "Microsoft.Agents.AI.Hosting.A2A.UnitTests": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:54921;http://localhost:54922"
+ }
+ }
+}
\ No newline at end of file