chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>LongRunningTools</AssemblyName>
|
||||
<RootNamespace>LongRunningTools</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
|
||||
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
|
||||
<!--
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
|
||||
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
-->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LongRunningTools;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the input for the content generation workflow.
|
||||
/// </summary>
|
||||
public sealed class ContentGenerationInput
|
||||
{
|
||||
[JsonPropertyName("topic")]
|
||||
public string Topic { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("max_review_attempts")]
|
||||
public int MaxReviewAttempts { get; set; } = 3;
|
||||
|
||||
[JsonPropertyName("approval_timeout_hours")]
|
||||
public float ApprovalTimeoutHours { get; set; } = 72;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the content generated by the writer agent.
|
||||
/// </summary>
|
||||
public sealed class GeneratedContent
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the human feedback response.
|
||||
/// </summary>
|
||||
public sealed class HumanFeedbackResponse
|
||||
{
|
||||
[JsonPropertyName("approved")]
|
||||
public bool Approved { get; set; }
|
||||
|
||||
[JsonPropertyName("feedback")]
|
||||
public string Feedback { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using LongRunningTools;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.DurableTask;
|
||||
using Microsoft.DurableTask;
|
||||
using Microsoft.DurableTask.Client;
|
||||
using Microsoft.DurableTask.Client.AzureManaged;
|
||||
using Microsoft.DurableTask.Worker;
|
||||
using Microsoft.DurableTask.Worker.AzureManaged;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// Get the Azure OpenAI endpoint and deployment name from environment variables.
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
|
||||
|
||||
// Get DTS connection string from environment variable
|
||||
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
|
||||
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
|
||||
|
||||
// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
|
||||
string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
|
||||
// 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.
|
||||
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
|
||||
: new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||
|
||||
// Agent used by the orchestration to write content.
|
||||
const string WriterAgentName = "Writer";
|
||||
const string WriterAgentInstructions =
|
||||
"""
|
||||
You are a professional content writer who creates high-quality articles on various topics.
|
||||
You write engaging, informative, and well-structured content that follows best practices for readability and accuracy.
|
||||
""";
|
||||
|
||||
AIAgent writerAgent = client.GetChatClient(deploymentName).AsAIAgent(WriterAgentInstructions, WriterAgentName);
|
||||
|
||||
// Agent that can start content generation workflows using tools
|
||||
const string PublisherAgentName = "Publisher";
|
||||
const string PublisherAgentInstructions =
|
||||
"""
|
||||
You are a publishing agent that can manage content generation workflows.
|
||||
You have access to tools to start, monitor, and raise events for content generation workflows.
|
||||
""";
|
||||
|
||||
const string HumanFeedbackEventName = "HumanFeedback";
|
||||
|
||||
// Orchestrator function
|
||||
static async Task<object> RunOrchestratorAsync(TaskOrchestrationContext context, ContentGenerationInput input)
|
||||
{
|
||||
// Get the writer agent
|
||||
DurableAIAgent writerAgent = context.GetAgent(WriterAgentName);
|
||||
AgentSession writerSession = await writerAgent.CreateSessionAsync();
|
||||
|
||||
// Set initial status
|
||||
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");
|
||||
|
||||
// Step 1: Generate initial content
|
||||
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"Write a short article about '{input.Topic}'.",
|
||||
session: writerSession);
|
||||
GeneratedContent content = writerResponse.Result;
|
||||
|
||||
// Human-in-the-loop iteration - we set a maximum number of attempts to avoid infinite loops
|
||||
int iterationCount = 0;
|
||||
while (iterationCount++ < input.MaxReviewAttempts)
|
||||
{
|
||||
// NOTE: CustomStatus has a 16 KB UTF-16 limit in Durable Functions.
|
||||
// Only include short metadata here - the full content is passed via activity inputs/outputs.
|
||||
context.SetCustomStatus(
|
||||
new
|
||||
{
|
||||
message = "Requesting human feedback.",
|
||||
approvalTimeoutHours = input.ApprovalTimeoutHours,
|
||||
iterationCount,
|
||||
contentTitle = content.Title,
|
||||
});
|
||||
|
||||
// Step 2: Notify user to review the content
|
||||
await context.CallActivityAsync(nameof(NotifyUserForApproval), content);
|
||||
|
||||
// Step 3: Wait for human feedback with configurable timeout
|
||||
HumanFeedbackResponse humanResponse;
|
||||
try
|
||||
{
|
||||
humanResponse = await context.WaitForExternalEvent<HumanFeedbackResponse>(
|
||||
eventName: HumanFeedbackEventName,
|
||||
timeout: TimeSpan.FromHours(input.ApprovalTimeoutHours));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Timeout occurred - treat as rejection
|
||||
context.SetCustomStatus(
|
||||
new
|
||||
{
|
||||
message = $"Human approval timed out after {input.ApprovalTimeoutHours} hour(s). Treating as rejection.",
|
||||
iterationCount,
|
||||
});
|
||||
throw new TimeoutException($"Human approval timed out after {input.ApprovalTimeoutHours} hour(s).");
|
||||
}
|
||||
|
||||
if (humanResponse.Approved)
|
||||
{
|
||||
context.SetCustomStatus(new
|
||||
{
|
||||
message = "Content approved by human reviewer. Publishing content...",
|
||||
contentTitle = content.Title,
|
||||
});
|
||||
|
||||
// Step 4: Publish the approved content
|
||||
await context.CallActivityAsync(nameof(PublishContent), content);
|
||||
|
||||
context.SetCustomStatus(new
|
||||
{
|
||||
message = $"Content published successfully at {context.CurrentUtcDateTime:s}",
|
||||
humanFeedback = humanResponse,
|
||||
contentTitle = content.Title,
|
||||
});
|
||||
return new { content = content.Content };
|
||||
}
|
||||
|
||||
context.SetCustomStatus(new
|
||||
{
|
||||
message = "Content rejected by human reviewer. Incorporating feedback and regenerating...",
|
||||
humanFeedback = humanResponse,
|
||||
contentTitle = content.Title,
|
||||
});
|
||||
|
||||
// Incorporate human feedback and regenerate
|
||||
writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"""
|
||||
The content was rejected by a human reviewer. Please rewrite the article incorporating their feedback.
|
||||
|
||||
Human Feedback: {humanResponse.Feedback}
|
||||
""",
|
||||
session: writerSession);
|
||||
|
||||
content = writerResponse.Result;
|
||||
}
|
||||
|
||||
// If we reach here, it means we exhausted the maximum number of iterations
|
||||
throw new InvalidOperationException(
|
||||
$"Content could not be approved after {input.MaxReviewAttempts} iterations.");
|
||||
}
|
||||
|
||||
// Activity functions
|
||||
static void NotifyUserForApproval(TaskActivityContext context, GeneratedContent content)
|
||||
{
|
||||
// In a real implementation, this would send notifications via email, SMS, etc.
|
||||
Console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||
Console.WriteLine(
|
||||
$"""
|
||||
NOTIFICATION: Please review the following content for approval:
|
||||
Title: {content.Title}
|
||||
Content: {content.Content}
|
||||
""");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
static void PublishContent(TaskActivityContext context, GeneratedContent content)
|
||||
{
|
||||
// In a real implementation, this would publish to a CMS, website, etc.
|
||||
Console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||
Console.WriteLine(
|
||||
$"""
|
||||
PUBLISHING: Content has been published successfully.
|
||||
Title: {content.Title}
|
||||
Content: {content.Content}
|
||||
""");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
// Tools that demonstrate starting orchestrations from agent tool calls.
|
||||
[Description("Starts a content generation workflow and returns the instance ID for tracking.")]
|
||||
static string StartContentGenerationWorkflow([Description("The topic for content generation")] string topic)
|
||||
{
|
||||
const int MaxReviewAttempts = 3;
|
||||
const float ApprovalTimeoutHours = 72;
|
||||
|
||||
// Schedule the orchestration, which will start running after the tool call completes.
|
||||
string instanceId = DurableAgentContext.Current.ScheduleNewOrchestration(
|
||||
name: nameof(RunOrchestratorAsync),
|
||||
input: new ContentGenerationInput
|
||||
{
|
||||
Topic = topic,
|
||||
MaxReviewAttempts = MaxReviewAttempts,
|
||||
ApprovalTimeoutHours = ApprovalTimeoutHours
|
||||
});
|
||||
|
||||
return $"Workflow started with instance ID: {instanceId}";
|
||||
}
|
||||
|
||||
[Description("Gets the status of a workflow orchestration and returns a summary of the workflow's current status.")]
|
||||
static async Task<object> GetWorkflowStatusAsync(
|
||||
[Description("The instance ID of the workflow to check")] string instanceId,
|
||||
[Description("Whether to include detailed information")] bool includeDetails = true)
|
||||
{
|
||||
// Get the current agent context using the session-static property
|
||||
OrchestrationMetadata? status = await DurableAgentContext.Current.GetOrchestrationStatusAsync(
|
||||
instanceId,
|
||||
includeDetails);
|
||||
|
||||
if (status is null)
|
||||
{
|
||||
return new
|
||||
{
|
||||
instanceId,
|
||||
error = $"Workflow instance '{instanceId}' not found.",
|
||||
};
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
instanceId = status.InstanceId,
|
||||
createdAt = status.CreatedAt,
|
||||
executionStatus = status.RuntimeStatus,
|
||||
workflowStatus = status.SerializedCustomStatus,
|
||||
lastUpdatedAt = status.LastUpdatedAt,
|
||||
failureDetails = status.FailureDetails
|
||||
};
|
||||
}
|
||||
|
||||
[Description(
|
||||
"Raises a feedback event for the content generation workflow. If approved, the workflow will be published. " +
|
||||
"If rejected, the workflow will generate new content.")]
|
||||
static async Task SubmitHumanFeedbackAsync(
|
||||
[Description("The instance ID of the workflow to submit feedback for")] string instanceId,
|
||||
[Description("Feedback to submit")] HumanFeedbackResponse feedback)
|
||||
{
|
||||
await DurableAgentContext.Current.RaiseOrchestrationEventAsync(instanceId, HumanFeedbackEventName, feedback);
|
||||
}
|
||||
|
||||
// Configure the console app to host the AI agents.
|
||||
IHost host = Host.CreateDefaultBuilder(args)
|
||||
.ConfigureLogging(loggingBuilder => loggingBuilder.SetMinimumLevel(LogLevel.Warning))
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.ConfigureDurableAgents(
|
||||
options =>
|
||||
{
|
||||
// Add the writer agent used by the orchestration
|
||||
options.AddAIAgent(writerAgent);
|
||||
|
||||
// Define the agent that can start orchestrations from tool calls
|
||||
options.AddAIAgentFactory(PublisherAgentName, sp =>
|
||||
{
|
||||
return client.GetChatClient(deploymentName).AsAIAgent(
|
||||
instructions: PublisherAgentInstructions,
|
||||
name: PublisherAgentName,
|
||||
services: sp,
|
||||
tools: [
|
||||
AIFunctionFactory.Create(StartContentGenerationWorkflow),
|
||||
AIFunctionFactory.Create(GetWorkflowStatusAsync),
|
||||
AIFunctionFactory.Create(SubmitHumanFeedbackAsync),
|
||||
]);
|
||||
});
|
||||
},
|
||||
workerBuilder: builder =>
|
||||
{
|
||||
builder.UseDurableTaskScheduler(dtsConnectionString);
|
||||
builder.AddTasks(registry =>
|
||||
{
|
||||
registry.AddOrchestratorFunc<ContentGenerationInput>(nameof(RunOrchestratorAsync), RunOrchestratorAsync);
|
||||
registry.AddActivityFunc<GeneratedContent>(nameof(NotifyUserForApproval), NotifyUserForApproval);
|
||||
registry.AddActivityFunc<GeneratedContent>(nameof(PublishContent), PublishContent);
|
||||
});
|
||||
},
|
||||
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.StartAsync();
|
||||
|
||||
// Get the agent proxy from services
|
||||
IServiceProvider services = host.Services;
|
||||
AIAgent? agentProxy = services.GetKeyedService<AIAgent>(PublisherAgentName);
|
||||
if (agentProxy == null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine("Agent 'Publisher' not found.");
|
||||
Console.ResetColor();
|
||||
Environment.Exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Console colors for better UX
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("=== Long Running Tools Sample ===");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine("Enter a topic for the Publisher agent to write about (or 'exit' to quit):");
|
||||
Console.WriteLine();
|
||||
|
||||
// Create a session for the conversation
|
||||
AgentSession session = await agentProxy.CreateSessionAsync();
|
||||
|
||||
using CancellationTokenSource cts = new();
|
||||
Console.CancelKeyPress += (sender, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
// Read input from stdin
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.Write("You: ");
|
||||
Console.ResetColor();
|
||||
|
||||
string? input = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Run the agent
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("Publisher: ");
|
||||
Console.ResetColor();
|
||||
|
||||
try
|
||||
{
|
||||
AgentResponse agentResponse = await agentProxy.RunAsync(
|
||||
message: input,
|
||||
session: session,
|
||||
cancellationToken: cts.Token);
|
||||
|
||||
Console.WriteLine(agentResponse.Text);
|
||||
Console.WriteLine();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine($"Error: {ex.Message}");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine("(Press Enter to prompt the Publisher agent again)");
|
||||
_ = Console.ReadLine();
|
||||
}
|
||||
|
||||
await host.StopAsync();
|
||||
@@ -0,0 +1,90 @@
|
||||
# Long Running Tools Sample
|
||||
|
||||
This sample demonstrates how to use the durable agents extension to create a console app with agents that have long running tools. This sample builds on the [05_AgentOrchestration_HITL](../05_AgentOrchestration_HITL) sample by adding a publisher agent that can start and manage content generation workflows. A key difference is that the publisher agent knows the IDs of the workflows it starts, so it can check the status of the workflows and approve or reject them without being explicitly given the context (instance IDs, etc).
|
||||
|
||||
## Key Concepts Demonstrated
|
||||
|
||||
The same key concepts as the [05_AgentOrchestration_HITL](../05_AgentOrchestration_HITL) sample are demonstrated, but with the following additional concepts:
|
||||
|
||||
- **Long running tools**: Using `DurableAgentContext.Current` to start orchestrations from tool calls
|
||||
- **Multi-agent orchestration**: Agents can start and manage workflows that orchestrate other agents
|
||||
- **Human-in-the-loop (with delegation)**: The agent acts as an intermediary between the human and the workflow. The human remains in the loop, but delegates to the agent to start the workflow and approve or reject the content.
|
||||
|
||||
## Environment Setup
|
||||
|
||||
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
|
||||
|
||||
## Running the Sample
|
||||
|
||||
With the environment setup, you can run the sample:
|
||||
|
||||
```bash
|
||||
cd dotnet/samples/04-hosting/DurableAgents/ConsoleApps/06_LongRunningTools
|
||||
dotnet run --framework net10.0
|
||||
```
|
||||
|
||||
The app will prompt you for input. You can interact with the Publisher agent:
|
||||
|
||||
```text
|
||||
=== Long Running Tools Sample ===
|
||||
Enter a topic for the Publisher agent to write about (or 'exit' to quit):
|
||||
|
||||
You: Start a content generation workflow for the topic 'The Future of Artificial Intelligence'
|
||||
Publisher: The content generation workflow for the topic "The Future of Artificial Intelligence" has been successfully started, and the instance ID is **6a04276e8d824d8d941e1dc4142cc254**. If you need any further assistance or updates on the workflow, feel free to ask!
|
||||
```
|
||||
|
||||
Behind the scenes, the publisher agent will:
|
||||
|
||||
1. Start the content generation workflow via a tool call
|
||||
2. The workflow will generate initial content using the Writer agent and wait for human approval, which will be visible in the terminal
|
||||
|
||||
Once the workflow is waiting for human approval, you can send approval or rejection by prompting the publisher agent accordingly.
|
||||
|
||||
> [!NOTE]
|
||||
> You must press Enter after each message to continue the conversation. The sample is set up this way because the workflow is running in the background and may write to the console asynchronously.
|
||||
|
||||
To tell the agent to rewrite the content with feedback, you can prompt it to reject the content with feedback.
|
||||
|
||||
```text
|
||||
You: Reject the content with feedback: The article needs more technical depth and better examples.
|
||||
Publisher: The content has been successfully rejected with the feedback: "The article needs more technical depth and better examples." The workflow will now generate new content based on this feedback.
|
||||
```
|
||||
|
||||
Once you're satisfied with the content, you can approve it for publishing.
|
||||
|
||||
```text
|
||||
You: Approve the content
|
||||
Publisher: The content has been successfully approved for publishing. If you need any more assistance or have further requests, feel free to let me know!
|
||||
```
|
||||
|
||||
Once the workflow has completed, you can get the status by prompting the publisher agent to give you the status.
|
||||
|
||||
```text
|
||||
You: Get the status of the workflow you previously started
|
||||
Publisher: The status of the workflow with instance ID **6a04276e8d824d8d941e1dc4142cc254** is as follows:
|
||||
|
||||
- **Execution Status:** Completed
|
||||
- **Created At:** December 22, 2025, 23:08:13 UTC
|
||||
- **Last Updated At:** December 22, 2025, 23:09:59 UTC
|
||||
- **Workflow Status:**
|
||||
- Message: Content published successfully at December 22, 2025, 23:09:59 UTC
|
||||
- Human Feedback: Approved
|
||||
```
|
||||
|
||||
## Viewing Agent and Orchestration State
|
||||
|
||||
You can view the state of both the agent and the orchestrations it starts in the Durable Task Scheduler dashboard:
|
||||
|
||||
1. Open your browser and navigate to `http://localhost:8082`
|
||||
2. In the dashboard, you can see:
|
||||
- **Agents**: View the state of the Publisher agent, including its conversation history and tool call history
|
||||
- **Orchestrations**: View the content generation orchestration instances that were started by the agent via tool calls, including their runtime status, custom status, input, output, and execution history
|
||||
|
||||
When the publisher agent starts a workflow, the orchestration instance ID is included in the agent's response. You can use this ID to find the specific orchestration in the dashboard and inspect:
|
||||
|
||||
- The orchestration's execution progress
|
||||
- When it's waiting for human approval (visible in custom status)
|
||||
- The content generation workflow state
|
||||
- The WriterAgent state within the orchestration
|
||||
|
||||
This demonstrates how agents can manage long-running workflows and how you can monitor both the agent's state and the workflows it orchestrates.
|
||||
Reference in New Issue
Block a user