// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
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;
///
/// This workflow demonstrates using multiple agents to provide automated
/// troubleshooting steps to resolve common issues with escalation options.
///
///
/// See the README.md file in the parent folder (../README.md) for detailed
/// information about the configuration required to run this sample.
///
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)
{
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential());
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 DeclarativeAgentDefinition DefineSelfServiceAgent(IConfiguration configuration) =>
new(configuration.GetValue(Application.Settings.FoundryModel))
{
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 DeclarativeAgentDefinition DefineTicketingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModel))
{
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 DeclarativeAgentDefinition DefineTicketRoutingAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModel))
{
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 DeclarativeAgentDefinition DefineWindowsSupportAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModel))
{
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 DeclarativeAgentDefinition DefineResolutionAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModel))
{
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 DeclarativeAgentDefinition TicketEscalationAgent(IConfiguration configuration, TicketingPlugin plugin) =>
new(configuration.GetValue(Application.Settings.FoundryModel))
{
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),
}
};
}