mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
38de991481
* Fix RequestInfoEvent lost when resuming workflow from checkpoint * Fix streaming run double disposal in tests and lockstep republishing before Started event is emitted. * Fix bug to remove messages after sending to avoid losing messages on send failure. * Fix declarative test harness
188 lines
8.1 KiB
C#
188 lines
8.1 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Agents.AI.Workflows.Execution;
|
|
|
|
namespace Microsoft.Agents.AI.Workflows.InProc;
|
|
|
|
/// <summary>
|
|
/// Provides an in-process implementation of the workflow execution environment for running, streaming, and
|
|
/// checkpointing workflows within the current application domain.
|
|
/// </summary>
|
|
public sealed class InProcessExecutionEnvironment : IWorkflowExecutionEnvironment
|
|
{
|
|
internal InProcessExecutionEnvironment(ExecutionMode mode, bool enableConcurrentRuns = false, CheckpointManager? checkpointManager = null)
|
|
{
|
|
this.ExecutionMode = mode;
|
|
this.EnableConcurrentRuns = enableConcurrentRuns;
|
|
|
|
this.CheckpointManager = checkpointManager;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configure a new execution environment, inheriting configuration for the current one with the specified <see cref="Workflows.CheckpointManager"/>
|
|
/// for use in checkpointing.
|
|
/// </summary>
|
|
/// <param name="checkpointManager">The CheckpointManager to use for checkpointing.</param>
|
|
/// <returns>
|
|
/// A new InProcess <see cref="IWorkflowExecutionEnvironment"/> configured for checkpointing, inheriting configuration from the current
|
|
/// environment.
|
|
/// </returns>
|
|
public InProcessExecutionEnvironment WithCheckpointing(CheckpointManager? checkpointManager)
|
|
{
|
|
return new(this.ExecutionMode, this.EnableConcurrentRuns, checkpointManager);
|
|
}
|
|
|
|
internal ExecutionMode ExecutionMode { get; }
|
|
internal bool EnableConcurrentRuns { get; }
|
|
internal CheckpointManager? CheckpointManager { get; }
|
|
|
|
/// <inheritdoc/>
|
|
public bool IsCheckpointingEnabled => this.CheckpointManager != null;
|
|
|
|
internal ValueTask<AsyncRunHandle> BeginRunAsync(Workflow workflow, string? sessionId, IEnumerable<Type> knownValidInputTypes, CancellationToken cancellationToken)
|
|
{
|
|
InProcessRunner runner = InProcessRunner.CreateTopLevelRunner(workflow, this.CheckpointManager, sessionId, this.EnableConcurrentRuns, knownValidInputTypes);
|
|
return runner.BeginStreamAsync(this.ExecutionMode, cancellationToken);
|
|
}
|
|
|
|
internal ValueTask<AsyncRunHandle> ResumeRunAsync(Workflow workflow, CheckpointInfo fromCheckpoint, IEnumerable<Type> knownValidInputTypes, CancellationToken cancellationToken = default)
|
|
=> this.ResumeRunAsync(workflow, fromCheckpoint, knownValidInputTypes, republishPendingEvents: true, cancellationToken);
|
|
|
|
internal ValueTask<AsyncRunHandle> ResumeRunAsync(Workflow workflow, CheckpointInfo fromCheckpoint, IEnumerable<Type> knownValidInputTypes, bool republishPendingEvents, CancellationToken cancellationToken = default)
|
|
{
|
|
InProcessRunner runner = InProcessRunner.CreateTopLevelRunner(workflow, this.CheckpointManager, fromCheckpoint.SessionId, this.EnableConcurrentRuns, knownValidInputTypes);
|
|
return runner.ResumeStreamAsync(this.ExecutionMode, fromCheckpoint, republishPendingEvents, cancellationToken);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask<StreamingRun> OpenStreamingAsync(
|
|
Workflow workflow,
|
|
string? sessionId = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
AsyncRunHandle runHandle = await this.BeginRunAsync(workflow, sessionId, [], cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return new(runHandle);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask<StreamingRun> RunStreamingAsync<TInput>(
|
|
Workflow workflow,
|
|
TInput input,
|
|
string? sessionId = null,
|
|
CancellationToken cancellationToken = default) where TInput : notnull
|
|
{
|
|
AsyncRunHandle runHandle = await this.BeginRunAsync(workflow, sessionId, [], cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return await runHandle.EnqueueAndStreamAsync(input, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
[MemberNotNull(nameof(CheckpointManager))]
|
|
private void VerifyCheckpointingConfigured()
|
|
{
|
|
if (this.CheckpointManager == null)
|
|
{
|
|
throw new InvalidOperationException("Checkpointing is not configured for this execution environment. Please use the InProcessExecutionEnvironment.WithCheckpointing method to attach a CheckpointManager.");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask<StreamingRun> ResumeStreamingAsync(
|
|
Workflow workflow,
|
|
CheckpointInfo fromCheckpoint,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
this.VerifyCheckpointingConfigured();
|
|
|
|
AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return new(runHandle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes a streaming workflow run from a checkpoint with control over whether
|
|
/// pending request events are republished through the event stream.
|
|
/// </summary>
|
|
/// <param name="workflow">The workflow to resume.</param>
|
|
/// <param name="fromCheckpoint">The checkpoint to resume from.</param>
|
|
/// <param name="republishPendingEvents">
|
|
/// When <see langword="true"/>, any pending request events are republished through the event
|
|
/// stream after subscribing. When <see langword="false"/>, the caller is responsible for
|
|
/// handling pending requests (e.g., <see cref="WorkflowSession"/> already sends responses).
|
|
/// </param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
internal async ValueTask<StreamingRun> ResumeStreamingInternalAsync(
|
|
Workflow workflow,
|
|
CheckpointInfo fromCheckpoint,
|
|
bool republishPendingEvents,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
this.VerifyCheckpointingConfigured();
|
|
|
|
AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], republishPendingEvents, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return new(runHandle);
|
|
}
|
|
|
|
private async ValueTask<AsyncRunHandle> BeginRunHandlingChatProtocolAsync<TInput>(Workflow workflow,
|
|
TInput input,
|
|
string? sessionId = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ProtocolDescriptor descriptor = await workflow.DescribeProtocolAsync(cancellationToken).ConfigureAwait(false);
|
|
AsyncRunHandle runHandle = await this.BeginRunAsync(workflow, sessionId, descriptor.Accepts, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
await runHandle.EnqueueMessageAsync(input, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (descriptor.IsChatProtocol() && input is not TurnToken)
|
|
{
|
|
await runHandle.EnqueueMessageAsync(new TurnToken(emitEvents: true), cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
return runHandle;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask<Run> RunAsync<TInput>(
|
|
Workflow workflow,
|
|
TInput input,
|
|
string? sessionId = null,
|
|
CancellationToken cancellationToken = default) where TInput : notnull
|
|
{
|
|
AsyncRunHandle runHandle = await this.BeginRunHandlingChatProtocolAsync(
|
|
workflow,
|
|
input,
|
|
sessionId,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
Run run = new(runHandle);
|
|
await run.RunToNextHaltAsync(cancellationToken).ConfigureAwait(false);
|
|
return run;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask<Run> ResumeAsync(
|
|
Workflow workflow,
|
|
CheckpointInfo fromCheckpoint,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
this.VerifyCheckpointingConfigured();
|
|
|
|
AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return new(runHandle);
|
|
}
|
|
}
|