mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Add orchestration ID to durable agent entity state (#2137)
* Propagate orchestration ID (if any). * Add integration test for orchestration ID in entity state. * Update schema. * Fixup formatting issues. * Fix more formatting issues.
This commit is contained in:
committed by
GitHub
Unverified
parent
486b78126d
commit
17b4dfab14
@@ -99,6 +99,8 @@ public sealed class DurableAIAgent : AIAgent
|
|||||||
}
|
}
|
||||||
|
|
||||||
RunRequest request = new([.. messages], responseFormat, enableToolCalls, enableToolNames);
|
RunRequest request = new([.. messages], responseFormat, enableToolCalls, enableToolNames);
|
||||||
|
request.OrchestrationId = this._context.InstanceId;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await this._context.Entities.CallEntityAsync<AgentRunResponse>(
|
return await this._context.Entities.CallEntityAsync<AgentRunResponse>(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="Microsoft.Agents.AI.DurableTask.IntegrationTests" />
|
||||||
<InternalsVisibleTo Include="Microsoft.Agents.AI.DurableTask.UnitTests" />
|
<InternalsVisibleTo Include="Microsoft.Agents.AI.DurableTask.UnitTests" />
|
||||||
<InternalsVisibleTo Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
|
<InternalsVisibleTo Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ public record RunRequest
|
|||||||
[JsonInclude]
|
[JsonInclude]
|
||||||
internal string CorrelationId { get; set; } = Guid.NewGuid().ToString("N");
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RunRequest"/> class for a single message.
|
/// Initializes a new instance of the <see cref="RunRequest"/> class for a single message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ internal sealed class DurableAgentState
|
|||||||
/// The version is specified in semver (i.e. "major.minor.patch") format.
|
/// The version is specified in semver (i.e. "major.minor.patch") format.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonPropertyName("schemaVersion")]
|
[JsonPropertyName("schemaVersion")]
|
||||||
public string SchemaVersion { get; init; } = "1.0.0";
|
public string SchemaVersion { get; init; } = "1.1.0";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ namespace Microsoft.Agents.AI.DurableTask.State;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class DurableAgentStateRequest : DurableAgentStateEntry
|
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>
|
/// <summary>
|
||||||
/// Gets the expected response type for this request (e.g. "json" or "text").
|
/// Gets the expected response type for this request (e.g. "json" or "text").
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -41,6 +47,7 @@ internal sealed class DurableAgentStateRequest : DurableAgentStateEntry
|
|||||||
return new DurableAgentStateRequest()
|
return new DurableAgentStateRequest()
|
||||||
{
|
{
|
||||||
CorrelationId = request.CorrelationId,
|
CorrelationId = request.CorrelationId,
|
||||||
|
OrchestrationId = request.OrchestrationId,
|
||||||
Messages = request.Messages.Select(DurableAgentStateMessage.FromChatMessage).ToList(),
|
Messages = request.Messages.Select(DurableAgentStateMessage.FromChatMessage).ToList(),
|
||||||
CreatedAt = request.Messages.Min(m => m.CreatedAt) ?? DateTimeOffset.UtcNow,
|
CreatedAt = request.Messages.Min(m => m.CreatedAt) ?? DateTimeOffset.UtcNow,
|
||||||
ResponseType = request.ResponseFormat is ChatResponseFormatJson ? "json" : "text",
|
ResponseType = request.ResponseFormat is ChatResponseFormatJson ? "json" : "text",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.Agents.AI.DurableTask.State;
|
||||||
|
using Microsoft.DurableTask;
|
||||||
using Microsoft.DurableTask.Client;
|
using Microsoft.DurableTask.Client;
|
||||||
using Microsoft.DurableTask.Client.Entities;
|
using Microsoft.DurableTask.Client.Entities;
|
||||||
using Microsoft.DurableTask.Entities;
|
using Microsoft.DurableTask.Entities;
|
||||||
@@ -67,8 +69,72 @@ public sealed class AgentEntityTests(ITestOutputHelper outputHelper) : IDisposab
|
|||||||
cancellationToken: this.TestTimeoutToken);
|
cancellationToken: this.TestTimeoutToken);
|
||||||
|
|
||||||
// Assert: verify the agent state was stored with the correct entity name prefix
|
// 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.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -166,6 +166,10 @@
|
|||||||
"description": "The request (i.e. prompt) sent to the agent.",
|
"description": "The request (i.e. prompt) sent to the agent.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"$type": { "type": "string", "const": "request" },
|
"$type": { "type": "string", "const": "request" },
|
||||||
|
"orchestrationId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The identifier of the orchestration that initiated this agent request (if any)."
|
||||||
|
},
|
||||||
"responseSchema": {
|
"responseSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "If the expected response type is JSON, this schema defines the expected structure of the response."
|
"description": "If the expected response type is JSON, this schema defines the expected structure of the response."
|
||||||
|
|||||||
Reference in New Issue
Block a user