Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public override async Task<AgentRunResponse> RunAsync(
}

RunRequest request = new([.. messages], responseFormat, enableToolCalls, enableToolNames);
request.OrchestrationId = this._context.InstanceId;

try
{
return await this._context.Entities.CallEntityAsync<AgentRunResponse>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.DurableTask.IntegrationTests" />
<InternalsVisibleTo Include="Microsoft.Agents.AI.DurableTask.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
</ItemGroup>
Expand Down
7 changes: 7 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.DurableTask/RunRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ public record RunRequest
[JsonInclude]
internal string CorrelationId { get; set; } = Guid.NewGuid().ToString("N");

/// <summary>
/// Gets or sets the ID of the orchestration that initiated this request (if any).
/// </summary>
[JsonInclude]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
internal string? OrchestrationId { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="RunRequest"/> class for a single message.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ internal sealed class DurableAgentState
/// The version is specified in semver (i.e. "major.minor.patch") format.
/// </remarks>
[JsonPropertyName("schemaVersion")]
public string SchemaVersion { get; init; } = "1.0.0";
public string SchemaVersion { get; init; } = "1.1.0";
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ namespace Microsoft.Agents.AI.DurableTask.State;
/// </summary>
internal sealed class DurableAgentStateRequest : DurableAgentStateEntry
{
/// <summary>
/// Gets the ID of the orchestration that initiated this request (if any).
/// </summary>
[JsonPropertyName("orchestrationId")]
public string? OrchestrationId { get; init; }

/// <summary>
/// Gets the expected response type for this request (e.g. "json" or "text").
/// </summary>
Expand Down Expand Up @@ -41,6 +47,7 @@ public static DurableAgentStateRequest FromRunRequest(RunRequest request)
return new DurableAgentStateRequest()
{
CorrelationId = request.CorrelationId,
OrchestrationId = request.OrchestrationId,
Messages = request.Messages.Select(DurableAgentStateMessage.FromChatMessage).ToList(),
CreatedAt = request.Messages.Min(m => m.CreatedAt) ?? DateTimeOffset.UtcNow,
ResponseType = request.ResponseFormat is ChatResponseFormatJson ? "json" : "text",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

using System.Diagnostics;
using System.Reflection;
using Microsoft.Agents.AI.DurableTask.State;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.Entities;
using Microsoft.DurableTask.Entities;
Expand Down Expand Up @@ -67,8 +69,72 @@ await simpleAgentProxy.RunAsync(
cancellationToken: this.TestTimeoutToken);

// Assert: verify the agent state was stored with the correct entity name prefix
entity = await client.Entities.GetEntityAsync(expectedEntityId, false, this.TestTimeoutToken);
entity = await client.Entities.GetEntityAsync(expectedEntityId, true, this.TestTimeoutToken);

Assert.NotNull(entity);
Assert.True(entity.IncludesState);

DurableAgentState state = entity.State.ReadAs<DurableAgentState>();

DurableAgentStateRequest request = Assert.Single(state.Data.ConversationHistory.OfType<DurableAgentStateRequest>());

Assert.Null(request.OrchestrationId);
}

[Fact]
public async Task OrchestrationIdSetDuringOrchestrationAsync()
{
// Arrange
AIAgent simpleAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
name: "TestAgent",
instructions: "You are a helpful assistant that always responds with a friendly greeting."
);

using TestHelper testHelper = TestHelper.Start(
[simpleAgent],
this._outputHelper,
registry => registry.AddOrchestrator<TestOrchestrator>());

DurableTaskClient client = testHelper.GetClient();

// Act
string orchestrationId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(TestOrchestrator), "What is the capital of Maine?");

OrchestrationMetadata? status = await client.WaitForInstanceCompletionAsync(
orchestrationId,
true,
this.TestTimeoutToken);

// Assert
EntityInstanceId expectedEntityId = AgentSessionId.Parse(status.ReadOutputAs<string>()!);

EntityMetadata? entity = await client.Entities.GetEntityAsync(expectedEntityId, true, this.TestTimeoutToken);

Assert.NotNull(entity);
Assert.True(entity.IncludesState);

DurableAgentState state = entity.State.ReadAs<DurableAgentState>();

DurableAgentStateRequest request = Assert.Single(state.Data.ConversationHistory.OfType<DurableAgentStateRequest>());

Assert.Equal(orchestrationId, request.OrchestrationId);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Constructed via reflection.")]
private sealed class TestOrchestrator : TaskOrchestrator<string, string>
{
public override async Task<string> RunAsync(TaskOrchestrationContext context, string input)
{
DurableAIAgent writer = context.GetAgent("TestAgent");
AgentThread writerThread = writer.GetNewThread();

await writer.RunAsync(
message: context.GetInput<string>()!,
thread: writerThread);

AgentSessionId sessionId = writerThread.GetService<AgentSessionId>();

return sessionId.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using Microsoft.Agents.AI.DurableTask.State;

namespace Microsoft.Agents.AI.DurableTask.Tests.Unit.State;

public sealed class DurableAgentStateRequestTests
{
[Fact]
public void RequestSerializationDeserialization()
{
// Arrange
RunRequest originalRequest = new("Hello, world!")
{
OrchestrationId = "orch-456"
};
DurableAgentStateRequest originalDurableRequest = DurableAgentStateRequest.FromRunRequest(originalRequest);

// Act
string jsonContent = JsonSerializer.Serialize(
originalDurableRequest,
DurableAgentStateJsonContext.Default.GetTypeInfo(typeof(DurableAgentStateRequest))!);

DurableAgentStateRequest? convertedJsonContent = (DurableAgentStateRequest?)JsonSerializer.Deserialize(
jsonContent,
DurableAgentStateJsonContext.Default.GetTypeInfo(typeof(DurableAgentStateRequest))!);

// Assert
Assert.NotNull(convertedJsonContent);
Assert.Equal(originalRequest.CorrelationId, convertedJsonContent.CorrelationId);
Assert.Equal(originalRequest.OrchestrationId, convertedJsonContent.OrchestrationId);
}
}
4 changes: 4 additions & 0 deletions schemas/durable-agent-entity-state.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@
"description": "The request (i.e. prompt) sent to the agent.",
"properties": {
"$type": { "type": "string", "const": "request" },
"orchestrationId": {
"type": "string",
"description": "The identifier of the orchestration that initiated this agent request (if any)."
},
"responseSchema": {
"type": "object",
"description": "If the expected response type is JSON, this schema defines the expected structure of the response."
Expand Down
Loading