mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
3168eb4870
* .NET: [BREAKING] Add session statebag to use for state storage instead of inside providers (#3737) * Add a StateBag to AgentSession and pass Agent and AgentSession to AIContextProvider and ChatHistoryProviders * Convert all AIContextProviders to use the statebag * Update InMemoryChatHistoryProvider to use StateBag * Update Comsos and Workflow ChatHistoryProviders * Update 3rd party chat history storage sample. * Remove serialize method from providers * Replacing provider factories with properties * Remove Providers from Session and flatten state bag serialization * Update samples to use getservice on agent * Updated additional session types to serialize statebag * Fix regression * Address PR comments * Address PR comments. * Fix formatting * Fix unit tests * Remove InMemoryAgentSession since it is not required anymore. * Address PR comments * Convert sessions for A2AAgent, ChatClientAgent, CopilotStudioAgent and GithubCopilotAgent to use regular json serialization. * Fix durable agent session jso usgae * Add jso to InMemory and Workflow ChatHistoryProviders * Update InMemoryChatHistoryProvider to use an options class for it's many optional settings. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR feedback * Fix verification bug. * Improve state bag thread safety * Address PR comments and fix unit tests * Address PR comments * Fix unit test --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add a public StateKey property to providers (#3810) * .NET: [BREAKING] Update providers in such a way that they can participate in a pipeline (#3846) * Make providers pipeline capable * Fix unit tests * Move source stamping to providers from base class * Also update samples. * Address PR comments * Rename AsAgentRequestMessageSourcedMessage to WithAgentRequestMessageSource * .NET: [BREAKING] Add consistent message filtering to all providers. (#3851) * Add consistent message filtering to all providers. * Remove old chat history filtering classes * Fix merge issues * Fix unit test * Enforce non-nullable property * Fix merging bug and make troubleshooting source info easier by adding tostring implementation * .NET: [BREAKING] Add support for multiple AIContextProviders on a ChatClientAgent (#3863) * Add support for multiple AIContextProviders on a ChatClientAgent * Address PR comments and fix tests * Address PR comments. * .NET: [BREAKING]Delay AIContext Materialization until the end of the pipeline is reached. (#3883) * Delay AIContext Materialization until the end of the pipeline is reached. * Address PR comments. * Address PR comments * Modify InvokedContext to be immutable (#3888) * .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> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
183 lines
7.1 KiB
C#
183 lines
7.1 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Microsoft.Agents.AI;
|
|
|
|
/// <summary>
|
|
/// Used to store a value in session state.
|
|
/// </summary>
|
|
[JsonConverter(typeof(AgentSessionStateBagValueJsonConverter))]
|
|
internal class AgentSessionStateBagValue
|
|
{
|
|
private readonly object _lock = new();
|
|
private DeserializedCache? _cache;
|
|
private JsonElement _jsonValue;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the SessionStateValue class with the specified value.
|
|
/// </summary>
|
|
/// <param name="jsonValue">The serialized value to associate with the session state.</param>
|
|
public AgentSessionStateBagValue(JsonElement jsonValue)
|
|
{
|
|
this.JsonValue = jsonValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the SessionStateValue class with the specified value.
|
|
/// </summary>
|
|
/// <param name="deserializedValue">The value to associate with the session state. Can be any object, including null.</param>
|
|
/// <param name="valueType">The type of the value.</param>
|
|
/// <param name="jsonSerializerOptions">The JSON serializer options to use for serializing the value.</param>
|
|
public AgentSessionStateBagValue(object? deserializedValue, Type valueType, JsonSerializerOptions jsonSerializerOptions)
|
|
{
|
|
this._cache = new DeserializedCache(deserializedValue, valueType, jsonSerializerOptions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the value associated with this instance.
|
|
/// </summary>
|
|
public JsonElement JsonValue
|
|
{
|
|
get
|
|
{
|
|
lock (this._lock)
|
|
{
|
|
// We are assuming here that JsonValue will only be read when the object is being serialized,
|
|
// which means that we will only call SerializeToElement when serializing and therefore it's
|
|
// OK to serialize on each read if the cache is set.
|
|
if (this._cache is { } cache)
|
|
{
|
|
this._jsonValue = JsonSerializer.SerializeToElement(cache.Value, cache.Options.GetTypeInfo(cache.ValueType));
|
|
}
|
|
|
|
return this._jsonValue;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
lock (this._lock)
|
|
{
|
|
this._jsonValue = value;
|
|
this._cache = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to read the deserialized value of this session state value.
|
|
/// Returns false if the value could not be deserialized into the required type, or if the value is undefined.
|
|
/// Returns true and sets the out parameter to null if the value is null.
|
|
/// </summary>
|
|
public bool TryReadDeserializedValue<T>(out T? value, JsonSerializerOptions? jsonSerializerOptions = null)
|
|
where T : class
|
|
{
|
|
var jso = jsonSerializerOptions ?? AgentAbstractionsJsonUtilities.DefaultOptions;
|
|
|
|
lock (this._lock)
|
|
{
|
|
switch (this._cache)
|
|
{
|
|
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)
|
|
{
|
|
case JsonElement jsonElement when jsonElement.ValueKind == JsonValueKind.Undefined:
|
|
value = null;
|
|
return false;
|
|
case JsonElement jsonElement when jsonElement.ValueKind == JsonValueKind.Null:
|
|
value = null;
|
|
return true;
|
|
default:
|
|
T? result = this._jsonValue.Deserialize(jso.GetTypeInfo(typeof(T))) as T;
|
|
if (result is null)
|
|
{
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
this._cache = new DeserializedCache(result, typeof(T), jso);
|
|
|
|
value = result;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the deserialized value of this session state value, throwing an exception if the value could not be deserialized into the required type or is undefined.
|
|
/// </summary>
|
|
public T? ReadDeserializedValue<T>(JsonSerializerOptions? jsonSerializerOptions = null)
|
|
where T : class
|
|
{
|
|
var jso = jsonSerializerOptions ?? AgentAbstractionsJsonUtilities.DefaultOptions;
|
|
|
|
lock (this._lock)
|
|
{
|
|
switch (this._cache)
|
|
{
|
|
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)
|
|
{
|
|
case JsonElement jsonElement when jsonElement.ValueKind == JsonValueKind.Null || jsonElement.ValueKind == JsonValueKind.Undefined:
|
|
return null;
|
|
default:
|
|
T? result = this._jsonValue.Deserialize(jso.GetTypeInfo(typeof(T))) as T;
|
|
if (result is null)
|
|
{
|
|
throw new InvalidOperationException($"Failed to deserialize session state value to type {typeof(T).FullName}.");
|
|
}
|
|
|
|
this._cache = new DeserializedCache(result, typeof(T), jso);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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<T>(T? deserializedValue, Type valueType, JsonSerializerOptions jsonSerializerOptions)
|
|
{
|
|
lock (this._lock)
|
|
{
|
|
this._cache = new DeserializedCache(deserializedValue, valueType, jsonSerializerOptions);
|
|
}
|
|
}
|
|
|
|
private readonly struct DeserializedCache
|
|
{
|
|
public DeserializedCache(object? value, Type valueType, JsonSerializerOptions options)
|
|
{
|
|
this.Value = value;
|
|
this.ValueType = valueType;
|
|
this.Options = options;
|
|
}
|
|
|
|
public object? Value { get; }
|
|
|
|
public Type ValueType { get; }
|
|
|
|
public JsonSerializerOptions Options { get; }
|
|
}
|
|
}
|