Skip to content

Commit 4615d06

Browse files
committed
refactor: Rename and simplify interface around getting typed data out of ExternalRequest/Response
* Also adds hints around using value types in PortableValue
1 parent 9f369e7 commit 4615d06

File tree

10 files changed

+27
-37
lines changed

10 files changed

+27
-37
lines changed

dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private static async Task Main()
101101
{
102102
case RequestInfoEvent e:
103103
{
104-
if (e.Request.DataIs(out FunctionApprovalRequestContent? approvalRequestContent))
104+
if (e.Request.TryGetDataAs(out FunctionApprovalRequestContent? approvalRequestContent))
105105
{
106106
Console.WriteLine();
107107
Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.Request.PortInfo.PortId}");

dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointWithHumanInTheLoop/Program.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@ private static async Task Main()
9898

9999
private static ExternalResponse HandleExternalRequest(ExternalRequest request)
100100
{
101-
var signal = request.DataAs<SignalWithNumber>();
102-
if (signal is not null)
101+
if (request.TryGetDataAs<SignalWithNumber>(out var signal))
103102
{
104103
switch (signal.Signal)
105104
{

dotnet/samples/GettingStarted/Workflows/HumanInTheLoop/HumanInTheLoopBasic/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ private static ExternalResponse HandleExternalRequest(ExternalRequest request)
5050
{
5151
if (request.DataIs<NumberSignal>())
5252
{
53-
switch (request.DataAs<NumberSignal>())
53+
// We explicitly use .Data.As because we rely on the value defaulting to NumberSignal.Init when not set
54+
switch (request.Data.As<NumberSignal>())
5455
{
5556
case NumberSignal.Init:
5657
int initialGuess = ReadIntegerFromConsole("Please provide your initial guess: ");

dotnet/src/Microsoft.Agents.AI.Workflows/ExternalRequest.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ namespace Microsoft.Agents.AI.Workflows;
1515
/// <param name="Data">The data contained in the request.</param>
1616
public record ExternalRequest(RequestPortInfo PortInfo, string RequestId, PortableValue Data)
1717
{
18-
/// <summary>
19-
/// Attempts to retrieve the underlying data as the specified type.
20-
/// </summary>
21-
/// <typeparam name="TValue">The type to which the data should be cast or converted.</typeparam>
22-
/// <returns>The data cast to the specified type, or null if the data cannot be cast to the specified type.</returns>
23-
public TValue? DataAs<TValue>() => this.Data.As<TValue>();
24-
2518
/// <summary>
2619
/// Determines whether the underlying data is of the specified type.
2720
/// </summary>
@@ -34,7 +27,16 @@ public record ExternalRequest(RequestPortInfo PortInfo, string RequestId, Portab
3427
/// </summary>
3528
/// <typeparam name="TValue">The type to compare with the underlying data.</typeparam>
3629
/// <returns>true if the underlying data is of type TValue; otherwise, false.</returns>
37-
public bool DataIs<TValue>([NotNullWhen(true)] out TValue? value) => this.Data.Is(out value);
30+
public bool TryGetDataAs<TValue>([NotNullWhen(true)] out TValue? value) => this.Data.Is(out value);
31+
32+
/// <summary>
33+
/// Attempts to retrieve the underlying data as the specified type.
34+
/// </summary>
35+
/// <param name="targetType">The type to which the data should be cast or converted.</param>
36+
/// <param name="value">When this method returns <see langword="true"/>, contains the value of type
37+
/// <paramref name="targetType"/> if the data is available and compatible.</param>
38+
/// <returns>true if the data is present and can be cast to <paramref name="targetType"/>; otherwise, false.</returns>
39+
public bool TryGetDataAs(Type targetType, [NotNullWhen(true)] out object? value) => this.Data.IsType(targetType, out value);
3840

3941
/// <summary>
4042
/// Creates a new <see cref="ExternalRequest"/> for the specified input port and data payload.

dotnet/src/Microsoft.Agents.AI.Workflows/ExternalResponse.cs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ namespace Microsoft.Agents.AI.Workflows;
1414
/// <param name="Data">The data contained in the response.</param>
1515
public record ExternalResponse(RequestPortInfo PortInfo, string RequestId, PortableValue Data)
1616
{
17-
/// <summary>
18-
/// Attempts to retrieve the underlying data as the specified type.
19-
/// </summary>
20-
/// <typeparam name="TValue">The type to which the data should be cast or converted.</typeparam>
21-
/// <returns>The data cast to the specified type, or null if the data cannot be cast to the specified type.</returns>
22-
public TValue? DataAs<TValue>() => this.Data.As<TValue>();
23-
2417
/// <summary>
2518
/// Determines whether the underlying data is of the specified type.
2619
/// </summary>
@@ -35,14 +28,7 @@ public record ExternalResponse(RequestPortInfo PortInfo, string RequestId, Porta
3528
/// <param name="value">When this method returns, contains the value of type <typeparamref name="TValue"/> if the data is
3629
/// available and compatible.</param>
3730
/// <returns>true if the data is present and can be cast to <typeparamref name="TValue"/>; otherwise, false.</returns>
38-
public bool DataIs<TValue>([NotNullWhen(true)] out TValue? value) => this.Data.Is(out value);
39-
40-
/// <summary>
41-
/// Attempts to retrieve the underlying data as the specified type.
42-
/// </summary>
43-
/// <param name="targetType">The type to which the data should be cast or converted.</param>
44-
/// <returns>The data cast to the specified type, or null if the data cannot be cast to the specified type.</returns>
45-
public object? DataAs(Type targetType) => this.Data.AsType(targetType);
31+
public bool TryGetDataAs<TValue>([NotNullWhen(true)] out TValue? value) => this.Data.Is(out value);
4632

4733
/// <summary>
4834
/// Attempts to retrieve the underlying data as the specified type.
@@ -51,5 +37,5 @@ public record ExternalResponse(RequestPortInfo PortInfo, string RequestId, Porta
5137
/// <param name="value">When this method returns <see langword="true"/>, contains the value of type
5238
/// <paramref name="targetType"/> if the data is available and compatible.</param>
5339
/// <returns>true if the data is present and can be cast to <paramref name="targetType"/>; otherwise, false.</returns>
54-
public bool DataIs(Type targetType, [NotNullWhen(true)] out object? value) => this.Data.IsType(targetType, out value);
40+
public bool TryGetDataAs(Type targetType, [NotNullWhen(true)] out object? value) => this.Data.IsType(targetType, out value);
5541
}

dotnet/src/Microsoft.Agents.AI.Workflows/PortableValue.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ public override int GetHashCode()
9696
/// </summary>
9797
/// <remarks>If the underlying value implements delayed deserialization, this method will attempt to
9898
/// deserialize it to the specified type. If the value is already of the requested type, it is returned directly.
99-
/// Otherwise, the default value for TValue is returned.
99+
/// Otherwise, the default value for TValue is returned. For value types, the default is not <see langword="null"/>,
100+
/// UNLESS <typeparamref name="TValue"/> is nullable, e.g. <c>int?</c>.
100101
/// </remarks>
101102
/// <typeparam name="TValue">The type to which the value should be cast or deserialized.</typeparam>
102103
/// <returns>The value cast or deserialized to type TValue if possible; otherwise, the default value for type TValue.</returns>

dotnet/src/Microsoft.Agents.AI.Workflows/RouteBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ internal RouteBuilder AddPortHandler<TRequest, TResponse>(string id, Func<TRespo
158158

159159
async ValueTask<ExternalResponse?> InvokeHandlerAsync(ExternalResponse response, IWorkflowContext context, CancellationToken cancellationToken)
160160
{
161-
if (!response.DataIs(out TResponse? typedResponse))
161+
if (!response.TryGetDataAs(out TResponse? typedResponse))
162162
{
163163
throw new InvalidOperationException($"Received response data is not of expected type {typeof(TResponse).FullName} for port {port.Id}.");
164164
}

dotnet/src/Shared/Workflows/Execution/WorkflowRunner.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,10 @@ public async Task ExecuteAsync(Func<Workflow> workflowProvider, string input)
272272
/// </summary>
273273
private async ValueTask<ExternalInputResponse> HandleExternalRequestAsync(ExternalRequest request)
274274
{
275-
ExternalInputRequest inputRequest =
276-
request.DataAs<ExternalInputRequest>() ??
275+
if (!request.TryGetDataAs<ExternalInputRequest>(out var inputRequest))
276+
{
277277
throw new InvalidOperationException($"Expected external request type: {request.GetType().Name}.");
278+
}
278279

279280
List<ChatMessage> responseMessages = [];
280281

dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/09_Subworkflow_ExternalRequest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,14 @@ public static async ValueTask<List<RequestFinished>> RunAsync(TextWriter writer,
237237

238238
foreach (ExternalRequest request in resourceRequests)
239239
{
240-
ResourceRequest resourceRequest = request.DataAs<ResourceRequest>()!;
240+
ResourceRequest resourceRequest = request.Data.As<ResourceRequest>()!;
241241
resourceRequest.Id.Should().BeOneOf(ResourceMissIds);
242242
responses.Add(request.CreateResponse(Part2FinishedResponses[resourceRequest.Id].ResourceResponse!));
243243
}
244244

245245
foreach (ExternalRequest request in policyRequests)
246246
{
247-
PolicyCheckRequest policyRequest = request.DataAs<PolicyCheckRequest>()!;
247+
PolicyCheckRequest policyRequest = request.Data.As<PolicyCheckRequest>()!;
248248
policyRequest.Id.Should().BeOneOf(PolicyMissIds);
249249
responses.Add(request.CreateResponse(Part2FinishedResponses[policyRequest.Id].PolicyResponse!));
250250
}
@@ -372,7 +372,7 @@ void ConfigureRoutes(RouteBuilder routeBuilder)
372372

373373
private async ValueTask UnwrapAndHandleRequestAsync(ExternalRequest request, IWorkflowContext context, CancellationToken cancellationToken = default)
374374
{
375-
if (request.DataIs(out ResourceRequest? resourceRequest))
375+
if (request.TryGetDataAs(out ResourceRequest? resourceRequest))
376376
{
377377
ResourceResponse? response = await this.TryHandleResourceRequestAsync(resourceRequest, context, cancellationToken)
378378
.ConfigureAwait(false);
@@ -459,7 +459,7 @@ void ConfigureRoutes(RouteBuilder routeBuilder)
459459

460460
private async ValueTask UnwrapAndHandleRequestAsync(ExternalRequest request, IWorkflowContext context)
461461
{
462-
if (request.DataIs(out PolicyCheckRequest? policyRquest))
462+
if (request.TryGetDataAs(out PolicyCheckRequest? policyRquest))
463463
{
464464
PolicyResponse? response = await this.TryHandlePolicyCheckRequestAsync(policyRquest, context)
465465
.ConfigureAwait(false);

dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ internal IEnumerable<ExternalResponse> ValidateUnpairedRequests(List<ExternalReq
325325

326326
static TRequest AssertAndExtractRequestContent<TRequest>(ExternalRequest request)
327327
{
328-
request.DataIs(out TRequest? content).Should().BeTrue();
328+
request.TryGetDataAs(out TRequest? content).Should().BeTrue();
329329
return content!;
330330
}
331331
}

0 commit comments

Comments
 (0)