mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Replace Typed Base Providers with Composition (#3988)
This commit is contained in:
committed by
GitHub
Unverified
parent
8015e00f56
commit
cd4e36ebf7
-199
@@ -1,199 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="AIContextProvider{TState}"/> class.
|
||||
/// </summary>
|
||||
public class AIContextProviderTStateTests
|
||||
{
|
||||
private static readonly AIAgent s_mockAgent = new Mock<AIAgent>().Object;
|
||||
|
||||
#region GetOrInitializeState Tests
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_InitializesFromStateInitializerOnFirstCall()
|
||||
{
|
||||
// Arrange
|
||||
var expectedState = new TestState { Value = "initialized" };
|
||||
var provider = new TestAIContextProvider(_ => expectedState);
|
||||
var session = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state = provider.GetState(session);
|
||||
|
||||
// Assert
|
||||
Assert.Same(expectedState, state);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_ReturnsCachedStateFromStateBagOnSecondCall()
|
||||
{
|
||||
// Arrange
|
||||
var callCount = 0;
|
||||
var provider = new TestAIContextProvider(_ =>
|
||||
{
|
||||
callCount++;
|
||||
return new TestState { Value = $"init-{callCount}" };
|
||||
});
|
||||
var session = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state1 = provider.GetState(session);
|
||||
var state2 = provider.GetState(session);
|
||||
|
||||
// Assert - initializer called only once; second call reads from StateBag
|
||||
Assert.Equal(1, callCount);
|
||||
Assert.Equal("init-1", state1.Value);
|
||||
Assert.Equal("init-1", state2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_WorksWhenSessionIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider(_ => new TestState { Value = "no-session" });
|
||||
|
||||
// Act
|
||||
var state = provider.GetState(null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("no-session", state.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_ReInitializesWhenSessionIsNull()
|
||||
{
|
||||
// Arrange - without a session, state can't be cached in StateBag
|
||||
var callCount = 0;
|
||||
var provider = new TestAIContextProvider(_ =>
|
||||
{
|
||||
callCount++;
|
||||
return new TestState { Value = $"init-{callCount}" };
|
||||
});
|
||||
|
||||
// Act
|
||||
provider.GetState(null);
|
||||
provider.GetState(null);
|
||||
|
||||
// Assert - initializer called each time since there's no session to cache in
|
||||
Assert.Equal(2, callCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SaveState Tests
|
||||
|
||||
[Fact]
|
||||
public void SaveState_SavesToStateBag()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider(_ => new TestState());
|
||||
var session = new TestAgentSession();
|
||||
var state = new TestState { Value = "saved" };
|
||||
|
||||
// Act
|
||||
provider.DoSaveState(session, state);
|
||||
var retrieved = provider.GetState(session);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("saved", retrieved.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveState_NoOpWhenSessionIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider(_ => new TestState { Value = "default" });
|
||||
|
||||
// Act - should not throw
|
||||
provider.DoSaveState(null, new TestState { Value = "saved" });
|
||||
|
||||
// Assert - no exception; can't verify further without a session
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StateKey Tests
|
||||
|
||||
[Fact]
|
||||
public void StateKey_DefaultsToTypeName()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider(_ => new TestState());
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(nameof(TestAIContextProvider), provider.StateKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StateKey_UsesCustomKeyWhenProvided()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider(_ => new TestState(), stateKey: "custom-key");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("custom-key", provider.StateKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Integration Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingCoreAsync_CanUseStateInProvideAIContextAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider(_ => new TestState { Value = "state-value" });
|
||||
var session = new TestAgentSession();
|
||||
var inputContext = new AIContext { Messages = [new ChatMessage(ChatRole.User, "Hi")] };
|
||||
var context = new AIContextProvider.InvokingContext(s_mockAgent, session, inputContext);
|
||||
|
||||
// Act
|
||||
var result = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert - the provider uses state to produce context messages
|
||||
var messages = result.Messages!.ToList();
|
||||
Assert.Equal(2, messages.Count);
|
||||
Assert.Contains("state-value", messages[1].Text);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public sealed class TestState
|
||||
{
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private sealed class TestAIContextProvider : AIContextProvider<TestState>
|
||||
{
|
||||
public TestAIContextProvider(
|
||||
Func<AgentSession?, TestState> stateInitializer,
|
||||
string? stateKey = null)
|
||||
: base(stateInitializer, stateKey, null, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public TestState GetState(AgentSession? session) => this.GetOrInitializeState(session);
|
||||
|
||||
public void DoSaveState(AgentSession? session, TestState state) => this.SaveState(session, state);
|
||||
|
||||
protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var state = this.GetOrInitializeState(context.Session);
|
||||
return new(new AIContext
|
||||
{
|
||||
Messages = [new ChatMessage(ChatRole.System, $"Context from state: {state.Value}")]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestAgentSession : AgentSession;
|
||||
}
|
||||
-195
@@ -1,195 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="ChatHistoryProvider{TState}"/> class.
|
||||
/// </summary>
|
||||
public class ChatHistoryProviderTStateTests
|
||||
{
|
||||
private static readonly AIAgent s_mockAgent = new Mock<AIAgent>().Object;
|
||||
|
||||
#region GetOrInitializeState Tests
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_InitializesFromStateInitializerOnFirstCall()
|
||||
{
|
||||
// Arrange
|
||||
var expectedState = new TestState { Value = "initialized" };
|
||||
var provider = new TestChatHistoryProvider(_ => expectedState);
|
||||
var session = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state = provider.GetState(session);
|
||||
|
||||
// Assert
|
||||
Assert.Same(expectedState, state);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_ReturnsCachedStateFromStateBagOnSecondCall()
|
||||
{
|
||||
// Arrange
|
||||
var callCount = 0;
|
||||
var provider = new TestChatHistoryProvider(_ =>
|
||||
{
|
||||
callCount++;
|
||||
return new TestState { Value = $"init-{callCount}" };
|
||||
});
|
||||
var session = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state1 = provider.GetState(session);
|
||||
var state2 = provider.GetState(session);
|
||||
|
||||
// Assert - initializer called only once; second call reads from StateBag
|
||||
Assert.Equal(1, callCount);
|
||||
Assert.Equal("init-1", state1.Value);
|
||||
Assert.Equal("init-1", state2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_WorksWhenSessionIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider(_ => new TestState { Value = "no-session" });
|
||||
|
||||
// Act
|
||||
var state = provider.GetState(null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("no-session", state.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_ReInitializesWhenSessionIsNull()
|
||||
{
|
||||
// Arrange - without a session, state can't be cached in StateBag
|
||||
var callCount = 0;
|
||||
var provider = new TestChatHistoryProvider(_ =>
|
||||
{
|
||||
callCount++;
|
||||
return new TestState { Value = $"init-{callCount}" };
|
||||
});
|
||||
|
||||
// Act
|
||||
_ = provider.GetState(null);
|
||||
provider.GetState(null);
|
||||
|
||||
// Assert - initializer called each time since there's no session to cache in
|
||||
Assert.Equal(2, callCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SaveState Tests
|
||||
|
||||
[Fact]
|
||||
public void SaveState_SavesToStateBag()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider(_ => new TestState());
|
||||
var session = new TestAgentSession();
|
||||
var state = new TestState { Value = "saved" };
|
||||
|
||||
// Act
|
||||
provider.DoSaveState(session, state);
|
||||
var retrieved = provider.GetState(session);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("saved", retrieved.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveState_NoOpWhenSessionIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider(_ => new TestState { Value = "default" });
|
||||
|
||||
// Act - should not throw
|
||||
provider.DoSaveState(null, new TestState { Value = "saved" });
|
||||
|
||||
// Assert - no exception; can't verify further without a session
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StateKey Tests
|
||||
|
||||
[Fact]
|
||||
public void StateKey_DefaultsToTypeName()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider(_ => new TestState());
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(nameof(TestChatHistoryProvider), provider.StateKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StateKey_UsesCustomKeyWhenProvided()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider(_ => new TestState(), stateKey: "custom-key");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("custom-key", provider.StateKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Integration Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingCoreAsync_CanUseStateInProvideChatHistoryAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider(_ => new TestState { Value = "state-value" });
|
||||
var session = new TestAgentSession();
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Hi")]);
|
||||
|
||||
// Act
|
||||
var result = (await provider.InvokingAsync(context)).ToList();
|
||||
|
||||
// Assert - the provider uses state to produce history messages
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains("state-value", result[0].Text);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public sealed class TestState
|
||||
{
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private sealed class TestChatHistoryProvider : ChatHistoryProvider<TestState>
|
||||
{
|
||||
public TestChatHistoryProvider(
|
||||
Func<AgentSession?, TestState> stateInitializer,
|
||||
string? stateKey = null)
|
||||
: base(stateInitializer, stateKey, null, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public TestState GetState(AgentSession? session) => this.GetOrInitializeState(session);
|
||||
|
||||
public void DoSaveState(AgentSession? session, TestState state) => this.SaveState(session, state);
|
||||
|
||||
protected override ValueTask<IEnumerable<ChatMessage>> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var state = this.GetOrInitializeState(context.Session);
|
||||
return new(new[] { new ChatMessage(ChatRole.System, $"History from state: {state.Value}") });
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestAgentSession : AgentSession;
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="ProviderSessionState{TState}"/> class.
|
||||
/// </summary>
|
||||
public class ProviderSessionStateTests
|
||||
{
|
||||
#region GetOrInitializeState Tests
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_InitializesFromStateInitializerOnFirstCall()
|
||||
{
|
||||
// Arrange
|
||||
var expectedState = new TestState { Value = "initialized" };
|
||||
var sessionState = new ProviderSessionState<TestState>(_ => expectedState, "test-key");
|
||||
var session = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state = sessionState.GetOrInitializeState(session);
|
||||
|
||||
// Assert
|
||||
Assert.Same(expectedState, state);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_ReturnsCachedStateFromStateBagOnSecondCall()
|
||||
{
|
||||
// Arrange
|
||||
var callCount = 0;
|
||||
var sessionState = new ProviderSessionState<TestState>(_ =>
|
||||
{
|
||||
callCount++;
|
||||
return new TestState { Value = $"init-{callCount}" };
|
||||
}, "test-key");
|
||||
var session = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state1 = sessionState.GetOrInitializeState(session);
|
||||
var state2 = sessionState.GetOrInitializeState(session);
|
||||
|
||||
// Assert - initializer called only once; second call reads from StateBag
|
||||
Assert.Equal(1, callCount);
|
||||
Assert.Equal("init-1", state1.Value);
|
||||
Assert.Equal("init-1", state2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_WorksWhenSessionIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var sessionState = new ProviderSessionState<TestState>(_ => new TestState { Value = "no-session" }, "test-key");
|
||||
|
||||
// Act
|
||||
var state = sessionState.GetOrInitializeState(null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("no-session", state.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_ReInitializesWhenSessionIsNull()
|
||||
{
|
||||
// Arrange - without a session, state can't be cached in StateBag
|
||||
var callCount = 0;
|
||||
var sessionState = new ProviderSessionState<TestState>(_ =>
|
||||
{
|
||||
callCount++;
|
||||
return new TestState { Value = $"init-{callCount}" };
|
||||
}, "test-key");
|
||||
|
||||
// Act
|
||||
sessionState.GetOrInitializeState(null);
|
||||
sessionState.GetOrInitializeState(null);
|
||||
|
||||
// Assert - initializer called each time since there's no session to cache in
|
||||
Assert.Equal(2, callCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SaveState Tests
|
||||
|
||||
[Fact]
|
||||
public void SaveState_SavesToStateBag()
|
||||
{
|
||||
// Arrange
|
||||
var sessionState = new ProviderSessionState<TestState>(_ => new TestState(), "test-key");
|
||||
var session = new TestAgentSession();
|
||||
var state = new TestState { Value = "saved" };
|
||||
|
||||
// Act
|
||||
sessionState.SaveState(session, state);
|
||||
var retrieved = sessionState.GetOrInitializeState(session);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("saved", retrieved.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveState_NoOpWhenSessionIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var sessionState = new ProviderSessionState<TestState>(_ => new TestState { Value = "default" }, "test-key");
|
||||
|
||||
// Act - should not throw
|
||||
sessionState.SaveState(null, new TestState { Value = "saved" });
|
||||
|
||||
// Assert - no exception; can't verify further without a session
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StateKey Tests
|
||||
|
||||
[Fact]
|
||||
public void StateKey_UsesProvidedKey()
|
||||
{
|
||||
// Arrange
|
||||
var sessionState = new ProviderSessionState<TestState>(_ => new TestState(), "my-provider-key");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("my-provider-key", sessionState.StateKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StateKey_UsesCustomKeyWhenProvided()
|
||||
{
|
||||
// Arrange
|
||||
var sessionState = new ProviderSessionState<TestState>(_ => new TestState(), "custom-key");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("custom-key", sessionState.StateKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Isolation Tests
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_IsolatesStateBetweenDifferentKeys()
|
||||
{
|
||||
// Arrange
|
||||
var sessionState1 = new ProviderSessionState<TestState>(_ => new TestState { Value = "state-1" }, "key-1");
|
||||
var sessionState2 = new ProviderSessionState<TestState>(_ => new TestState { Value = "state-2" }, "key-2");
|
||||
var session = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state1 = sessionState1.GetOrInitializeState(session);
|
||||
var state2 = sessionState2.GetOrInitializeState(session);
|
||||
|
||||
// Assert - each key maintains independent state
|
||||
Assert.Equal("state-1", state1.Value);
|
||||
Assert.Equal("state-2", state2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrInitializeState_IsolatesStateBetweenDifferentSessions()
|
||||
{
|
||||
// Arrange
|
||||
var callCount = 0;
|
||||
var sessionState = new ProviderSessionState<TestState>(_ =>
|
||||
{
|
||||
callCount++;
|
||||
return new TestState { Value = $"init-{callCount}" };
|
||||
}, "test-key");
|
||||
var session1 = new TestAgentSession();
|
||||
var session2 = new TestAgentSession();
|
||||
|
||||
// Act
|
||||
var state1 = sessionState.GetOrInitializeState(session1);
|
||||
var state2 = sessionState.GetOrInitializeState(session2);
|
||||
|
||||
// Assert - each session gets its own state
|
||||
Assert.Equal(2, callCount);
|
||||
Assert.Equal("init-1", state1.Value);
|
||||
Assert.Equal("init-2", state2.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public sealed class TestState
|
||||
{
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private sealed class TestAgentSession : AgentSession;
|
||||
}
|
||||
Reference in New Issue
Block a user