mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into feat/durable_task
This commit is contained in:
@@ -33,9 +33,17 @@
|
||||
|
||||
## v1.0.0-preview.251219.1
|
||||
|
||||
- Filter empty `AIContent` from durable agent state responses ([#4670](https://github.com/microsoft/agent-framework/pull/4670))
|
||||
|
||||
## v1.0.0-preview.260311.1
|
||||
|
||||
### Changed
|
||||
|
||||
- Added TTL configuration for durable agent entities ([#2679](https://github.com/microsoft/agent-framework/pull/2679))
|
||||
- Switch to new "Run" method name ([#2843](https://github.com/microsoft/agent-framework/pull/2843))
|
||||
|
||||
NOTE: Some of the above changes may have been part of earlier releases not mentioned in this file.
|
||||
|
||||
## v1.0.0-preview.251204.1
|
||||
|
||||
- Added orchestration ID to durable agent entity state ([#2137](https://github.com/microsoft/agent-framework/pull/2137))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.State;
|
||||
|
||||
@@ -28,7 +29,10 @@ internal sealed class DurableAgentStateResponse : DurableAgentStateEntry
|
||||
{
|
||||
CorrelationId = correlationId,
|
||||
CreatedAt = response.CreatedAt ?? response.Messages.Max(m => m.CreatedAt) ?? DateTimeOffset.UtcNow,
|
||||
Messages = response.Messages.Select(DurableAgentStateMessage.FromChatMessage).ToList(),
|
||||
Messages = response.Messages
|
||||
.Where(HasSerializableContent)
|
||||
.Select(DurableAgentStateMessage.FromChatMessage)
|
||||
.ToList(),
|
||||
Usage = DurableAgentStateUsage.FromUsage(response.Usage)
|
||||
};
|
||||
}
|
||||
@@ -46,4 +50,18 @@ internal sealed class DurableAgentStateResponse : DurableAgentStateEntry
|
||||
Usage = this.Usage?.ToUsageDetails(),
|
||||
};
|
||||
}
|
||||
|
||||
// Checks whether a ChatMessage has any content that will produce meaningful serialized data.
|
||||
// Known derived AIContent types (TextContent, FunctionCallContent, etc.) are always serializable.
|
||||
// Base AIContent instances only carry RawRepresentation (which is [JsonIgnore]), Annotations, and
|
||||
// AdditionalProperties. We keep the message if any base AIContent has annotations or additional
|
||||
// properties set. NOTE: if AIContent gains new serializable properties in the future, this check
|
||||
// should be updated accordingly.
|
||||
private static bool HasSerializableContent(ChatMessage message)
|
||||
{
|
||||
return message.Contents.Any(c =>
|
||||
c.GetType() != typeof(AIContent) ||
|
||||
c.Annotations?.Count > 0 ||
|
||||
c.AdditionalProperties?.Count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.DurableTask.State;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.Tests.Unit.State;
|
||||
|
||||
public sealed class DurableAgentStateResponseTests
|
||||
{
|
||||
[Fact]
|
||||
public void FromResponseDropsMessagesContainingOnlyOpaqueContent()
|
||||
{
|
||||
// Arrange: one message with real text, one with only opaque AIContent
|
||||
ChatMessage usefulMessage = new(ChatRole.Assistant, "Hello, world!")
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
ChatMessage opaqueOnlyMessage = new(ChatRole.Assistant, [
|
||||
new AIContent
|
||||
{
|
||||
RawRepresentation = new { kind = "sessionEvent", sessionId = "s123" }
|
||||
}])
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow.AddSeconds(1)
|
||||
};
|
||||
|
||||
AgentResponse response = new(new List<ChatMessage> { usefulMessage, opaqueOnlyMessage })
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
// Act
|
||||
DurableAgentStateResponse durableResponse = DurableAgentStateResponse.FromResponse("corr-123", response);
|
||||
|
||||
// Assert: only the useful message survives
|
||||
DurableAgentStateMessage durableMessage = Assert.Single(durableResponse.Messages);
|
||||
Assert.Equal(ChatRole.Assistant.Value, durableMessage.Role);
|
||||
|
||||
// Round-trip to verify the content is correct
|
||||
AgentResponse convertedResponse = durableResponse.ToResponse();
|
||||
ChatMessage convertedMessage = Assert.Single(convertedResponse.Messages);
|
||||
TextContent textContent = Assert.IsType<TextContent>(Assert.Single(convertedMessage.Contents));
|
||||
Assert.Equal("Hello, world!", textContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromResponseKeepsMessagesWithMixedContent()
|
||||
{
|
||||
// Arrange: one message with both real text and opaque AIContent
|
||||
ChatMessage mixedMessage = new(ChatRole.Assistant, [
|
||||
new TextContent("Some useful text"),
|
||||
new AIContent { RawRepresentation = new { kind = "metadata" } }])
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
AgentResponse response = new(new List<ChatMessage> { mixedMessage })
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
// Act
|
||||
DurableAgentStateResponse durableResponse = DurableAgentStateResponse.FromResponse("corr-456", response);
|
||||
|
||||
// Assert: the message is kept because it contains at least one serializable content
|
||||
DurableAgentStateMessage durableMessage = Assert.Single(durableResponse.Messages);
|
||||
Assert.Equal(ChatRole.Assistant.Value, durableMessage.Role);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromResponseDropsAllMessagesWhenAllAreOpaque()
|
||||
{
|
||||
// Arrange: all messages contain only opaque AIContent
|
||||
ChatMessage opaque1 = new(ChatRole.Assistant, [
|
||||
new AIContent { RawRepresentation = new { kind = "event1" } }])
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
ChatMessage opaque2 = new(ChatRole.Assistant, [
|
||||
new AIContent { RawRepresentation = new { kind = "event2" } }])
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow.AddSeconds(1)
|
||||
};
|
||||
|
||||
AgentResponse response = new(new List<ChatMessage> { opaque1, opaque2 })
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
// Act
|
||||
DurableAgentStateResponse durableResponse = DurableAgentStateResponse.FromResponse("corr-789", response);
|
||||
|
||||
// Assert: no messages stored
|
||||
Assert.Empty(durableResponse.Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromResponseKeepsBaseAIContentWithAnnotations()
|
||||
{
|
||||
// Arrange: base AIContent with annotations should be kept
|
||||
AIContent contentWithAnnotations = new()
|
||||
{
|
||||
RawRepresentation = new { kind = "event" },
|
||||
Annotations = [new AIAnnotation() { AdditionalProperties = new() { ["cite"] = "ref-1" } }]
|
||||
};
|
||||
ChatMessage message = new(ChatRole.Assistant, [contentWithAnnotations])
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
AgentResponse response = new([message]) { CreatedAt = DateTimeOffset.UtcNow };
|
||||
|
||||
// Act
|
||||
DurableAgentStateResponse durableResponse = DurableAgentStateResponse.FromResponse("corr-ann", response);
|
||||
|
||||
// Assert: message is kept because the AIContent has annotations
|
||||
Assert.Single(durableResponse.Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromResponseKeepsBaseAIContentWithAdditionalProperties()
|
||||
{
|
||||
// Arrange: base AIContent with additional properties should be kept
|
||||
AIContent contentWithProps = new()
|
||||
{
|
||||
RawRepresentation = new { kind = "event" },
|
||||
AdditionalProperties = new() { ["custom_key"] = "custom_value" }
|
||||
};
|
||||
ChatMessage message = new(ChatRole.Assistant, [contentWithProps])
|
||||
{
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
AgentResponse response = new([message]) { CreatedAt = DateTimeOffset.UtcNow };
|
||||
|
||||
// Act
|
||||
DurableAgentStateResponse durableResponse = DurableAgentStateResponse.FromResponse("corr-props", response);
|
||||
|
||||
// Assert: message is kept because the AIContent has additional properties
|
||||
Assert.Single(durableResponse.Messages);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user