// 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), } }; }