mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
0e2fcb1c7f
* Add Azure AI Foundry Memory Context Provider with unit tests * Add FoundryMemory integration tests and sample application * Fix ClearStoredMemoriesAsync to handle 404 gracefully and rename to EnsureStoredMemoriesDeletedAsync * Refactor FoundryMemory: simplify architecture and add memory store creation - Remove IFoundryMemoryOperations interface (was only for test mocking) - Remove AIProjectClientMemoryOperations wrapper class - Provider now directly uses AIProjectClient with internal extension methods - Extension methods return actual response models instead of extracted values - Remove WaitForUpdateCompletionAsync from provider (sample uses delay) - Simplify EnsureMemoryStoreCreatedAsync to return Task instead of Task<bool> - Add memory store creation with chat_model and embedding_model - Add UpdateMemoriesResponse with SupersededBy and Error fields - Simplify unit tests to focus on constructor validation and serialization - Update sample to use simple delay for memory processing wait * Add waiting operation for memory store updates * Fix UTF-8 BOM encoding for FoundryMemory csproj files * Update copilot instructions for UTF-8 BOM and fix sample API rename * Fix UTF-8 BOM encoding for TestableAIProjectClient.cs * Add missing response headers for TS * Changing default embedding * Using the SDK Models * Program update * Remove debugging code from sample * Adapt FoundryMemoryProvider to new AIContextProvider API and add UTF-8 BOM instruction - Override ProvideAIContextAsync/StoreAIContextAsync instead of removed virtual InvokingAsync/InvokedAsync - Use ProviderSessionState<State> for session-scoped state management (matching Mem0Provider pattern) - Replace constructor-based scope with stateInitializer delegate - Remove Serialize method (no longer on base class) - Add SearchInputMessageFilter, StorageInputMessageFilter, StateKey to options - Update sample to use AIContextProviders list instead of AIContextProviderFactory - Update unit and integration tests for new API - Add UTF-8 BOM encoding and --tl:off instructions to dotnet/AGENTS.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use DefaultAzureCredential in Foundry Memory sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review comments for FoundryMemoryProvider - Move memoryStoreName from options to required constructor parameter - Make FoundryMemoryProviderScope require non-null/whitespace scope in constructor - Make Scope property read-only (getter only) - Replace ConcurrentQueue with single last update ID to fix memory leak - Only clear pending update ID after successful completion - Add delete success logging - Mark FoundryMemoryProvider with [Experimental] attribute - Update unit tests for new API signatures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use Throw.IfNullOrWhitespace for scope and memoryStoreName validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
131 lines
4.3 KiB
C#
131 lines
4.3 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
|
|
namespace Microsoft.Agents.AI.FoundryMemory.UnitTests;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="FoundryMemoryProvider"/> constructor validation.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Since <see cref="FoundryMemoryProvider"/> directly uses <see cref="Azure.AI.Projects.AIProjectClient"/>,
|
|
/// integration tests are used to verify the memory operations. These unit tests focus on:
|
|
/// - Constructor parameter validation
|
|
/// - State initializer validation
|
|
/// </remarks>
|
|
public sealed class FoundryMemoryProviderTests
|
|
{
|
|
[Fact]
|
|
public void Constructor_Throws_WhenClientIsNull()
|
|
{
|
|
// Act & Assert
|
|
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => new FoundryMemoryProvider(
|
|
null!,
|
|
"store",
|
|
stateInitializer: _ => new(new FoundryMemoryProviderScope("test"))));
|
|
Assert.Equal("client", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_Throws_WhenStateInitializerIsNull()
|
|
{
|
|
// Arrange
|
|
using TestableAIProjectClient testClient = new();
|
|
|
|
// Act & Assert
|
|
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => new FoundryMemoryProvider(
|
|
testClient.Client,
|
|
"store",
|
|
stateInitializer: null!));
|
|
Assert.Equal("stateInitializer", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_Throws_WhenMemoryStoreNameIsEmpty()
|
|
{
|
|
// Arrange
|
|
using TestableAIProjectClient testClient = new();
|
|
|
|
// Act & Assert
|
|
ArgumentException ex = Assert.Throws<ArgumentException>(() => new FoundryMemoryProvider(
|
|
testClient.Client,
|
|
"",
|
|
stateInitializer: _ => new(new FoundryMemoryProviderScope("test"))));
|
|
Assert.Equal("memoryStoreName", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_Throws_WhenMemoryStoreNameIsNull()
|
|
{
|
|
// Arrange
|
|
using TestableAIProjectClient testClient = new();
|
|
|
|
// Act & Assert
|
|
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => new FoundryMemoryProvider(
|
|
testClient.Client,
|
|
null!,
|
|
stateInitializer: _ => new(new FoundryMemoryProviderScope("test"))));
|
|
Assert.Equal("memoryStoreName", ex.ParamName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Scope_Throws_WhenScopeIsNull()
|
|
{
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => new FoundryMemoryProviderScope(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void Scope_Throws_WhenScopeIsEmpty()
|
|
{
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() => new FoundryMemoryProviderScope(""));
|
|
}
|
|
|
|
[Fact]
|
|
public void StateInitializer_Throws_WhenScopeIsNull()
|
|
{
|
|
// Arrange
|
|
using TestableAIProjectClient testClient = new();
|
|
FoundryMemoryProvider sut = new(
|
|
testClient.Client,
|
|
"store",
|
|
stateInitializer: _ => new(null!));
|
|
|
|
// Act & Assert - state initializer validation is deferred to first use
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
{
|
|
// Force state initialization by creating a session-like scenario
|
|
// The validation happens inside the ValidateStateInitializer wrapper
|
|
try
|
|
{
|
|
// The stateInitializer wraps with validation, so calling it will throw
|
|
var field = typeof(FoundryMemoryProvider).GetField("_sessionState", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
var sessionState = field!.GetValue(sut);
|
|
var method = sessionState!.GetType().GetMethod("GetOrInitializeState");
|
|
method!.Invoke(sessionState, [null]);
|
|
}
|
|
catch (System.Reflection.TargetInvocationException tie) when (tie.InnerException is not null)
|
|
{
|
|
throw tie.InnerException;
|
|
}
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_Succeeds_WithValidParameters()
|
|
{
|
|
// Arrange
|
|
using TestableAIProjectClient testClient = new();
|
|
|
|
// Act
|
|
FoundryMemoryProvider sut = new(
|
|
testClient.Client,
|
|
"my-store",
|
|
stateInitializer: _ => new(new FoundryMemoryProviderScope("user-456")));
|
|
|
|
// Assert
|
|
Assert.NotNull(sut);
|
|
}
|
|
}
|