mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Address Feedback on StateBag feature branch PR (#3910)
* Address Feedback on statebag feature branch PR * Update dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
af801e57f8
commit
42b4328ac7
+4
-4
@@ -59,13 +59,13 @@ namespace SampleApp
|
||||
|
||||
// Get existing messages from the store
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(this, session, messages);
|
||||
var storeMessages = await this.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
var userAndChatHistoryMessages = await this.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
|
||||
// Clone the input messages and turn them into response messages with upper case text.
|
||||
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
|
||||
|
||||
// Notify the session of the input and output messages.
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, responseMessages);
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, userAndChatHistoryMessages, responseMessages);
|
||||
await this.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
|
||||
|
||||
return new AgentResponse
|
||||
@@ -88,13 +88,13 @@ namespace SampleApp
|
||||
|
||||
// Get existing messages from the store
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(this, session, messages);
|
||||
var storeMessages = await this.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
var userAndChatHistoryMessages = await this.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
|
||||
// Clone the input messages and turn them into response messages with upper case text.
|
||||
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
|
||||
|
||||
// Notify the session of the input and output messages.
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, responseMessages);
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, userAndChatHistoryMessages, responseMessages);
|
||||
await this.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
|
||||
|
||||
foreach (var message in responseMessages)
|
||||
|
||||
@@ -223,9 +223,9 @@ public abstract class AIContextProvider
|
||||
/// Contains the context information provided to <see cref="InvokedCoreAsync(InvokedContext, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class provides context about a completed agent invocation, including both the
|
||||
/// request messages that were used and the response messages that were generated. It also indicates
|
||||
/// whether the invocation succeeded or failed.
|
||||
/// This class provides context about a completed agent invocation, including the accumulated
|
||||
/// request messages (user input, chat history and any others provided by AI context providers) that were used
|
||||
/// and the response messages that were generated. It also indicates whether the invocation succeeded or failed.
|
||||
/// </remarks>
|
||||
public sealed class InvokedContext
|
||||
{
|
||||
@@ -234,7 +234,8 @@ public abstract class AIContextProvider
|
||||
/// </summary>
|
||||
/// <param name="agent">The agent that was invoked.</param>
|
||||
/// <param name="session">The session associated with the agent invocation.</param>
|
||||
/// <param name="requestMessages">The messages that were used by the agent for this invocation.</param>
|
||||
/// <param name="requestMessages">The accumulated request messages (user input, chat history and any others provided by AI context providers)
|
||||
/// that were used by the agent for this invocation.</param>
|
||||
/// <param name="responseMessages">The response messages generated during this invocation.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="responseMessages"/> is <see langword="null"/>.</exception>
|
||||
public InvokedContext(
|
||||
@@ -254,7 +255,8 @@ public abstract class AIContextProvider
|
||||
/// </summary>
|
||||
/// <param name="agent">The agent that was invoked.</param>
|
||||
/// <param name="session">The session associated with the agent invocation.</param>
|
||||
/// <param name="requestMessages">The messages that were used by the agent for this invocation.</param>
|
||||
/// <param name="requestMessages">The accumulated request messages (user input, chat history and any others provided by AI context providers)
|
||||
/// that were used by the agent for this invocation.</param>
|
||||
/// <param name="invokeException">The exception that caused the invocation to fail.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="invokeException"/> is <see langword="null"/>.</exception>
|
||||
public InvokedContext(
|
||||
@@ -280,7 +282,8 @@ public abstract class AIContextProvider
|
||||
public AgentSession? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the messages that were used by the agent for this invocation.
|
||||
/// Gets the accumulated request messages (user input, chat history and any others provided by AI context providers)
|
||||
/// that were used by the agent for this invocation.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A collection of <see cref="ChatMessage"/> instances representing all messages that were used by the agent for this invocation.
|
||||
|
||||
@@ -78,10 +78,17 @@ internal class AgentSessionStateBagValue
|
||||
|
||||
lock (this._lock)
|
||||
{
|
||||
if (this._cache is { } cache)
|
||||
switch (this._cache)
|
||||
{
|
||||
value = cache.Value as T;
|
||||
return true;
|
||||
case DeserializedCache { Value: null, ValueType: Type cacheValueType } when cacheValueType == typeof(T):
|
||||
value = null;
|
||||
return true;
|
||||
case DeserializedCache { Value: T cacheValue, ValueType: Type cacheValueType } when cacheValueType == typeof(T):
|
||||
value = cacheValue;
|
||||
return true;
|
||||
case DeserializedCache { ValueType: Type cacheValueType } when cacheValueType != typeof(T):
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this._jsonValue)
|
||||
@@ -118,9 +125,14 @@ internal class AgentSessionStateBagValue
|
||||
|
||||
lock (this._lock)
|
||||
{
|
||||
if (this._cache is { } cache)
|
||||
switch (this._cache)
|
||||
{
|
||||
return cache.Value as T;
|
||||
case DeserializedCache { Value: null, ValueType: Type cacheValueType } when cacheValueType == typeof(T):
|
||||
return null;
|
||||
case DeserializedCache { Value: T cacheValue, ValueType: Type cacheValueType } when cacheValueType == typeof(T):
|
||||
return cacheValue;
|
||||
case DeserializedCache { ValueType: Type cacheValueType } when cacheValueType != typeof(T):
|
||||
throw new InvalidOperationException($"The type of the cached value is {cacheValueType.FullName}, but the requested type is {typeof(T).FullName}.");
|
||||
}
|
||||
|
||||
switch (this._jsonValue)
|
||||
@@ -144,7 +156,7 @@ internal class AgentSessionStateBagValue
|
||||
/// Sets the deserialized value of this session state value, updating the cache accordingly.
|
||||
/// This does not update the JsonValue directly; the JsonValue will be updated on the next read or when the object is serialized.
|
||||
/// </summary>
|
||||
public void SetDeserialized(object? deserializedValue, Type valueType, JsonSerializerOptions jsonSerializerOptions)
|
||||
public void SetDeserialized<T>(T? deserializedValue, Type valueType, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
lock (this._lock)
|
||||
{
|
||||
|
||||
@@ -253,9 +253,9 @@ public abstract class ChatHistoryProvider
|
||||
/// Contains the context information provided to <see cref="InvokedCoreAsync(InvokedContext, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class provides context about a completed agent invocation, including both the
|
||||
/// request messages that were used and the response messages that were generated. It also indicates
|
||||
/// whether the invocation succeeded or failed.
|
||||
/// This class provides context about a completed agent invocation, including the accumulated
|
||||
/// request messages (user input, chat history and any others provided by AI context providers) that were used
|
||||
/// and the response messages that were generated. It also indicates whether the invocation succeeded or failed.
|
||||
/// </remarks>
|
||||
public sealed class InvokedContext
|
||||
{
|
||||
@@ -264,7 +264,8 @@ public abstract class ChatHistoryProvider
|
||||
/// </summary>
|
||||
/// <param name="agent">The agent that was invoked.</param>
|
||||
/// <param name="session">The session associated with the agent invocation.</param>
|
||||
/// <param name="requestMessages">The caller provided messages that were used by the agent for this invocation.</param>
|
||||
/// <param name="requestMessages">The accumulated request messages (user input, chat history and any others provided by AI context providers)
|
||||
/// that were used by the agent for this invocation.</param>
|
||||
/// <param name="responseMessages">The response messages generated during this invocation.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="responseMessages"/> is <see langword="null"/>.</exception>
|
||||
public InvokedContext(
|
||||
@@ -284,7 +285,8 @@ public abstract class ChatHistoryProvider
|
||||
/// </summary>
|
||||
/// <param name="agent">The agent that was invoked.</param>
|
||||
/// <param name="session">The session associated with the agent invocation.</param>
|
||||
/// <param name="requestMessages">The caller provided messages that were used by the agent for this invocation.</param>
|
||||
/// <param name="requestMessages">The accumulated request messages (user input, chat history and any others provided by AI context providers)
|
||||
/// that were used by the agent for this invocation.</param>
|
||||
/// <param name="invokeException">The exception that caused the invocation to fail.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="invokeException"/> is <see langword="null"/>.</exception>
|
||||
public InvokedContext(
|
||||
@@ -310,7 +312,8 @@ public abstract class ChatHistoryProvider
|
||||
public AgentSession? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the caller provided messages that were used by the agent for this invocation.
|
||||
/// Gets the accumulated request messages (user input, chat history and any others provided by AI context providers)
|
||||
/// that were used by the agent for this invocation.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A collection of <see cref="ChatMessage"/> instances representing new messages that were provided by the caller.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- Renamed serializedSession parameter to serializedState on DeserializeSessionAsync for consistency ([#3681](https://github.com/microsoft/agent-framework/pull/3681))
|
||||
- Introduce Core method pattern for Session management methods on AIAgent ([#3699](https://github.com/microsoft/agent-framework/pull/3699))
|
||||
- Changed AIAgent.SerializeSession to AIAgent.SerializeSessionAsync ([#3879](https://github.com/microsoft/agent-framework/pull/3879))
|
||||
- Changed ChatHistory and AIContext Providers to have pipeline semantics ([#3806](https://github.com/microsoft/agent-framework/pull/3806))
|
||||
|
||||
## v1.0.0-preview.251204.1
|
||||
|
||||
|
||||
+101
-8
@@ -534,14 +534,9 @@ public sealed class AgentSessionStateBagTests
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
int index = i;
|
||||
if (index % 2 == 0)
|
||||
{
|
||||
tasks[i] = System.Threading.Tasks.Task.Run(() => stateBag.GetValue<string>("key"));
|
||||
}
|
||||
else
|
||||
{
|
||||
tasks[i] = System.Threading.Tasks.Task.Run(() => stateBag.SetValue("key", $"value{index}"));
|
||||
}
|
||||
tasks[i] = (index % 2 == 0)
|
||||
? System.Threading.Tasks.Task.Run(() => stateBag.GetValue<string>("key"))
|
||||
: System.Threading.Tasks.Task.Run(() => stateBag.SetValue("key", $"value{index}"));
|
||||
}
|
||||
|
||||
await System.Threading.Tasks.Task.WhenAll(tasks);
|
||||
@@ -650,6 +645,104 @@ public sealed class AgentSessionStateBagTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type Mismatch Tests
|
||||
|
||||
[Fact]
|
||||
public void TryGetValue_WithDifferentTypeAfterSet_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var stateBag = new AgentSessionStateBag();
|
||||
stateBag.SetValue("key1", "hello");
|
||||
|
||||
// Act
|
||||
var found = stateBag.TryGetValue<Animal>("key1", out var result, TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.False(found);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetValue_WithDifferentTypeAfterSet_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var stateBag = new AgentSessionStateBag();
|
||||
stateBag.SetValue("key1", "hello");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() => stateBag.GetValue<Animal>("key1", TestJsonSerializerContext.Default.Options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValue_WithDifferentTypeAfterDeserializedRead_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var stateBag = new AgentSessionStateBag();
|
||||
stateBag.SetValue("key1", "hello");
|
||||
|
||||
// First read caches the value as string
|
||||
var cachedValue = stateBag.GetValue<string>("key1");
|
||||
Assert.Equal("hello", cachedValue);
|
||||
|
||||
// Act - request as a different type
|
||||
var found = stateBag.TryGetValue<Animal>("key1", out var result, TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.False(found);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetValue_WithDifferentTypeAfterDeserializedRoundtrip_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var originalStateBag = new AgentSessionStateBag();
|
||||
originalStateBag.SetValue("key1", "hello");
|
||||
|
||||
// Round-trip through serialization
|
||||
var json = originalStateBag.Serialize();
|
||||
var restoredStateBag = AgentSessionStateBag.Deserialize(json);
|
||||
|
||||
// First read caches the value as string
|
||||
var cachedValue = restoredStateBag.GetValue<string>("key1");
|
||||
Assert.Equal("hello", cachedValue);
|
||||
|
||||
// Act & Assert - request as a different type
|
||||
Assert.Throws<InvalidOperationException>(() => restoredStateBag.GetValue<Animal>("key1", TestJsonSerializerContext.Default.Options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValue_ComplexTypeAfterSetString_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var stateBag = new AgentSessionStateBag();
|
||||
stateBag.SetValue("animal", "not an animal");
|
||||
|
||||
// Act
|
||||
var found = stateBag.TryGetValue<Animal>("animal", out var result, TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.False(found);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetValue_TypeMismatch_ExceptionMessageContainsBothTypeNames()
|
||||
{
|
||||
// Arrange
|
||||
var stateBag = new AgentSessionStateBag();
|
||||
stateBag.SetValue("key1", "hello");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => stateBag.GetValue<Animal>("key1", TestJsonSerializerContext.Default.Options));
|
||||
|
||||
// Assert
|
||||
Assert.Contains(typeof(string).FullName!, exception.Message);
|
||||
Assert.Contains(typeof(Animal).FullName!, exception.Message);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JsonSerializer Integration Tests
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user