Merge remote-tracking branch 'upstream/main' into feature-declarative-agents-dotnet

This commit is contained in:
markwallace-microsoft
2025-11-24 15:15:36 +00:00
963 changed files with 54989 additions and 3751 deletions
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -32,7 +30,7 @@
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\workflow-samples\ConfirmInput.yaml">
<None Include="ConfirmInput.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
@@ -0,0 +1,61 @@
#
# This workflow demonstrates how to use the Question action
# to request user input and confirm it matches the original input.
#
# Note: This workflow doesn't make use of any agents.
#
kind: Workflow
trigger:
kind: OnConversationStart
id: workflow_demo
actions:
# Capture original input
- kind: SetVariable
id: set_project
variable: Local.OriginalInput
value: =System.LastMessage.Text
# Request input from user
- kind: Question
id: question_confirm
alwaysPrompt: false
autoSend: false
property: Local.ConfirmedInput
prompt:
kind: Message
text:
- "CONFIRM:"
entity:
kind: StringPrebuiltEntity
# Confirm input
- kind: ConditionGroup
id: check_completion
conditions:
# Didn't match
- condition: =Local.OriginalInput <> Local.ConfirmedInput
id: check_confirm
actions:
- kind: SendActivity
id: sendActivity_mismatch
activity: |-
"{Local.ConfirmedInput}" does not match the original input of "{Local.OriginalInput}". Please try again.
- kind: GotoAction
id: goto_again
actionId: question_confirm
# Confirmed
elseActions:
- kind: SendActivity
id: sendActivity_confirmed
activity: |-
You entered:
{Local.OriginalInput}
Confirmed input:
{Local.ConfirmedInput}
@@ -11,7 +11,7 @@ namespace Demo.Workflows.Declarative.ConfirmInput;
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information the configuration required to run this sample.
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
<InjectSharedFoundryAgents>true</InjectSharedFoundryAgents>
<InjectSharedWorkflowsExecution>true</InjectSharedWorkflowsExecution>
<InjectSharedWorkflowsSettings>true</InjectSharedWorkflowsSettings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Logging" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative\Microsoft.Agents.AI.Workflows.Declarative.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative.AzureAI\Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\workflow-samples\CustomerSupport.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,441 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
using Shared.Foundry;
using Shared.Workflows;
namespace Demo.Workflows.Declarative.CustomerSupport;
/// <summary>
/// This workflow demonstrates using multiple agents to provide automated
/// troubleshooting steps to resolve common issues with escalation options.
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
public static async Task Main(string[] args)
{
// Initialize configuration
IConfiguration configuration = Application.InitializeConfig();
Uri foundryEndpoint = new(configuration.GetValue(Application.Settings.FoundryEndpoint));
// Create the ticketing plugin (mock functionality)
TicketingPlugin plugin = new();
// Ensure sample agents exist in Foundry.
await CreateAgentsAsync(foundryEndpoint, configuration, plugin);
// Get input from command line or console
string workflowInput = Application.GetInput(args);
// Create the workflow factory. This class demonstrates how to initialize a
// declarative workflow from a YAML file. Once the workflow is created, it
// can be executed just like any regular workflow.
WorkflowFactory workflowFactory =
new("CustomerSupport.yaml", foundryEndpoint)
{
Functions =
[
AIFunctionFactory.Create(plugin.CreateTicket),
AIFunctionFactory.Create(plugin.GetTicket),
AIFunctionFactory.Create(plugin.ResolveTicket),
AIFunctionFactory.Create(plugin.SendNotification),
]
};
// Execute the workflow: The WorkflowRunner demonstrates how to execute
// a workflow, handle the workflow events, and providing external input.
// This also includes the ability to checkpoint workflow state and how to
// resume execution.
WorkflowRunner runner = new();
await runner.ExecuteAsync(workflowFactory.CreateWorkflow, workflowInput);
}
private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration, TicketingPlugin plugin)
{
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
await aiProjectClient.CreateAgentAsync(
agentName: "SelfServiceAgent",
agentDefinition: DefineSelfServiceAgent(configuration),
agentDescription: "Service agent for CustomerSupport workflow");
await aiProjectClient.CreateAgentAsync(
agentName: "TicketingAgent",
agentDefinition: DefineTicketingAgent(configuration, plugin),
agentDescription: "Ticketing agent for CustomerSupport workflow");
await aiProjectClient.CreateAgentAsync(
agentName: "TicketRoutingAgent",
agentDefinition: DefineTicketRoutingAgent(configuration, plugin),
agentDescription: "Routing agent for CustomerSupport workflow");
await aiProjectClient.CreateAgentAsync(
agentName: "WindowsSupportAgent",
agentDefinition: DefineWindowsSupportAgent(configuration, plugin),
agentDescription: "Windows support agent for CustomerSupport workflow");
await aiProjectClient.CreateAgentAsync(
agentName: "TicketResolutionAgent",
agentDefinition: DefineResolutionAgent(configuration, plugin),
agentDescription: "Resolution agent for CustomerSupport workflow");
await aiProjectClient.CreateAgentAsync(
agentName: "TicketEscalationAgent",
agentDefinition: TicketEscalationAgent(configuration, plugin),
agentDescription: "Escalate agent for human support");
}
private static PromptAgentDefinition DefineSelfServiceAgent(IConfiguration configuration) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
Use your knowledge to work with the user to provide the best possible troubleshooting steps.
- If the user confirms that the issue is resolved, then the issue is resolved.
- If the user reports that the issue persists, then escalate.
""",
TextOptions =
new ResponseTextOptions
{
TextFormat =
ResponseTextFormat.CreateJsonSchemaFormat(
"TaskEvaluation",
BinaryData.FromString(
"""
{
"type": "object",
"properties": {
"IsResolved": {
"type": "boolean",
"description": "True if the user issue/ask has been resolved."
},
"NeedsTicket": {
"type": "boolean",
"description": "True if the user issue/ask requires that a ticket be filed."
},
"IssueDescription": {
"type": "string",
"description": "A concise description of the issue."
},
"AttemptedResolutionSteps": {
"type": "string",
"description": "An outline of the steps taken to attempt resolution."
}
},
"required": ["IsResolved", "NeedsTicket", "IssueDescription", "AttemptedResolutionSteps"],
"additionalProperties": false
}
"""),
jsonSchemaFormatDescription: null,
jsonSchemaIsStrict: true),
}
};
private static PromptAgentDefinition DefineTicketingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
Always create a ticket in Azure DevOps using the available tools.
Include the following information in the TicketSummary.
- Issue description: {{IssueDescription}}
- Attempted resolution steps: {{AttemptedResolutionSteps}}
After creating the ticket, provide the user with the ticket ID.
""",
Tools =
{
AIFunctionFactory.Create(plugin.CreateTicket).AsOpenAIResponseTool()
},
StructuredInputs =
{
["IssueDescription"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "A concise description of the issue.",
},
["AttemptedResolutionSteps"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "An outline of the steps taken to attempt resolution.",
}
},
TextOptions =
new ResponseTextOptions
{
TextFormat =
ResponseTextFormat.CreateJsonSchemaFormat(
"TaskEvaluation",
BinaryData.FromString(
"""
{
"type": "object",
"properties": {
"TicketId": {
"type": "string",
"description": "The identifier of the ticket created in response to the user issue."
},
"TicketSummary": {
"type": "string",
"description": "The summary of the ticket created in response to the user issue."
}
},
"required": ["TicketId", "TicketSummary"],
"additionalProperties": false
}
"""),
jsonSchemaFormatDescription: null,
jsonSchemaIsStrict: true),
}
};
private static PromptAgentDefinition DefineTicketRoutingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
Determine how to route the given issue to the appropriate support team.
Choose from the available teams and their functions:
- Windows Activation Support: Windows license activation issues
- Windows Support: Windows related issues
- Azure Support: Azure related issues
- Network Support: Network related issues
- Hardware Support: Hardware related issues
- Microsoft Office Support: Microsoft Office related issues
- General Support: General issues not related to the above categories
""",
Tools =
{
AIFunctionFactory.Create(plugin.GetTicket).AsOpenAIResponseTool(),
},
TextOptions =
new ResponseTextOptions
{
TextFormat =
ResponseTextFormat.CreateJsonSchemaFormat(
"TaskEvaluation",
BinaryData.FromString(
"""
{
"type": "object",
"properties": {
"TeamName": {
"type": "string",
"description": "The name of the team to route the issue"
}
},
"required": ["TeamName"],
"additionalProperties": false
}
"""),
jsonSchemaFormatDescription: null,
jsonSchemaIsStrict: true),
}
};
private static PromptAgentDefinition DefineWindowsSupportAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
Use your knowledge to work with the user to provide the best possible troubleshooting steps
for issues related to Windows operating system.
- Utilize the "Attempted Resolutions Steps" as a starting point for your troubleshooting.
- Never escalate without troubleshooting with the user.
- If the user confirms that the issue is resolved, then the issue is resolved.
- If the user reports that the issue persists, then escalate.
Issue: {{IssueDescription}}
Attempted Resolution Steps: {{AttemptedResolutionSteps}}
""",
StructuredInputs =
{
["IssueDescription"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "A concise description of the issue.",
},
["AttemptedResolutionSteps"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "An outline of the steps taken to attempt resolution.",
}
},
Tools =
{
AIFunctionFactory.Create(plugin.GetTicket).AsOpenAIResponseTool(),
},
TextOptions =
new ResponseTextOptions
{
TextFormat =
ResponseTextFormat.CreateJsonSchemaFormat(
"TaskEvaluation",
BinaryData.FromString(
"""
{
"type": "object",
"properties": {
"IsResolved": {
"type": "boolean",
"description": "True if the user issue/ask has been resolved."
},
"NeedsEscalation": {
"type": "boolean",
"description": "True resolution could not be achieved and the issue/ask requires escalation."
},
"ResolutionSummary": {
"type": "string",
"description": "The summary of the steps that led to resolution."
}
},
"required": ["IsResolved", "NeedsEscalation", "ResolutionSummary"],
"additionalProperties": false
}
"""),
jsonSchemaFormatDescription: null,
jsonSchemaIsStrict: true),
}
};
private static PromptAgentDefinition DefineResolutionAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
Resolve the following ticket in Azure DevOps.
Always include the resolution details.
- Ticket ID: #{{TicketId}}
- Resolution Summary: {{ResolutionSummary}}
""",
Tools =
{
AIFunctionFactory.Create(plugin.ResolveTicket).AsOpenAIResponseTool(),
},
StructuredInputs =
{
["TicketId"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "The identifier of the ticket being resolved.",
},
["ResolutionSummary"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "The steps taken to resolve the issue.",
}
}
};
private static PromptAgentDefinition TicketEscalationAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
You escalate the provided issue to human support team by sending an email if the issue is not resolved.
Here are some additional details that might help:
- TicketId : {{TicketId}}
- IssueDescription : {{IssueDescription}}
- AttemptedResolutionSteps : {{AttemptedResolutionSteps}}
Before escalating, gather the user's email address for follow-up.
If not known, ask the user for their email address so that the support team can reach them when needed.
When sending the email, include the following details:
- To: support@contoso.com
- Cc: user's email address
- Subject of the email: "Support Ticket - {TicketId} - [Compact Issue Description]"
- Body:
- Issue description
- Attempted resolution steps
- User's email address
- Any other relevant information from the conversation history
Assure the user that their issue will be resolved and provide them with a ticket ID for reference.
""",
Tools =
{
AIFunctionFactory.Create(plugin.GetTicket).AsOpenAIResponseTool(),
AIFunctionFactory.Create(plugin.SendNotification).AsOpenAIResponseTool(),
},
StructuredInputs =
{
["TicketId"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "The identifier of the ticket being escalated.",
},
["IssueDescription"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "A concise description of the issue.",
},
["ResolutionSummary"] =
new StructuredInputDefinition
{
IsRequired = false,
DefaultValue = BinaryData.FromString(@"""unknown"""),
Description = "An outline of the steps taken to attempt resolution.",
}
},
TextOptions =
new ResponseTextOptions
{
TextFormat =
ResponseTextFormat.CreateJsonSchemaFormat(
"TaskEvaluation",
BinaryData.FromString(
"""
{
"type": "object",
"properties": {
"IsComplete": {
"type": "boolean",
"description": "Has the email been sent and no more user input is required."
},
"UserMessage": {
"type": "string",
"description": "A natural language message to the user."
}
},
"required": ["IsComplete", "UserMessage"],
"additionalProperties": false
}
"""),
jsonSchemaFormatDescription: null,
jsonSchemaIsStrict: true),
}
};
}
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
namespace Demo.Workflows.Declarative.CustomerSupport;
internal sealed class TicketingPlugin
{
private readonly Dictionary<string, TicketItem> _ticketStore = [];
[Description("Retrieve a ticket by identifier from Azure DevOps.")]
public TicketItem? GetTicket(string id)
{
Trace(nameof(GetTicket));
this._ticketStore.TryGetValue(id, out TicketItem? ticket);
return ticket;
}
[Description("Create a ticket in Azure DevOps and return its identifier.")]
public string CreateTicket(string subject, string description, string notes)
{
Trace(nameof(CreateTicket));
TicketItem ticket = new()
{
Subject = subject,
Description = description,
Notes = notes,
Id = Guid.NewGuid().ToString("N"),
};
this._ticketStore[ticket.Id] = ticket;
return ticket.Id;
}
[Description("Resolve an existing ticket in Azure DevOps given its identifier.")]
public void ResolveTicket(string id, string resolutionSummary)
{
Trace(nameof(ResolveTicket));
if (this._ticketStore.TryGetValue(id, out TicketItem? ticket))
{
ticket.Status = TicketStatus.Resolved;
}
}
[Description("Send an email notification to escalate ticket engagement.")]
public void SendNotification(string id, string email, string cc, string body)
{
Trace(nameof(SendNotification));
}
private static void Trace(string functionName)
{
Console.ForegroundColor = ConsoleColor.DarkMagenta;
try
{
Console.WriteLine($"\nFUNCTION: {functionName}");
}
finally
{
Console.ResetColor();
}
}
public enum TicketStatus
{
Open,
InProgress,
Resolved,
Closed,
}
public sealed class TicketItem
{
public TicketStatus Status { get; set; } = TicketStatus.Open;
public string Subject { get; init; } = string.Empty;
public string Id { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public string Notes { get; init; } = string.Empty;
}
}
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -15,7 +16,7 @@ namespace Demo.Workflows.Declarative.DeepResearch;
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information the configuration required to run this sample.
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
@@ -46,39 +47,63 @@ internal sealed class Program
private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration)
{
AgentClient agentClient = new(foundryEndpoint, new AzureCliCredential());
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "ResearchAgent",
agentDefinition: DefineResearchAgent(configuration),
agentDescription: "Planner agent for DeepResearch workflow");
<<<<<<< HEAD
await agentClient.CreateAgentAsync(
=======
await aiProjectClient.CreateAgentAsync(
>>>>>>> upstream/main
agentName: "PlannerAgent",
agentDefinition: DefinePlannerAgent(configuration),
agentDescription: "Planner agent for DeepResearch workflow");
<<<<<<< HEAD
await agentClient.CreateAgentAsync(
=======
await aiProjectClient.CreateAgentAsync(
>>>>>>> upstream/main
agentName: "ManagerAgent",
agentDefinition: DefineManagerAgent(configuration),
agentDescription: "Manager agent for DeepResearch workflow");
<<<<<<< HEAD
await agentClient.CreateAgentAsync(
=======
await aiProjectClient.CreateAgentAsync(
>>>>>>> upstream/main
agentName: "SummaryAgent",
agentDefinition: DefineSummaryAgent(configuration),
agentDescription: "Summary agent for DeepResearch workflow");
<<<<<<< HEAD
await agentClient.CreateAgentAsync(
=======
await aiProjectClient.CreateAgentAsync(
>>>>>>> upstream/main
agentName: "KnowledgeAgent",
agentDefinition: DefineKnowledgeAgent(configuration),
agentDescription: "Research agent for DeepResearch workflow");
<<<<<<< HEAD
await agentClient.CreateAgentAsync(
=======
await aiProjectClient.CreateAgentAsync(
>>>>>>> upstream/main
agentName: "CoderAgent",
agentDefinition: DefineCoderAgent(configuration),
agentDescription: "Coder agent for DeepResearch workflow");
<<<<<<< HEAD
await agentClient.CreateAgentAsync(
=======
await aiProjectClient.CreateAgentAsync(
>>>>>>> upstream/main
agentName: "WeatherAgent",
agentDefinition: DefineWeatherAgent(configuration),
agentDescription: "Weather agent for DeepResearch workflow");
@@ -271,10 +296,17 @@ internal sealed class Program
Tools =
{
AgentTool.CreateOpenApiTool(
<<<<<<< HEAD
new OpenApiFunctionDefinition(
"weather-forecast",
BinaryData.FromString(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "wttr.json"))),
new OpenApiAnonymousAuthDetails()))
=======
new OpenAPIFunctionDefinition(
"weather-forecast",
BinaryData.FromString(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "wttr.json"))),
new OpenAPIAnonymousAuthenticationDetails()))
>>>>>>> upstream/main
}
};
}
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net9.0</TargetFrameworks>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net9.0</TargetFrameworks>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA1812</NoWarn>
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
@@ -16,7 +17,7 @@ namespace Demo.Workflows.Declarative.FunctionTools;
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information the configuration required to run this sample.
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
@@ -55,9 +56,9 @@ internal sealed class Program
private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration, AIFunction[] functions)
{
AgentClient agentClient = new(foundryEndpoint, new AzureCliCredential());
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "MenuAgent",
agentDefinition: DefineMenuAgent(configuration, functions),
agentDescription: "Provides information about the restaurant menu");
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA1812</NoWarn>
</PropertyGroup>
<PropertyGroup>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
<InjectSharedFoundryAgents>true</InjectSharedFoundryAgents>
<InjectSharedWorkflowsExecution>true</InjectSharedWorkflowsExecution>
<InjectSharedWorkflowsSettings>true</InjectSharedWorkflowsSettings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Logging" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative\Microsoft.Agents.AI.Workflows.Declarative.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative.AzureAI\Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\workflow-samples\MathChat.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,169 @@
// Copyright (c) Microsoft. All rights reserved.
// Uncomment this to enable JSON checkpointing to the local file system.
//#define CHECKPOINT_JSON
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.Workflows;
namespace Demo.DeclarativeWorkflow;
/// <summary>
/// %%% COMMENT
/// </summary>
/// <remarks>
/// <b>Configuration</b>
/// Define FOUNDRY_PROJECT_ENDPOINT as a user-secret or environment variable that
/// points to your Foundry project endpoint.
/// <b>Usage</b>
/// Provide the path to the workflow definition file as the first argument.
/// All other arguments are intepreted as a queue of inputs.
/// When no input is queued, interactive input is requested from the console.
/// </remarks>
internal sealed class Program
{
public static async Task Main(string[] args)
{
// Initialize configuration
IConfiguration configuration = Application.InitializeConfig();
Uri foundryEndpoint = new(configuration.GetValue(Application.Settings.FoundryEndpoint));
// Create the agent service client
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
// Ensure sample agents exist in Foundry.
await CreateAgentsAsync(aiProjectClient, configuration);
// Ensure workflow agent exists in Foundry.
AgentVersion agentVersion = await CreateWorkflowAsync(aiProjectClient, configuration);
string workflowInput = GetWorkflowInput(args);
AIAgent agent = aiProjectClient.GetAIAgent(agentVersion);
AgentThread thread = agent.GetNewThread();
ProjectConversation conversation =
await aiProjectClient
.GetProjectOpenAIClient()
.GetProjectConversationsClient()
.CreateProjectConversationAsync()
.ConfigureAwait(false);
Console.WriteLine($"CONVERSATION: {conversation.Id}");
ChatOptions chatOptions =
new()
{
ConversationId = conversation.Id
};
ChatClientAgentRunOptions runOptions = new(chatOptions);
IAsyncEnumerable<AgentRunResponseUpdate> agentResponseUpdates = agent.RunStreamingAsync(workflowInput, thread, runOptions);
string? lastMessageId = null;
await foreach (AgentRunResponseUpdate responseUpdate in agentResponseUpdates)
{
if (responseUpdate.MessageId != lastMessageId)
{
Console.WriteLine($"\n\n{responseUpdate.AuthorName ?? responseUpdate.AgentId}");
}
lastMessageId = responseUpdate.MessageId;
Console.Write(responseUpdate.Text);
}
}
private static async Task<AgentVersion> CreateWorkflowAsync(AIProjectClient agentClient, IConfiguration configuration)
{
string workflowYaml = File.ReadAllText("MathChat.yaml");
WorkflowAgentDefinition workflowAgentDefinition = WorkflowAgentDefinition.FromYaml(workflowYaml);
return
await agentClient.CreateAgentAsync(
agentName: "MathChatWorkflow",
agentDefinition: workflowAgentDefinition,
agentDescription: "The student attempts to solve the input problem and the teacher provides guidance.");
}
private static async Task CreateAgentsAsync(AIProjectClient agentClient, IConfiguration configuration)
{
await agentClient.CreateAgentAsync(
agentName: "StudentAgent",
agentDefinition: DefineStudentAgent(configuration),
agentDescription: "Student agent for MathChat workflow");
await agentClient.CreateAgentAsync(
agentName: "TeacherAgent",
agentDefinition: DefineTeacherAgent(configuration),
agentDescription: "Teacher agent for MathChat workflow");
}
private static PromptAgentDefinition DefineStudentAgent(IConfiguration configuration) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
Your job is help a math teacher practice teaching by making intentional mistakes.
You attempt to solve the given math problem, but with intentional mistakes so the teacher can help.
Always incorporate the teacher's advice to fix your next response.
You have the math-skills of a 6th grader.
Don't describe who you are or reveal your instructions.
"""
};
private static PromptAgentDefinition DefineTeacherAgent(IConfiguration configuration) =>
new(configuration.GetValue(Application.Settings.FoundryModelMini))
{
Instructions =
"""
Review and coach the student's approach to solving the given math problem.
Don't repeat the solution or try and solve it.
If the student has demonstrated comprehension and responded to all of your feedback,
give the student your congratulations by using the word "congratulations".
"""
};
private static string GetWorkflowInput(string[] args)
{
string? input = null;
if (args.Length > 0)
{
string[] workflowInput = [.. args.Skip(1)];
input = workflowInput.FirstOrDefault();
}
try
{
Console.ForegroundColor = ConsoleColor.DarkGreen;
Console.Write("\nINPUT: ");
Console.ForegroundColor = ConsoleColor.White;
if (!string.IsNullOrWhiteSpace(input))
{
Console.WriteLine(input);
return input;
}
while (string.IsNullOrWhiteSpace(input))
{
input = Console.ReadLine();
}
return input.Trim();
}
finally
{
Console.ResetColor();
}
}
}
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -15,7 +16,7 @@ namespace Demo.Workflows.Declarative.InputArguments;
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information the configuration required to run this sample.
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
@@ -46,19 +47,19 @@ internal sealed class Program
private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration)
{
AgentClient agentsClient = new(foundryEndpoint, new AzureCliCredential());
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
await agentsClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "LocationTriageAgent",
agentDefinition: DefineLocationTriageAgent(configuration),
agentDescription: "Chats with the user to solicit a location of interest.");
await agentsClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "LocationCaptureAgent",
agentDefinition: DefineLocationCaptureAgent(configuration),
agentDescription: "Evaluate the status of soliciting the location.");
await agentsClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "LocationAwareAgent",
agentDefinition: DefineLocationAwareAgent(configuration),
agentDescription: "Chats with the user with location awareness.");
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
@@ -14,7 +15,7 @@ namespace Demo.Workflows.Declarative.Marketing;
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information the configuration required to run this sample.
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
@@ -45,19 +46,19 @@ internal sealed class Program
private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration)
{
AgentClient agentClient = new(foundryEndpoint, new AzureCliCredential());
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "AnalystAgent",
agentDefinition: DefineAnalystAgent(configuration),
agentDescription: "Analyst agent for Marketing workflow");
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "WriterAgent",
agentDefinition: DefineWriterAgent(configuration),
agentDescription: "Writer agent for Marketing workflow");
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "EditorAgent",
agentDefinition: DefineEditorAgent(configuration),
agentDescription: "Editor agent for Marketing workflow");
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
@@ -14,7 +15,7 @@ namespace Demo.Workflows.Declarative.StudentTeacher;
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information the configuration required to run this sample.
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
@@ -45,14 +46,14 @@ internal sealed class Program
private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration)
{
AgentClient agentClient = new(foundryEndpoint, new AzureCliCredential());
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "StudentAgent",
agentDefinition: DefineStudentAgent(configuration),
agentDescription: "Student agent for MathChat workflow");
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "TeacherAgent",
agentDefinition: DefineTeacherAgent(configuration),
agentDescription: "Teacher agent for MathChat workflow");
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -15,7 +16,7 @@ namespace Demo.Workflows.Declarative.ToolApproval;
/// </summary>
/// <remarks>
/// See the README.md file in the parent folder (../README.md) for detailed
/// information the configuration required to run this sample.
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
@@ -46,9 +47,9 @@ internal sealed class Program
private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration)
{
AgentClient agentClient = new(foundryEndpoint, new AzureCliCredential());
AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
await agentClient.CreateAgentAsync(
await aiProjectClient.CreateAgentAsync(
agentName: "DocumentSearchAgent",
agentDefinition: DefineSearchAgent(configuration),
agentDescription: "Searches documents on Microsoft Learn");
@@ -2,9 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>