.NET: Declarative Agents (#1301)

* AgentFactory abstractions and ChatClient implementation

* Add a getitng started sample

* Update to latest M.B.OM

* Add some additional samples

* Work in progress

* Merge latest from main

* Start to add support for using different kinds of connections

* Remove IsSupported

* Remove IsSupported

* Refactor code to create clients to support DI

* Add some unit tests

* Update based on the latest code review feedback

* Add support for OOB tools when using persistent agent sdk

* Fix sample naming

* Fix error based on latest MEAI

* Update M.B.OM package to latest

* Update to the latest M.B.OM release

* Remove some obsolete helper methods

* Update to the latest M.B.OM version

* Fix broken unit test

* Update MCP sample

* Bump to latest M.B.OM release

* Update to latest M.B.OM release

* Update to latest M.B.OM release

* Switch to using ExternalModel

* Update to latest M.B.OM

* Resolve merge conflicts

* All tests pass

* All tests pass

* Start to clean up the code

* Start to clean up the code

* More clean up

* More clean up

* More clean up

* Fix apiType checks

* Run dotnet format

* Fix typo

* Address code review feedback

* Add all properties for MCP tool

* Address code review feedback

* Address code review feedback

* Fix merge

* Undo warnings

* Undo test change

* More copilot feedback

* Make class sealed

* Address additional core review feedback

---------

Co-authored-by: Mark Wallace <markwallace@microsoft.com>
This commit is contained in:
Mark Wallace
2025-11-11 11:39:20 +00:00
committed by GitHub
Unverified
parent 105dc82c39
commit aaa91954c5
75 changed files with 3980 additions and 3 deletions
+3
View File
@@ -0,0 +1,3 @@
# Declarative Agents
This folder contains sample agent definitions than be ran using the [Declarative Agents](../dotnet/samples/GettingStarted/DeclarativeAgents) demo.
@@ -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.
+25
View File
@@ -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.
@@ -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.
+18
View File
@@ -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.
+26
View File
@@ -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
@@ -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.
@@ -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.
+28
View File
@@ -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.
+28
View File
@@ -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.
+1 -1
View File
@@ -8,7 +8,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>13</LangVersion>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<NoWarn>$(NoWarn);NU5128;NU1900;NU1603</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ProjectsCoreTargetFrameworks>net9.0;net8.0</ProjectsCoreTargetFrameworks>
<ProjectsDebugCoreTargetFrameworks>net9.0</ProjectsDebugCoreTargetFrameworks>
+11
View File
@@ -64,6 +64,7 @@
<Project Path="samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step18_Declarative/Agent_Step18_Declarative.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/DevUI/">
<File Path="samples/GettingStarted/DevUI/README.md" />
@@ -80,6 +81,12 @@
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/DeclarativeAgents/">
<Project Path="samples/GettingStarted/DeclarativeAgents/Azure/DeclarativeAzureAgents.csproj" />
<Project Path="samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
<Project Path="samples/GettingStarted/DeclarativeAgents/Foundry/DeclarativeFoundryAgents.csproj" />
<Project Path="samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentWithRAG/">
<File Path="samples/GettingStarted/AgentWithRAG/README.md" />
<Project Path="samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/AgentWithRAG_Step01_BasicTextRAG.csproj" />
@@ -296,6 +303,8 @@
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
<Project Path="src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj" />
<Project Path="src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj" />
@@ -313,6 +322,7 @@
<Project Path="tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj" />
<Project Path="tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj" />
<Project Path="tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Declarative.IntegrationTests/Microsoft.Agents.AI.Declarative.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.csproj" />
@@ -323,6 +333,7 @@
<Folder Name="/Tests/UnitTests/">
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj" />
+1 -1
View File
@@ -13,4 +13,4 @@
<package pattern="Azure.AI.Agents" />
</packageSource>
</packageSourceMapping>
</configuration>
</configuration>
+1 -1
View File
@@ -12,7 +12,7 @@
<!-- Package validation. Baseline Version should be the latest version available on NuGet. -->
<PackageValidationBaselineVersion>0.0.1</PackageValidationBaselineVersion>
<!-- Validate assembly attributes only for Publish builds -->
<NoWarn Condition="'$(Configuration)' != 'Publish'">$(NoWarn);CP0003</NoWarn>
<NoWarn Condition="'$(Configuration)' != 'Publish'">$(NoWarn);CP0003;NU1900;NU1603</NoWarn>
<!-- Do not validate reference assemblies -->
<NoWarn>$(NoWarn);CP1002</NoWarn>
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Declarative\Microsoft.Agents.AI.Declarative.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -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);
}
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Declarative.AzureAI\Microsoft.Agents.AI.Declarative.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -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 <yaml-file-path> <prompt>");
Console.WriteLine(" <yaml-file-path>: The path to the YAML file containing the agent definition");
Console.WriteLine(" <prompt>: 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));
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Declarative\Microsoft.Agents.AI.Declarative.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample 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 <yaml-file-path> <prompt>");
Console.WriteLine(" <yaml-file-path>: The path to the YAML file containing the agent definition");
Console.WriteLine(" <prompt>: 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));
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Declarative.AzureAI\Microsoft.Agents.AI.Declarative.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -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 <yaml-file-path> <prompt>");
Console.WriteLine(" <yaml-file-path>: The path to the YAML file containing the agent definition");
Console.WriteLine(" <prompt>: 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));
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Declarative.AzureAI\Microsoft.Agents.AI.Declarative.AzureAI.csproj" />
</ItemGroup>
</Project>
@@ -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 <yaml-file-path> <prompt>");
Console.WriteLine(" <yaml-file-path>: The path to the YAML file containing the agent definition");
Console.WriteLine(" <prompt>: 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));
@@ -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;
/// <summary>
/// Extension methods for <see cref="CodeInterpreterTool"/>.
/// </summary>
internal static class CodeInterpreterToolExtensions
{
/// <summary>
/// Creates a <see cref="CodeInterpreterToolDefinition"/> from a <see cref="CodeInterpreterTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="CodeInterpreterTool"/></param>
internal static CodeInterpreterToolDefinition CreateCodeInterpreterToolDefinition(this CodeInterpreterTool tool)
{
Throw.IfNull(tool);
return new CodeInterpreterToolDefinition();
}
/// <summary>
/// Collects the file IDs from the extension data of a <see cref="CodeInterpreterTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="CodeInterpreterTool"/></param>
internal static List<string>? GetFileIds(this CodeInterpreterTool tool)
{
var fileIds = tool.ExtensionData?.GetPropertyOrNull<TableDataValue>(InitializablePropertyPath.Create("fileIds"));
return fileIds is not null
? [.. fileIds.Values.Select(fileId => fileId.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("value"))?.Value)]
: null;
}
/// <summary>
/// Collects the data sources from the extension data of a <see cref="CodeInterpreterTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="CodeInterpreterTool"/></param>
internal static List<VectorStoreDataSource>? GetDataSources(this CodeInterpreterTool tool)
{
var dataSources = tool.ExtensionData?.GetPropertyOrNull<TableDataValue>(InitializablePropertyPath.Create("dataSources"));
return dataSources is not null
? dataSources.Values.Select(dataSource => dataSource.CreateDataSource()).ToList()
: null;
}
}
@@ -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;
/// <summary>
/// Extension methods for <see cref="FileSearchTool"/>.
/// </summary>
internal static class FileSearchToolExtensions
{
/// <summary>
/// Creates a <see cref="FileSearchToolDefinition"/> from a <see cref="FileSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="FileSearchTool"/></param>
internal static FileSearchToolDefinition CreateFileSearchToolDefinition(this FileSearchTool tool)
{
Throw.IfNull(tool);
// TODO: Add support for FileSearchToolDefinitionDetails.
return new FileSearchToolDefinition();
}
/// <summary>
/// Get the vector store IDs for the specified <see cref="FileSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="FileSearchTool"/></param>
internal static List<string>? GetVectorStoreIds(this FileSearchTool tool)
{
return tool.VectorStoreIds?.LiteralValue.ToList();
}
internal static IList<VectorStoreConfigurations>? GetVectorStoreConfigurations(this FileSearchTool tool)
{
var dataSources = tool.ExtensionData?.GetPropertyOrNull<TableDataValue>(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<StringDataValue>(InitializablePropertyPath.Create("storeName"))?.Value;
Throw.IfNullOrEmpty(storeName);
var dataSources = value.GetDataSources();
Throw.IfNull(dataSources);
return new VectorStoreConfigurations(storeName, new VectorStoreConfiguration(dataSources));
}
}
@@ -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;
/// <summary>
/// Extension methods for <see cref="InvokeClientTaskAction"/>.
/// </summary>
public static class FunctionToolExtensions
{
/// <summary>
/// Creates a <see cref="FunctionToolDefinition"/> from a <see cref="InvokeClientTaskAction"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="InvokeClientTaskAction"/></param>
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);
}
/// <summary>
/// Creates the parameters schema for a <see cref="InvokeClientTaskAction"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="InvokeClientTaskAction"/></param>
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}";
}
@@ -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;
/// <summary>
/// Extension methods for <see cref="HostedCodeInterpreterTool"/>.
/// </summary>
internal static class HostedCodeInterpreterToolExtensions
{
/// <summary>
/// Creates a <see cref="CodeInterpreterToolDefinition"/> from a <see cref="HostedCodeInterpreterTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="CodeInterpreterTool"/></param>
internal static CodeInterpreterToolDefinition CreateHostedCodeInterpreterToolDefinition(this HostedCodeInterpreterTool tool)
{
Throw.IfNull(tool);
return new CodeInterpreterToolDefinition();
}
}
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Extensions.AI;
/// <summary>
/// Extension methods for <see cref="HostedFileSearchTool"/>.
/// </summary>
internal static class HostedFileSearchToolExtensions
{
/// <summary>
/// Creates a <see cref="FileSearchToolDefinition"/> from a <see cref="HostedFileSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="HostedFileSearchTool"/></param>
internal static FileSearchToolDefinition CreateFileSearchToolDefinition(this HostedFileSearchTool tool)
{
Throw.IfNull(tool);
// TODO: Add support for FileSearchToolDefinitionDetails.
return new FileSearchToolDefinition();
}
}
@@ -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;
/// <summary>
/// Extension methods for <see cref="HostedMcpServerTool"/>.
/// </summary>
internal static class HostedMcpServerToolExtensions
{
/// <summary>
/// Creates a <see cref="MCPToolDefinition"/> from a <see cref="HostedMcpServerTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="HostedMcpServerTool"/></param>
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;
}
}
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Extensions.AI;
/// <summary>
/// Extension methods for <see cref="HostedWebSearchTool"/>.
/// </summary>
internal static class HostedWebSearchToolExtensions
{
/// <summary>
/// Creates a <see cref="BingGroundingToolDefinition"/> from a <see cref="HostedWebSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="HostedWebSearchTool"/></param>
internal static BingGroundingToolDefinition CreateBingGroundingToolDefinition(this HostedWebSearchTool tool)
{
Throw.IfNull(tool);
// TODO: Add support for BingGroundingSearchToolParameters.
var parameters = new BingGroundingSearchToolParameters([]);
return new BingGroundingToolDefinition(parameters);
}
}
@@ -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;
/// <summary>
/// Extension methods for <see cref="McpServerTool"/>.
/// </summary>
internal static class McpServerToolExtensions
{
/// <summary>
/// Creates a <see cref="MCPToolDefinition"/> from a <see cref="McpServerTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="McpServerTool"/></param>
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);
}
}
@@ -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;
/// <summary>
/// Extension methods for <see cref="GptComponentMetadata"/>.
/// </summary>
internal static class PromptAgentExtensions
{
/// <summary>
/// Return the Foundry tool definitions which corresponds with the provided <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Instance of <see cref="GptComponentMetadata"/></param>
internal static IEnumerable<Azure.AI.Agents.Persistent.ToolDefinition> GetToolDefinitions(this GptComponentMetadata promptAgent)
{
Throw.IfNull(promptAgent);
return promptAgent.Tools.Select<TaskAction, Azure.AI.Agents.Persistent.ToolDefinition>(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();
}
/// <summary>
/// Return the Foundry tool resources which corresponds with the provided <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Instance of <see cref="GptComponentMetadata"/></param>
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<CodeInterpreterTool>();
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<FileSearchTool>();
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<T>(this GptComponentMetadata promptAgent)
{
return promptAgent.Tools.FirstOrDefault(tool => tool is T);
}
#endregion
}
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="RecordDataType"/>.
/// </summary>
internal static class RecordDataTypeExtensions
{
/// <summary>
/// Creates a <see cref="ChatResponseFormat"/> from a <see cref="RecordDataType"/>.
/// </summary>
/// <param name="recordDataType">Instance of <see cref="RecordDataType"/></param>
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
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
}
@@ -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;
/// <summary>
/// Extension methods for <see cref="RecordDataValue"/>.
/// </summary>
internal static class RecordDataValueExtensions
{
/// <summary>
/// Gets the data sources from the specified <see cref="RecordDataValue"/>.
/// </summary>
internal static List<VectorStoreDataSource>? GetDataSources(this RecordDataValue value)
{
var dataSources = value.GetPropertyOrNull<TableDataValue>(InitializablePropertyPath.Create("options.data_sources"));
return dataSources?.Values.Select(dataSource => dataSource.CreateDataSource()).ToList();
}
/// <summary>
/// Creates a new instance of <see cref="VectorStoreDataSource"/> using the specified <see cref="RecordDataValue"/>.
/// </summary>
internal static VectorStoreDataSource CreateDataSource(this RecordDataValue value)
{
Throw.IfNull(value);
string? assetIdentifier = value.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("assetIdentifier"))?.Value;
Throw.IfNullOrEmpty(assetIdentifier);
string? assetType = value.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("assetType"))?.Value;
Throw.IfNullOrEmpty(assetType);
return new VectorStoreDataSource(assetIdentifier, new VectorStoreDataSourceAssetType(assetType));
}
}
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="WebSearchTool"/>.
/// </summary>
internal static class WebSearchToolExtensions
{
/// <summary>
/// Creates a <see cref="BingGroundingToolDefinition"/> from a <see cref="WebSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="WebSearchTool"/></param>
internal static BingGroundingToolDefinition CreateBingGroundingToolDefinition(this WebSearchTool tool)
{
Throw.IfNull(tool);
// TODO: Add support for BingGroundingSearchToolParameters.
var parameters = new BingGroundingSearchToolParameters([]);
return new BingGroundingToolDefinition(parameters);
}
}
@@ -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;
/// <summary>
/// Provides an <see cref="AgentFactory"/> which creates instances of <see cref="AIAgent"/> using a <see cref="PersistentAgentsClient"/>.
/// </summary>
public sealed class FoundryPersistentAgentFactory : AgentFactory
{
private readonly PersistentAgentsClient? _agentClient;
private readonly TokenCredential? _tokenCredential;
/// <summary>
/// Creates a new instance of the <see cref="FoundryPersistentAgentFactory"/> class.
/// </summary>
public FoundryPersistentAgentFactory(PersistentAgentsClient agentClient)
{
Throw.IfNull(agentClient);
this._agentClient = agentClient;
}
/// <summary>
/// Creates a new instance of the <see cref="FoundryPersistentAgentFactory"/> class.
/// </summary>
public FoundryPersistentAgentFactory(TokenCredential tokenCredential)
{
Throw.IfNull(tokenCredential);
this._tokenCredential = tokenCredential;
}
/// <inheritdoc/>
public override async Task<AIAgent?> 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.");
}
}
@@ -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;
/// <summary>
/// A class to describe the parameters of an <see cref="AIFunction"/> in a JSON Schema friendly way.
/// </summary>
internal sealed class JsonSchemaFunctionParameters
{
/// <summary>
/// The type of schema which is always "object" when describing function parameters.
/// </summary>
[JsonPropertyName("type")]
public string Type => "object";
/// <summary>
/// The list of required properties.
/// </summary>
[JsonPropertyName("required")]
public List<string> Required { get; set; } = [];
/// <summary>
/// A dictionary of properties, keyed by name => JSON Schema.
/// </summary>
[JsonPropertyName("properties")]
public Dictionary<string, JsonElement> Properties { get; set; } = [];
}
@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<VersionSuffix>preview</VersionSuffix>
<NoWarn>$(NoWarn);MEAI001;OPENAI001</NoWarn>
</PropertyGroup>
<PropertyGroup>
<InjectSharedThrow>true</InjectSharedThrow>
<InjectDiagnosticClassesOnLegacy>true</InjectDiagnosticClassesOnLegacy>
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<PropertyGroup>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework Declarative AzureAI</Title>
<Description>Provides Microsoft Agent Framework support for declarative AzureAI agents.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
<PackageReference Include="Microsoft.PowerFx.Interpreter" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.Agents.AI.AzureAI.Persistent\Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<ProjectReference Include="..\Microsoft.Agents.AI.Declarative\Microsoft.Agents.AI.Declarative.csproj" />
<ProjectReference Include="..\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.Declarative.AzureAI.UnitTests" />
</ItemGroup>
</Project>
@@ -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;
/// <summary>
/// Provides an <see cref="OpenAIAgentFactory"/> abstract base class.
/// </summary>
public abstract class OpenAIAgentFactory : AgentFactory
{
/// <summary>
/// Creates a new instance of the <see cref="OpenAIAgentFactory"/> class.
/// </summary>
protected OpenAIAgentFactory(ILoggerFactory? loggerFactory)
{
this.LoggerFactory = loggerFactory;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIAgentFactory"/> class.
/// </summary>
protected OpenAIAgentFactory(Uri endpoint, TokenCredential tokenCredential, ILoggerFactory? loggerFactory)
{
Throw.IfNull(endpoint);
Throw.IfNull(tokenCredential);
this._endpoint = endpoint;
this._tokenCredential = tokenCredential;
this.LoggerFactory = loggerFactory;
}
/// <summary>
/// Gets the <see cref="ILoggerFactory"/> instance used for creating loggers.
/// </summary>
protected ILoggerFactory? LoggerFactory { get; }
/// <summary>
/// Creates a new instance of the <see cref="ChatClient"/> class.
/// </summary>
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;
}
/// <summary>
/// Creates a new instance of the <see cref="AssistantClient"/> class.
/// </summary>
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;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIResponseClient"/> class.
/// </summary>
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
}
@@ -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;
/// <summary>
/// Provides an <see cref="AgentFactory"/> which creates instances of <see cref="AIAgent"/> using a <see cref="AssistantClient"/>.
/// </summary>
public sealed class OpenAIAssistantAgentFactory : OpenAIAgentFactory
{
/// <summary>
/// Creates a new instance of the <see cref="OpenAIAssistantAgentFactory"/> class.
/// </summary>
public OpenAIAssistantAgentFactory(IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
this._functions = functions;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIAssistantAgentFactory"/> class.
/// </summary>
public OpenAIAssistantAgentFactory(AssistantClient assistantClient, IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
Throw.IfNull(assistantClient);
this._assistantClient = assistantClient;
this._functions = functions;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIAssistantAgentFactory"/> class.
/// </summary>
public OpenAIAssistantAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, loggerFactory)
{
this._functions = functions;
}
/// <inheritdoc/>
public override async Task<AIAgent?> 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<AIFunction>? _functions;
private const string API_TYPE_ASSISTANTS = "ASSISTANTS";
#endregion
}
@@ -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;
/// <summary>
/// Provides an <see cref="AgentFactory"/> which creates instances of <see cref="AIAgent"/> using a <see cref="ChatClient"/>.
/// </summary>
public sealed class OpenAIChatAgentFactory : OpenAIAgentFactory
{
/// <summary>
/// Creates a new instance of the <see cref="OpenAIChatAgentFactory"/> class.
/// </summary>
public OpenAIChatAgentFactory(IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
this._functions = functions;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIChatAgentFactory"/> class.
/// </summary>
public OpenAIChatAgentFactory(ChatClient chatClient, IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
Throw.IfNull(chatClient);
this._chatClient = chatClient;
this._functions = functions;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIChatAgentFactory"/> class.
/// </summary>
public OpenAIChatAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, loggerFactory)
{
this._functions = functions;
}
/// <inheritdoc/>
public override async Task<AIAgent?> 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<AIFunction>? _functions;
#endregion
}
@@ -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;
/// <summary>
/// Provides an <see cref="AgentFactory"/> which creates instances of <see cref="AIAgent"/> using a <see cref="OpenAIResponseClient"/>.
/// </summary>
public sealed class OpenAIResponseAgentFactory : OpenAIAgentFactory
{
/// <summary>
/// Creates a new instance of the <see cref="OpenAIResponseAgentFactory"/> class.
/// </summary>
public OpenAIResponseAgentFactory(IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
this._functions = functions;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIResponseAgentFactory"/> class.
/// </summary>
public OpenAIResponseAgentFactory(OpenAIResponseClient responseClient, IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
Throw.IfNull(responseClient);
this._responseClient = responseClient;
this._functions = functions;
}
/// <summary>
/// Creates a new instance of the <see cref="OpenAIChatAgentFactory"/> class.
/// </summary>
public OpenAIResponseAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, loggerFactory)
{
this._functions = functions;
}
/// <inheritdoc/>
public override async Task<AIAgent?> 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<AIFunction>? _functions;
#endregion
}
@@ -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;
/// <summary>
/// Helper methods for creating <see cref="BotElement"/> from YAML.
/// </summary>
internal static class AgentBotElementYaml
{
/// <summary>
/// Convert the given YAML text to a <see cref="GptComponentMetadata"/> model.
/// </summary>
/// <param name="text">YAML representation of the <see cref="BotElement"/> to use to create the prompt function.</param>
/// <param name="configuration">Optional <see cref="IConfiguration"/> instance which provides environment variables to the template.</param>
[RequiresDynamicCode("Calls YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()")]
public static GptComponentMetadata FromYaml(string text, IConfiguration? configuration = null)
{
Throw.IfNullOrEmpty(text);
using var yamlReader = new StringReader(text);
BotElement rootElement = YamlSerializer.Deserialize<BotElement>(yamlReader) ?? throw new InvalidDataException("Text does not contain a valid agent definition.");
if (rootElement is not GptComponentMetadata promptAgent)
{
throw new InvalidDataException($"Unsupported root element: {rootElement.GetType().Name}. Expected an {nameof(GptComponentMetadata)}.");
}
var botDefinition = WrapPromptAgentWithBot(promptAgent, configuration);
return botDefinition.Descendants().OfType<GptComponentMetadata>().First();
}
#region private
private sealed class AgentFeatureConfiguration : IFeatureConfiguration
{
public long GetInt64Value(string settingName, long defaultValue) => defaultValue;
public string GetStringValue(string settingName, string defaultValue) => defaultValue;
public bool IsEnvironmentFeatureEnabled(string featureName, bool defaultValue) => true;
public bool IsTenantFeatureEnabled(string featureName, bool defaultValue) => defaultValue;
}
public static BotDefinition WrapPromptAgentWithBot(this GptComponentMetadata element, IConfiguration? configuration = null)
{
var botBuilder =
new BotDefinition.Builder
{
Components =
{
new GptComponent.Builder
{
SchemaName = "default-schema",
Metadata = element.ToBuilder(),
}
}
};
if (configuration is not null)
{
foreach (var kvp in configuration.AsEnumerable().Where(kvp => kvp.Value is not null))
{
botBuilder.EnvironmentVariables.Add(new EnvironmentVariableDefinition.Builder()
{
SchemaName = kvp.Key,
Id = Guid.NewGuid(),
DisplayName = kvp.Key,
ValueComponent = new EnvironmentVariableValue.Builder()
{
Id = Guid.NewGuid(),
Value = kvp.Value!,
},
});
}
}
return botBuilder.Build();
}
#endregion
}
@@ -0,0 +1,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;
/// <summary>
/// Represents a factory for creating <see cref="AIAgent"/> instances.
/// </summary>
public abstract class AgentFactory
{
/// <summary>
/// Create a <see cref="AIAgent"/> from the specified <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Definition of the agent to create.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <return>The created <see cref="AIAgent"/>, if null the agent type is not supported.</return>
public async Task<AIAgent> CreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
{
Throw.IfNull(promptAgent);
var agent = await this.TryCreateAsync(promptAgent, cancellationToken).ConfigureAwait(false);
return agent ?? throw new NotSupportedException($"Agent type {promptAgent.Kind} is not supported.");
}
/// <summary>
/// Tries to create a <see cref="AIAgent"/> from the specified <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Definition of the agent to create.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <return>The created <see cref="AIAgent"/>, if null the agent type is not supported.</return>
public abstract Task<AIAgent?> TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default);
}
@@ -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;
/// <summary>
/// Provides a <see cref="AgentFactory"/> which aggregates multiple agent factories.
/// </summary>
public sealed class AggregatorAgentFactory : AgentFactory
{
private readonly AgentFactory[] _agentFactories;
/// <summary>Initializes the instance.</summary>
/// <param name="agentFactories">Ordered <see cref="AgentFactory"/> instances to aggregate.</param>
/// <remarks>
/// Where multiple <see cref="AgentFactory"/> instances are provided, the first factory that supports the <see cref="GptComponentMetadata"/> will be used.
/// </remarks>
public AggregatorAgentFactory(params AgentFactory[] agentFactories)
{
Throw.IfNullOrEmpty(agentFactories);
foreach (AgentFactory agentFactory in agentFactories)
{
Throw.IfNull(agentFactory, nameof(agentFactories));
}
this._agentFactories = agentFactories;
}
/// <inheritdoc/>
public override async Task<AIAgent?> TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
{
Throw.IfNull(promptAgent);
foreach (var agentFactory in this._agentFactories)
{
var agent = await agentFactory.TryCreateAsync(promptAgent, cancellationToken).ConfigureAwait(false);
if (agent is not null)
{
return agent;
}
}
return null;
}
}
@@ -0,0 +1,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;
/// <summary>
/// Provides an <see cref="AgentFactory"/> which creates instances of <see cref="ChatClientAgent"/>.
/// </summary>
public sealed class ChatClientAgentFactory : AgentFactory
{
/// <summary>
/// Creates a new instance of the <see cref="ChatClientAgentFactory"/> class.
/// </summary>
public ChatClientAgentFactory(IChatClient chatClient, IList<AIFunction>? functions = null, ILoggerFactory? loggerFactory = null)
{
Throw.IfNull(chatClient);
this._chatClient = chatClient;
this._functions = functions;
this._loggerFactory = loggerFactory;
}
/// <inheritdoc/>
public override Task<AIAgent?> TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
{
Throw.IfNull(promptAgent);
var options = new ChatClientAgentOptions()
{
Name = promptAgent.Name,
Description = promptAgent.Description,
Instructions = promptAgent.Instructions?.ToTemplateString(),
ChatOptions = promptAgent.GetChatOptions(this._functions),
};
var agent = new ChatClientAgent(this._chatClient, options, this._loggerFactory);
return Task.FromResult<AIAgent?>(agent);
}
#region private
private readonly IChatClient _chatClient;
private readonly IList<AIFunction>? _functions;
private readonly ILoggerFactory? _loggerFactory;
#endregion
}
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="CodeInterpreterTool"/>.
/// </summary>
internal static class CodeInterpreterToolExtensions
{
/// <summary>
/// Creates a <see cref="HostedCodeInterpreterTool"/> from a <see cref="CodeInterpreterTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="CodeInterpreterTool"/></param>
internal static HostedCodeInterpreterTool AsCodeInterpreterTool(this CodeInterpreterTool tool)
{
Throw.IfNull(tool);
return new HostedCodeInterpreterTool();
}
}
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="FileSearchTool"/>.
/// </summary>
internal static class FileSearchToolExtensions
{
/// <summary>
/// Create a <see cref="HostedFileSearchTool"/> from a <see cref="FileSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="FileSearchTool"/></param>
internal static HostedFileSearchTool CreateFileSearchTool(this FileSearchTool tool)
{
Throw.IfNull(tool);
return new HostedFileSearchTool()
{
MaximumResultCount = (int?)tool.MaximumResultCount?.LiteralValue,
Inputs = tool.VectorStoreIds?.LiteralValue.Select(id => (AIContent)new HostedVectorStoreContent(id)).ToList(),
};
}
}
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="InvokeClientTaskAction"/>.
/// </summary>
internal static class FunctionToolExtensions
{
/// <summary>
/// Creates a <see cref="AIFunctionDeclaration"/> from a <see cref="InvokeClientTaskAction"/>.
/// </summary>
/// <remarks>
/// If a matching function already exists in the provided list, it will be returned.
/// Otherwise, a new function declaration will be created.
/// </remarks>
/// <param name="tool">Instance of <see cref="InvokeClientTaskAction"/></param>
/// <param name="functions">Instance of <see cref="IList{AIFunction}"/></param>
internal static AITool CreateOrGetAITool(this InvokeClientTaskAction tool, IList<AIFunction>? functions)
{
Throw.IfNull(tool);
Throw.IfNull(tool.Name);
// use the tool from the provided list if it exists
if (functions is not null)
{
var function = functions.FirstOrDefault(f => tool.Matches(f));
if (function is not null)
{
return function;
}
}
return AIFunctionFactory.CreateDeclaration(
name: tool.Name,
description: tool.Description,
jsonSchema: tool.ClientActionInputSchema?.GetSchema() ?? s_defaultSchema);
}
/// <summary>
/// Checks if a <see cref="InvokeClientTaskAction"/> matches an <see cref="AITool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="InvokeClientTaskAction"/></param>
/// <param name="aiFunc">Instance of <see cref="AIFunction"/></param>
internal static bool Matches(this InvokeClientTaskAction tool, AIFunction aiFunc)
{
Throw.IfNull(tool);
Throw.IfNull(aiFunc);
return tool.Name == aiFunc.Name;
}
private static readonly JsonElement s_defaultSchema = JsonDocument.Parse("{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false}").RootElement;
}
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="McpServerToolApprovalMode"/>.
/// </summary>
internal static class McpServerToolApprovalModeExtensions
{
/// <summary>
/// Converts a <see cref="McpServerToolApprovalMode"/> to a <see cref="HostedMcpServerToolApprovalMode"/>.
/// </summary>
/// <param name="mode">Instance of <see cref="McpServerToolApprovalMode"/></param>
internal static HostedMcpServerToolApprovalMode AsHostedMcpServerToolApprovalMode(this McpServerToolApprovalMode mode)
{
return mode switch
{
McpServerToolNeverRequireApprovalMode => HostedMcpServerToolApprovalMode.NeverRequire,
McpServerToolAlwaysRequireApprovalMode => HostedMcpServerToolApprovalMode.AlwaysRequire,
McpServerToolRequireSpecificApprovalMode specificMode =>
HostedMcpServerToolApprovalMode.RequireSpecific(
specificMode?.AlwaysRequireApprovalToolNames?.LiteralValue ?? [],
specificMode?.NeverRequireApprovalToolNames?.LiteralValue ?? []
),
_ => HostedMcpServerToolApprovalMode.AlwaysRequire,
};
}
}
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="McpServerTool"/>.
/// </summary>
internal static class McpServerToolExtensions
{
/// <summary>
/// Creates a <see cref="HostedMcpServerTool"/> from a <see cref="McpServerTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="McpServerTool"/></param>
internal static HostedMcpServerTool CreateHostedMcpTool(this McpServerTool tool)
{
Throw.IfNull(tool);
Throw.IfNull(tool.ServerName?.LiteralValue);
Throw.IfNull(tool.Connection);
var connection = tool.Connection as AnonymousConnection ?? throw new ArgumentException("Only AnonymousConnection is supported for MCP Server Tool connections.", nameof(tool));
var serverUrl = connection.Endpoint?.LiteralValue;
Throw.IfNullOrEmpty(serverUrl, nameof(connection.Endpoint));
return new HostedMcpServerTool(tool.ServerName.LiteralValue, serverUrl)
{
ServerDescription = tool.ServerDescription?.LiteralValue,
AllowedTools = tool.AllowedTools?.LiteralValue,
ApprovalMode = tool.ApprovalMode?.AsHostedMcpServerToolApprovalMode(),
};
}
}
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="ModelOptions"/>.
/// </summary>
internal static class ModelOptionsExtensions
{
/// <summary>
/// Converts the 'chatToolMode' property from a <see cref="ModelOptions"/> to a <see cref="ChatToolMode"/>.
/// </summary>
/// <param name="modelOptions">Instance of <see cref="ModelOptions"/></param>
internal static ChatToolMode? AsChatToolMode(this ModelOptions modelOptions)
{
Throw.IfNull(modelOptions);
var mode = modelOptions.ExtensionData?.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("chatToolMode"))?.Value;
if (mode is null)
{
return null;
}
return mode switch
{
"auto" => ChatToolMode.Auto,
"none" => ChatToolMode.None,
"require_any" => ChatToolMode.RequireAny,
_ => ChatToolMode.RequireSpecific(mode),
};
}
/// <summary>
/// Retrieves the 'additional_properties' property from a <see cref="ModelOptions"/>.
/// </summary>
/// <param name="modelOptions">Instance of <see cref="ModelOptions"/></param>
/// <param name="excludedProperties">List of properties which should not be included in additional properties.</param>
internal static AdditionalPropertiesDictionary? GetAdditionalProperties(this ModelOptions modelOptions, string[] excludedProperties)
{
Throw.IfNull(modelOptions);
var options = modelOptions.ExtensionData;
if (options is null || options.Properties.Count == 0)
{
return null;
}
var additionalProperties = options.Properties
.Where(kvp => !excludedProperties.Contains(kvp.Key))
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToObject());
if (additionalProperties is null || additionalProperties.Count == 0)
{
return null;
}
return new AdditionalPropertiesDictionary(additionalProperties);
}
}
@@ -0,0 +1,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;
/// <summary>
/// Extension methods for <see cref="GptComponentMetadata"/>.
/// </summary>
public static class PromptAgentExtensions
{
/// <summary>
/// Retrieves the 'options' property from a <see cref="GptComponentMetadata"/> as a <see cref="ChatOptions"/> instance.
/// </summary>
/// <param name="promptAgent">Instance of <see cref="GptComponentMetadata"/></param>
/// <param name="functions">Instance of <see cref="IList{AIFunction}"/></param>
public static ChatOptions? GetChatOptions(this GptComponentMetadata promptAgent, IList<AIFunction>? functions)
{
Throw.IfNull(promptAgent);
var outputSchema = promptAgent.OutputType;
var modelOptions = promptAgent.Model?.Options;
var tools = promptAgent.GetAITools(functions);
if (modelOptions is null && tools is null)
{
return null;
}
return new ChatOptions()
{
Instructions = promptAgent.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),
};
}
/// <summary>
/// Retrieves the 'tools' property from a <see cref="GptComponentMetadata"/>.
/// </summary>
/// <param name="promptAgent">Instance of <see cref="GptComponentMetadata"/></param>
/// <param name="functions">Instance of <see cref="IList{AIFunction}"/></param>
internal static List<AITool>? GetAITools(this GptComponentMetadata promptAgent, IList<AIFunction>? functions)
{
return promptAgent.Tools.Select(tool =>
{
return tool switch
{
CodeInterpreterTool => ((CodeInterpreterTool)tool).AsCodeInterpreterTool(),
InvokeClientTaskAction => ((InvokeClientTaskAction)tool).CreateOrGetAITool(functions),
McpServerTool => ((McpServerTool)tool).CreateHostedMcpTool(),
FileSearchTool => ((FileSearchTool)tool).CreateFileSearchTool(),
WebSearchTool => ((WebSearchTool)tool).CreateWebSearchTool(),
_ => throw new NotSupportedException($"Unable to create tool definition because of unsupported tool type: {tool.Kind}, supported tool types are: {string.Join(",", s_validToolKinds)}"),
};
}).ToList() ?? [];
}
#region private
private const string CodeInterpreterKind = "codeInterpreter";
private const string FileSearchKind = "fileSearch";
private const string FunctionKind = "function";
private const string WebSearchKind = "webSearch";
private const string McpKind = "mcp";
private static readonly string[] s_validToolKinds =
[
CodeInterpreterKind,
FileSearchKind,
FunctionKind,
WebSearchKind,
McpKind
];
private static readonly string[] s_chatOptionProperties =
[
"allowMultipleToolCalls",
"conversationId",
"chatToolMode",
"frequencyPenalty",
"additionalInstructions",
"maxOutputTokens",
"modelId",
"presencePenalty",
"responseFormat",
"seed",
"stopSequences",
"temperature",
"topK",
"topP",
"toolMode",
"tools",
];
#endregion
}
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Text.Json;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="PropertyInfo"/>.
/// </summary>
public static class PropertyInfoExtensions
{
/// <summary>
/// Creates a <see cref="Dictionary{TKey, TValue}"/> of <see cref="string"/> and <see cref="object"/>
/// from an <see cref="IReadOnlyDictionary{TKey, TValue}"/> of <see cref="string"/> and <see cref="PropertyInfo"/>.
/// </summary>
/// <param name="properties">A read-only dictionary of property names and their corresponding <see cref="PropertyInfo"/> objects.</param>
public static Dictionary<string, object> AsObjectDictionary(this IReadOnlyDictionary<string, PropertyInfo> properties)
{
var result = new Dictionary<string, object>();
foreach (var property in properties)
{
result[property.Key] = BuildPropertySchema(property.Value);
}
return result;
}
#region private
private static Dictionary<string, object> BuildPropertySchema(PropertyInfo propertyInfo)
{
var propertySchema = new Dictionary<string, object>();
// Map the DataType to JSON schema type and add type-specific properties
switch (propertyInfo.Type)
{
case StringDataType:
propertySchema["type"] = "string";
break;
case NumberDataType:
propertySchema["type"] = "number";
break;
case BooleanDataType:
propertySchema["type"] = "boolean";
break;
case DateTimeDataType:
propertySchema["type"] = "string";
propertySchema["format"] = "date-time";
break;
case DateDataType:
propertySchema["type"] = "string";
propertySchema["format"] = "date";
break;
case TimeDataType:
propertySchema["type"] = "string";
propertySchema["format"] = "time";
break;
case RecordDataType nestedRecordType:
#pragma warning disable IL2026, IL3050
// For nested records, recursively build the schema
var nestedSchema = nestedRecordType.GetSchema();
var nestedJson = JsonSerializer.Serialize(nestedSchema, ElementSerializer.CreateOptions());
var nestedDict = JsonSerializer.Deserialize<Dictionary<string, object>>(nestedJson, ElementSerializer.CreateOptions());
#pragma warning restore IL2026, IL3050
if (nestedDict != null)
{
return nestedDict;
}
propertySchema["type"] = "object";
break;
case TableDataType tableType:
propertySchema["type"] = "array";
// TableDataType has Properties like RecordDataType
propertySchema["items"] = new Dictionary<string, object>
{
["type"] = "object",
["properties"] = AsObjectDictionary(tableType.Properties),
["additionalProperties"] = false
};
break;
default:
propertySchema["type"] = "string";
break;
}
// Add description if available
if (!string.IsNullOrEmpty(propertyInfo.Description))
{
propertySchema["description"] = propertyInfo.Description;
}
return propertySchema;
}
#endregion
}
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="RecordDataType"/>.
/// </summary>
public static class RecordDataTypeExtensions
{
/// <summary>
/// Creates a <see cref="ChatResponseFormat"/> from a <see cref="RecordDataType"/>.
/// </summary>
/// <param name="recordDataType">Instance of <see cref="RecordDataType"/></param>
internal static ChatResponseFormat? AsChatResponseFormat(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
if (recordDataType.Properties.Count == 0)
{
return null;
}
// TODO: Consider adding schemaName and schemaDescription parameters to this method.
return ChatResponseFormat.ForJsonSchema(
schema: recordDataType.GetSchema(),
schemaName: recordDataType.GetSchemaName(),
schemaDescription: recordDataType.GetSchemaDescription());
}
/// <summary>
/// Converts a <see cref="RecordDataType"/> to a <see cref="JsonElement"/>.
/// </summary>
/// <param name="recordDataType">Instance of <see cref="RecordDataType"/></param>
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
public static JsonElement GetSchema(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
var schemaObject = new Dictionary<string, object>
{
["type"] = "object",
["properties"] = recordDataType.Properties.AsObjectDictionary(),
["additionalProperties"] = false
};
var json = JsonSerializer.Serialize(schemaObject, ElementSerializer.CreateOptions());
return JsonSerializer.Deserialize<JsonElement>(json);
}
#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
/// <summary>
/// Retrieves the 'schemaName' property from a <see cref="RecordDataType"/>.
/// </summary>
internal static string? GetSchemaName(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
return recordDataType.ExtensionData?.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("schemaName"))?.Value;
}
/// <summary>
/// Retrieves the 'schemaDescription' property from a <see cref="RecordDataType"/>.
/// </summary>
internal static string? GetSchemaDescription(this RecordDataType recordDataType)
{
Throw.IfNull(recordDataType);
return recordDataType.ExtensionData?.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("schemaDescription"))?.Value;
}
}
@@ -0,0 +1,107 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="RecordDataValue"/>.
/// </summary>
public static class RecordDataValueExtensions
{
/// <summary>
/// Retrieves a 'number' property from a <see cref="RecordDataValue"/>
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
/// <param name="propertyPath">Path of the property to retrieve</param>
public static decimal? GetNumber(this RecordDataValue recordData, string propertyPath)
{
Throw.IfNull(recordData);
var numberValue = recordData.GetPropertyOrNull<NumberDataValue>(InitializablePropertyPath.Create(propertyPath));
return numberValue?.Value;
}
/// <summary>
/// Retrieves a nullable boolean value from the specified property path within the given record data.
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
/// <param name="propertyPath">Path of the property to retrieve</param>
public static bool? GetBoolean(this RecordDataValue recordData, string propertyPath)
{
Throw.IfNull(recordData);
var booleanValue = recordData.GetPropertyOrNull<BooleanDataValue>(InitializablePropertyPath.Create(propertyPath));
return booleanValue?.Value;
}
/// <summary>
/// Converts a <see cref="RecordDataValue"/> to a <see cref="IReadOnlyDictionary{TKey, TValue}"/>.
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
public static IReadOnlyDictionary<string, string> ToDictionary(this RecordDataValue recordData)
{
Throw.IfNull(recordData);
return recordData.Properties.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty
);
}
/// <summary>
/// Retrieves the 'schema' property from a <see cref="RecordDataValue"/>.
/// </summary>
/// <param name="recordData">Instance of <see cref="RecordDataValue"/></param>
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
public static JsonElement? GetSchema(this RecordDataValue recordData)
{
Throw.IfNull(recordData);
try
{
var schemaStr = recordData.GetPropertyOrNull<StringDataValue>(InitializablePropertyPath.Create("json_schema.schema"));
if (schemaStr?.Value is not null)
{
return JsonSerializer.Deserialize<JsonElement>(schemaStr.Value);
}
}
catch (InvalidCastException)
{
// Ignore and try next
}
var responseFormRec = recordData.GetPropertyOrNull<RecordDataValue>(InitializablePropertyPath.Create("json_schema.schema"));
if (responseFormRec is not null)
{
var json = JsonSerializer.Serialize(responseFormRec, ElementSerializer.CreateOptions());
return JsonSerializer.Deserialize<JsonElement>(json);
}
return null;
}
#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
internal static object? ToObject(this DataValue? value)
{
if (value is null)
{
return null;
}
return value switch
{
StringDataValue s => s.Value,
NumberDataValue n => n.Value,
BooleanDataValue b => b.Value,
TableDataValue t => t.Values.Select(v => v.ToObject()).ToList(),
RecordDataValue r => r.Properties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToObject()),
_ => throw new NotSupportedException($"Unsupported DataValue type: {value.GetType().FullName}"),
};
}
}
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Bot.ObjectModel;
/// <summary>
/// Extension methods for <see cref="WebSearchTool"/>.
/// </summary>
internal static class WebSearchToolExtensions
{
/// <summary>
/// Create a <see cref="HostedWebSearchTool"/> from a <see cref="WebSearchTool"/>.
/// </summary>
/// <param name="tool">Instance of <see cref="WebSearchTool"/></param>
internal static HostedWebSearchTool CreateWebSearchTool(this WebSearchTool tool)
{
Throw.IfNull(tool);
return new HostedWebSearchTool();
}
}
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
/// <summary>
/// Extension methods for <see cref="AgentFactory"/> to support YAML based agent definitions.
/// </summary>
public static class YamlAgentFactoryExtensions
{
/// <summary>
/// Create a <see cref="AIAgent"/> from the given agent YAML.
/// </summary>
/// <param name="agentFactory"><see cref="AgentFactory"/> which will be used to create the agent.</param>
/// <param name="agentYaml">Text string containing the YAML representation of an <see cref="AIAgent" />.</param>
/// <param name="cancellationToken">Optional cancellation token</param>
[RequiresDynamicCode("Calls YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()")]
public static Task<AIAgent> CreateFromYamlAsync(this AgentFactory agentFactory, string agentYaml, CancellationToken cancellationToken = default)
{
Throw.IfNull(agentFactory);
Throw.IfNullOrEmpty(agentYaml);
var agentDefinition = AgentBotElementYaml.FromYaml(agentYaml);
return agentFactory.CreateAsync(
agentDefinition,
cancellationToken);
}
}
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<VersionSuffix>preview</VersionSuffix>
<NoWarn>$(NoWarn);MEAI001</NoWarn>
</PropertyGroup>
<PropertyGroup>
<InjectSharedThrow>true</InjectSharedThrow>
<InjectDiagnosticClassesOnLegacy>true</InjectDiagnosticClassesOnLegacy>
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
</ItemGroup>
<PropertyGroup>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework Declarative</Title>
<Description>Provides Microsoft Agent Framework support for declarative agents.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
<PackageReference Include="Microsoft.PowerFx.Interpreter" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.Declarative.UnitTests" />
</ItemGroup>
</Project>
@@ -28,6 +28,8 @@ public static class AGUIEndpointRouteBuilderExtensions
/// <param name="pattern">The URL pattern for the endpoint.</param>
/// <param name="aiAgent">The agent instance.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for the mapped endpoint.</returns>
[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,
@@ -35,6 +35,7 @@
<InternalsVisibleTo Include="Microsoft.Agents.AI.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Agents.AI.Hosting.UnitTests"/>
<InternalsVisibleTo Include="Microsoft.Agents.AI.Declarative.UnitTests" />
</ItemGroup>
</Project>
@@ -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; }
}
@@ -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;
/// <summary>
/// Tests for declarative agents created using <see cref="AggregatorAgentFactory"/>.
/// </summary>
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);
}
}
@@ -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;
/// <summary>
/// Base class for integration tests.
/// </summary>
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<AzureAIConfiguration>();
Assert.NotNull(this._foundryConfiguration);
return this._foundryConfiguration;
}
}
internal OpenAIConfiguration OpenAIConfiguration
{
get
{
this._openAIConfiguration ??= this.Configuration.GetSection("OpenAI").Get<OpenAIConfiguration>();
Assert.NotNull(this._openAIConfiguration);
return this._openAIConfiguration;
}
}
internal FoundryProjectConfiguration FoundryProjectConfiguration
{
get
{
this._foundryProjectConfiguration ??= this.Configuration.GetSection("FoundryProject").Get<FoundryProjectConfiguration>();
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();
}
@@ -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;
/// <summary>
/// Tests for declarative agents created using <see cref="ChatClientAgentFactory"/>.
/// </summary>
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();
}
}
@@ -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;
/// <summary>
/// Tests for declarative agents created using <see cref="FoundryPersistentAgentFactory"/>.
/// </summary>
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);
}
}
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<InjectSharedIntegrationTestCode>True</InjectSharedIntegrationTestCode>
<InjectSharedThrow>true</InjectSharedThrow>
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<InjectSharedIntegrationTestCode>true</InjectSharedIntegrationTestCode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Declarative.AzureAI\Microsoft.Agents.AI.Declarative.AzureAI.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Declarative\Microsoft.Agents.AI.Declarative.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -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;
/// <summary>
/// Tests for declarative agents created using <see cref="AggregatorAgentFactory"/>.
/// </summary>
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);
}
}
@@ -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<string> _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>(TState state) where TState : notnull
{
this._scopes.Push($"{state}");
return new LoggerScope(() => this._scopes.Pop());
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> 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;
}
}
}
}
@@ -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;
/// <summary>
/// Unit tests for <see cref="AgentBotElementYaml"/>
/// </summary>
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<ApiKeyConnection>(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<RemoteConnection>(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<string, string?>
{
["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<ApiKeyConnection>(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);
}
/// <summary>
/// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
/// </summary>
[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; }
}
}
@@ -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;
/// <summary>
/// Unit tests for <see cref="ChatClientAgentFactory"/>.
/// </summary>
public sealed class ChatClientAgentFactoryTests
{
private readonly Mock<IChatClient> _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<ChatClientAgent>(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<ChatClientAgent>(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<ChatClientAgent>(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<ChatClientAgent>(agent);
var chatClientAgent = agent as ChatClientAgent;
Assert.NotNull(chatClientAgent?.ChatOptions?.Tools);
var tools = chatClientAgent?.ChatOptions?.Tools;
Assert.Equal(5, tools?.Count);
}
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
<NoWarn>$(NoWarn);IDE1006</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Declarative\Microsoft.Agents.AI.Declarative.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bot.ObjectModel" />
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
</ItemGroup>
</Project>
@@ -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);
}
}
@@ -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"
}
}
}