mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Update AIContextProviders to use Microsoft.Extensions.Compliance.Redaction (#4854)
* Update providers to use Microsoft.Extensions.Compliance.Redaction * Fix formatting. * Fix readme
This commit is contained in:
committed by
GitHub
Unverified
parent
cc85bbc2dc
commit
2c000b032d
@@ -70,6 +70,7 @@
|
|||||||
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.3.0-preview.1.26109.11" />
|
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.3.0-preview.1.26109.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.4.0" />
|
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.4.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||||
|
<PackageVersion Include="Microsoft.Extensions.Compliance.Abstractions" Version="10.4.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
|
||||||
|
|||||||
@@ -452,6 +452,10 @@
|
|||||||
<File Path="src/Shared/Samples/TextOutputHelperExtensions.cs" />
|
<File Path="src/Shared/Samples/TextOutputHelperExtensions.cs" />
|
||||||
<File Path="src/Shared/Samples/XunitLogger.cs" />
|
<File Path="src/Shared/Samples/XunitLogger.cs" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
<Folder Name="/Solution Items/src/Shared/Redaction/">
|
||||||
|
<File Path="src/Shared/Redaction/README.md" />
|
||||||
|
<File Path="src/Shared/Redaction/ReplacingRedactor.cs" />
|
||||||
|
</Folder>
|
||||||
<Folder Name="/Solution Items/src/Shared/Throw/">
|
<Folder Name="/Solution Items/src/Shared/Throw/">
|
||||||
<File Path="src/Shared/Throw/README.md" />
|
<File Path="src/Shared/Throw/README.md" />
|
||||||
<File Path="src/Shared/Throw/Throw.cs" />
|
<File Path="src/Shared/Throw/Throw.cs" />
|
||||||
|
|||||||
@@ -29,4 +29,7 @@
|
|||||||
<ItemGroup Condition="'$(InjectSharedDiagnosticIds)' == 'true'">
|
<ItemGroup Condition="'$(InjectSharedDiagnosticIds)' == 'true'">
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\DiagnosticIds\*.cs" LinkBase="Shared\DiagnosticIds" />
|
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\DiagnosticIds\*.cs" LinkBase="Shared\DiagnosticIds" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(InjectSharedRedaction)' == 'true'">
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Redaction\*.cs" LinkBase="Shared\Redaction" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Azure.AI.Projects;
|
using Azure.AI.Projects;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Shared.DiagnosticIds;
|
using Microsoft.Shared.DiagnosticIds;
|
||||||
using Microsoft.Shared.Diagnostics;
|
using Microsoft.Shared.Diagnostics;
|
||||||
@@ -37,7 +38,7 @@ public sealed class FoundryMemoryProvider : AIContextProvider
|
|||||||
private readonly string _memoryStoreName;
|
private readonly string _memoryStoreName;
|
||||||
private readonly int _maxMemories;
|
private readonly int _maxMemories;
|
||||||
private readonly int _updateDelay;
|
private readonly int _updateDelay;
|
||||||
private readonly bool _enableSensitiveTelemetryData;
|
private readonly Redactor _redactor;
|
||||||
|
|
||||||
private readonly AIProjectClient _client;
|
private readonly AIProjectClient _client;
|
||||||
private readonly ILogger<FoundryMemoryProvider>? _logger;
|
private readonly ILogger<FoundryMemoryProvider>? _logger;
|
||||||
@@ -79,7 +80,7 @@ public sealed class FoundryMemoryProvider : AIContextProvider
|
|||||||
this._memoryStoreName = memoryStoreName;
|
this._memoryStoreName = memoryStoreName;
|
||||||
this._maxMemories = effectiveOptions.MaxMemories;
|
this._maxMemories = effectiveOptions.MaxMemories;
|
||||||
this._updateDelay = effectiveOptions.UpdateDelay;
|
this._updateDelay = effectiveOptions.UpdateDelay;
|
||||||
this._enableSensitiveTelemetryData = effectiveOptions.EnableSensitiveTelemetryData;
|
this._redactor = effectiveOptions.EnableSensitiveTelemetryData ? NullRedactor.Instance : (effectiveOptions.Redactor ?? new ReplacingRedactor("<redacted>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -416,7 +417,7 @@ public sealed class FoundryMemoryProvider : AIContextProvider
|
|||||||
private static bool IsAllowedRole(ChatRole role) =>
|
private static bool IsAllowedRole(ChatRole role) =>
|
||||||
role == ChatRole.User || role == ChatRole.Assistant || role == ChatRole.System;
|
role == ChatRole.User || role == ChatRole.Assistant || role == ChatRole.System;
|
||||||
|
|
||||||
private string? SanitizeLogData(string? data) => this._enableSensitiveTelemetryData ? data : "<redacted>";
|
private string SanitizeLogData(string? data) => this._redactor.Redact(data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the state of a <see cref="FoundryMemoryProvider"/> stored in the <see cref="AgentSession.StateBag"/>.
|
/// Represents the state of a <see cref="FoundryMemoryProvider"/> stored in the <see cref="AgentSession.StateBag"/>.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
|
|
||||||
namespace Microsoft.Agents.AI.FoundryMemory;
|
namespace Microsoft.Agents.AI.FoundryMemory;
|
||||||
|
|
||||||
@@ -37,8 +38,22 @@ public sealed class FoundryMemoryProviderOptions
|
|||||||
/// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
|
/// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>Defaults to <see langword="false"/>.</value>
|
/// <value>Defaults to <see langword="false"/>.</value>
|
||||||
|
/// <remarks>
|
||||||
|
/// When set to <see langword="true"/>, sensitive data is passed through to logs unchanged and any
|
||||||
|
/// configured <see cref="Redactor"/> is ignored. This property takes precedence over <see cref="Redactor"/>.
|
||||||
|
/// </remarks>
|
||||||
public bool EnableSensitiveTelemetryData { get; set; }
|
public bool EnableSensitiveTelemetryData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a custom <see cref="Redactor"/> used to redact sensitive data in log output.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// When <see langword="null"/> (the default), sensitive data is replaced with a placeholder.
|
||||||
|
/// When set, this redactor is used to transform sensitive values before they are logged.
|
||||||
|
/// Ignored when <see cref="EnableSensitiveTelemetryData"/> is <see langword="true"/>.
|
||||||
|
/// </value>
|
||||||
|
public Redactor? Redactor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the key used to store the provider state in the session's <see cref="AgentSessionStateBag"/>.
|
/// Gets or sets the key used to store the provider state in the session's <see cref="AgentSessionStateBag"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<InjectSharedThrow>true</InjectSharedThrow>
|
<InjectSharedThrow>true</InjectSharedThrow>
|
||||||
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
|
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
|
||||||
|
<InjectSharedRedaction>true</InjectSharedRedaction>
|
||||||
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
|
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
|
||||||
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
|
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Azure.AI.Projects" />
|
<PackageReference Include="Azure.AI.Projects" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Compliance.Abstractions" />
|
||||||
<PackageReference Include="OpenAI" />
|
<PackageReference Include="OpenAI" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Text.Json.Serialization;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Shared.Diagnostics;
|
using Microsoft.Shared.Diagnostics;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public sealed class Mem0Provider : MessageAIContextProvider
|
|||||||
private readonly ProviderSessionState<State> _sessionState;
|
private readonly ProviderSessionState<State> _sessionState;
|
||||||
private IReadOnlyList<string>? _stateKeys;
|
private IReadOnlyList<string>? _stateKeys;
|
||||||
private readonly string _contextPrompt;
|
private readonly string _contextPrompt;
|
||||||
private readonly bool _enableSensitiveTelemetryData;
|
private readonly Redactor _redactor;
|
||||||
|
|
||||||
private readonly Mem0Client _client;
|
private readonly Mem0Client _client;
|
||||||
private readonly ILogger<Mem0Provider>? _logger;
|
private readonly ILogger<Mem0Provider>? _logger;
|
||||||
@@ -91,7 +92,7 @@ public sealed class Mem0Provider : MessageAIContextProvider
|
|||||||
this._client = new Mem0Client(httpClient);
|
this._client = new Mem0Client(httpClient);
|
||||||
|
|
||||||
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
||||||
this._enableSensitiveTelemetryData = options?.EnableSensitiveTelemetryData ?? false;
|
this._redactor = options?.EnableSensitiveTelemetryData == true ? NullRedactor.Instance : (options?.Redactor ?? new ReplacingRedactor("<redacted>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -297,5 +298,5 @@ public sealed class Mem0Provider : MessageAIContextProvider
|
|||||||
public Mem0ProviderScope SearchScope { get; }
|
public Mem0ProviderScope SearchScope { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? SanitizeLogData(string? data) => this._enableSensitiveTelemetryData ? data : "<redacted>";
|
private string SanitizeLogData(string? data) => this._redactor.Redact(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
|
|
||||||
namespace Microsoft.Agents.AI.Mem0;
|
namespace Microsoft.Agents.AI.Mem0;
|
||||||
|
|
||||||
@@ -21,8 +22,22 @@ public sealed class Mem0ProviderOptions
|
|||||||
/// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
|
/// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>Defaults to <see langword="false"/>.</value>
|
/// <value>Defaults to <see langword="false"/>.</value>
|
||||||
|
/// <remarks>
|
||||||
|
/// When set to <see langword="true"/>, sensitive data is passed through to logs unchanged and any
|
||||||
|
/// configured <see cref="Redactor"/> is ignored. This property takes precedence over <see cref="Redactor"/>.
|
||||||
|
/// </remarks>
|
||||||
public bool EnableSensitiveTelemetryData { get; set; }
|
public bool EnableSensitiveTelemetryData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a custom <see cref="Redactor"/> used to redact sensitive data in log output.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// When <see langword="null"/> (the default), sensitive data is replaced with a placeholder.
|
||||||
|
/// When set, this redactor is used to transform sensitive values before they are logged.
|
||||||
|
/// Ignored when <see cref="EnableSensitiveTelemetryData"/> is <see langword="true"/>.
|
||||||
|
/// </value>
|
||||||
|
public Redactor? Redactor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the key used to store the provider state in the session's <see cref="AgentSessionStateBag"/>.
|
/// Gets or sets the key used to store the provider state in the session's <see cref="AgentSessionStateBag"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<InjectSharedThrow>true</InjectSharedThrow>
|
<InjectSharedThrow>true</InjectSharedThrow>
|
||||||
|
<InjectSharedRedaction>true</InjectSharedRedaction>
|
||||||
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
|
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -23,6 +24,10 @@
|
|||||||
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Compliance.Abstractions" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- NuGet Package Settings -->
|
<!-- NuGet Package Settings -->
|
||||||
<Title>Microsoft Agent Framework - Mem0 integration</Title>
|
<Title>Microsoft Agent Framework - Mem0 integration</Title>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Linq.Expressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.VectorData;
|
using Microsoft.Extensions.VectorData;
|
||||||
using Microsoft.Shared.Diagnostics;
|
using Microsoft.Shared.Diagnostics;
|
||||||
@@ -80,7 +81,7 @@ public sealed class ChatHistoryMemoryProvider : MessageAIContextProvider, IDispo
|
|||||||
private readonly VectorStoreCollection<object, Dictionary<string, object?>> _collection;
|
private readonly VectorStoreCollection<object, Dictionary<string, object?>> _collection;
|
||||||
private readonly int _maxResults;
|
private readonly int _maxResults;
|
||||||
private readonly string _contextPrompt;
|
private readonly string _contextPrompt;
|
||||||
private readonly bool _enableSensitiveTelemetryData;
|
private readonly Redactor _redactor;
|
||||||
private readonly ChatHistoryMemoryProviderOptions.SearchBehavior _searchTime;
|
private readonly ChatHistoryMemoryProviderOptions.SearchBehavior _searchTime;
|
||||||
private readonly string _toolName;
|
private readonly string _toolName;
|
||||||
private readonly string _toolDescription;
|
private readonly string _toolDescription;
|
||||||
@@ -118,7 +119,7 @@ public sealed class ChatHistoryMemoryProvider : MessageAIContextProvider, IDispo
|
|||||||
options ??= new ChatHistoryMemoryProviderOptions();
|
options ??= new ChatHistoryMemoryProviderOptions();
|
||||||
this._maxResults = options.MaxResults.HasValue ? Throw.IfLessThanOrEqual(options.MaxResults.Value, 0) : DefaultMaxResults;
|
this._maxResults = options.MaxResults.HasValue ? Throw.IfLessThanOrEqual(options.MaxResults.Value, 0) : DefaultMaxResults;
|
||||||
this._contextPrompt = options.ContextPrompt ?? DefaultContextPrompt;
|
this._contextPrompt = options.ContextPrompt ?? DefaultContextPrompt;
|
||||||
this._enableSensitiveTelemetryData = options.EnableSensitiveTelemetryData;
|
this._redactor = options.EnableSensitiveTelemetryData ? NullRedactor.Instance : (options.Redactor ?? new ReplacingRedactor("<redacted>"));
|
||||||
this._searchTime = options.SearchTime;
|
this._searchTime = options.SearchTime;
|
||||||
this._logger = loggerFactory?.CreateLogger<ChatHistoryMemoryProvider>();
|
this._logger = loggerFactory?.CreateLogger<ChatHistoryMemoryProvider>();
|
||||||
this._toolName = options.FunctionToolName ?? DefaultFunctionToolName;
|
this._toolName = options.FunctionToolName ?? DefaultFunctionToolName;
|
||||||
@@ -485,7 +486,7 @@ public sealed class ChatHistoryMemoryProvider : MessageAIContextProvider, IDispo
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? SanitizeLogData(string? data) => this._enableSensitiveTelemetryData ? data : "<redacted>";
|
private string SanitizeLogData(string? data) => this._redactor.Redact(data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rebinds a filter expression's body to use the specified shared parameter,
|
/// Rebinds a filter expression's body to use the specified shared parameter,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
|
|
||||||
namespace Microsoft.Agents.AI;
|
namespace Microsoft.Agents.AI;
|
||||||
|
|
||||||
@@ -46,8 +47,22 @@ public sealed class ChatHistoryMemoryProviderOptions
|
|||||||
/// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
|
/// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>Defaults to <see langword="false"/>.</value>
|
/// <value>Defaults to <see langword="false"/>.</value>
|
||||||
|
/// <remarks>
|
||||||
|
/// When set to <see langword="true"/>, sensitive data is passed through to logs unchanged and any
|
||||||
|
/// configured <see cref="Redactor"/> is ignored. This property takes precedence over <see cref="Redactor"/>.
|
||||||
|
/// </remarks>
|
||||||
public bool EnableSensitiveTelemetryData { get; set; }
|
public bool EnableSensitiveTelemetryData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a custom <see cref="Redactor"/> used to redact sensitive data in log output.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// When <see langword="null"/> (the default), sensitive data is replaced with a placeholder.
|
||||||
|
/// When set, this redactor is used to transform sensitive values before they are logged.
|
||||||
|
/// Ignored when <see cref="EnableSensitiveTelemetryData"/> is <see langword="true"/>.
|
||||||
|
/// </value>
|
||||||
|
public Redactor? Redactor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the key used to store provider state in the <see cref="AgentSession.StateBag"/>.
|
/// Gets or sets the key used to store provider state in the <see cref="AgentSession.StateBag"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<InjectSharedThrow>true</InjectSharedThrow>
|
<InjectSharedThrow>true</InjectSharedThrow>
|
||||||
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
|
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
|
||||||
|
<InjectSharedRedaction>true</InjectSharedRedaction>
|
||||||
<InjectDiagnosticClassesOnLegacy>true</InjectDiagnosticClassesOnLegacy>
|
<InjectDiagnosticClassesOnLegacy>true</InjectDiagnosticClassesOnLegacy>
|
||||||
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
|
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
|
||||||
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
|
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
<PackageReference Include="Microsoft.Extensions.AI" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Compliance.Abstractions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.VectorData.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.VectorData.Abstractions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Shared.Diagnostics;
|
using Microsoft.Shared.Diagnostics;
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ public sealed class TextSearchProvider : MessageAIContextProvider
|
|||||||
private readonly string _contextPrompt;
|
private readonly string _contextPrompt;
|
||||||
private readonly string _citationsPrompt;
|
private readonly string _citationsPrompt;
|
||||||
private readonly Func<IList<TextSearchResult>, string>? _contextFormatter;
|
private readonly Func<IList<TextSearchResult>, string>? _contextFormatter;
|
||||||
|
private readonly Redactor _redactor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TextSearchProvider"/> class.
|
/// Initializes a new instance of the <see cref="TextSearchProvider"/> class.
|
||||||
@@ -89,6 +91,7 @@ public sealed class TextSearchProvider : MessageAIContextProvider
|
|||||||
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
||||||
this._citationsPrompt = options?.CitationsPrompt ?? DefaultCitationsPrompt;
|
this._citationsPrompt = options?.CitationsPrompt ?? DefaultCitationsPrompt;
|
||||||
this._contextFormatter = options?.ContextFormatter;
|
this._contextFormatter = options?.ContextFormatter;
|
||||||
|
this._redactor = options?.EnableSensitiveTelemetryData == true ? NullRedactor.Instance : (options?.Redactor ?? new ReplacingRedactor("<redacted>"));
|
||||||
|
|
||||||
// Create the on-demand search tool (only used if behavior is OnDemandFunctionCalling)
|
// Create the on-demand search tool (only used if behavior is OnDemandFunctionCalling)
|
||||||
this._tools =
|
this._tools =
|
||||||
@@ -180,7 +183,7 @@ public sealed class TextSearchProvider : MessageAIContextProvider
|
|||||||
|
|
||||||
if (this._logger?.IsEnabled(LogLevel.Trace) is true)
|
if (this._logger?.IsEnabled(LogLevel.Trace) is true)
|
||||||
{
|
{
|
||||||
this._logger.LogTrace("TextSearchProvider: Search Results\nInput:{Input}\nOutput:{MessageText}", input, formatted);
|
this._logger.LogTrace("TextSearchProvider: Search Results\nInput:{Input}\nOutput:{MessageText}", this.SanitizeLogData(input), this.SanitizeLogData(formatted));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [new ChatMessage(ChatRole.User, formatted)];
|
return [new ChatMessage(ChatRole.User, formatted)];
|
||||||
@@ -249,7 +252,7 @@ public sealed class TextSearchProvider : MessageAIContextProvider
|
|||||||
|
|
||||||
if (this._logger.IsEnabled(LogLevel.Trace))
|
if (this._logger.IsEnabled(LogLevel.Trace))
|
||||||
{
|
{
|
||||||
this._logger.LogTrace("TextSearchProvider Input:{UserQuestion}\nOutput:{MessageText}", userQuestion, outputText);
|
this._logger.LogTrace("TextSearchProvider Input:{UserQuestion}\nOutput:{MessageText}", this.SanitizeLogData(userQuestion), this.SanitizeLogData(outputText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,6 +328,8 @@ public sealed class TextSearchProvider : MessageAIContextProvider
|
|||||||
public object? RawRepresentation { get; set; }
|
public object? RawRepresentation { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string SanitizeLogData(string? data) => this._redactor.Redact(data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the per-session state of a <see cref="TextSearchProvider"/> stored in the <see cref="AgentSession.StateBag"/>.
|
/// Represents the per-session state of a <see cref="TextSearchProvider"/> stored in the <see cref="AgentSession.StateBag"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
|
|
||||||
namespace Microsoft.Agents.AI;
|
namespace Microsoft.Agents.AI;
|
||||||
|
|
||||||
@@ -117,6 +118,26 @@ public sealed class TextSearchProviderOptions
|
|||||||
/// </value>
|
/// </value>
|
||||||
public List<ChatRole>? RecentMessageRolesIncluded { get; set; }
|
public List<ChatRole>? RecentMessageRolesIncluded { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether sensitive data such as user queries and search results may appear in logs.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Defaults to <see langword="false"/>.</value>
|
||||||
|
/// <remarks>
|
||||||
|
/// When set to <see langword="true"/>, sensitive data is passed through to logs unchanged and any
|
||||||
|
/// configured <see cref="Redactor"/> is ignored. This property takes precedence over <see cref="Redactor"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public bool EnableSensitiveTelemetryData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a custom <see cref="Redactor"/> used to redact sensitive data in log output.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// When <see langword="null"/> (the default), sensitive data is replaced with a placeholder.
|
||||||
|
/// When set, this redactor is used to transform sensitive values before they are logged.
|
||||||
|
/// Ignored when <see cref="EnableSensitiveTelemetryData"/> is <see langword="true"/>.
|
||||||
|
/// </value>
|
||||||
|
public Redactor? Redactor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Behavior choices for the provider.
|
/// Behavior choices for the provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Redaction
|
||||||
|
|
||||||
|
Log data redaction utilities built on `Microsoft.Extensions.Compliance.Redaction.Redactor`.
|
||||||
|
|
||||||
|
Provides `ReplacingRedactor`, an internal `Redactor` implementation that replaces
|
||||||
|
any input with a fixed replacement string (e.g. `"<redacted>"`).
|
||||||
|
|
||||||
|
To use this in your project, add the following to your `.csproj` file:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PropertyGroup>
|
||||||
|
<InjectSharedRedaction>true</InjectSharedRedaction>
|
||||||
|
</PropertyGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
You will also need to add a package reference to `Microsoft.Extensions.Compliance.Abstractions`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Compliance.Abstractions" />
|
||||||
|
</ItemGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally, this also depends on the shared Throw class, so when using redaction, InjectSharedThrow should also be enabled:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PropertyGroup>
|
||||||
|
<InjectSharedThrow>true</InjectSharedThrow>
|
||||||
|
</PropertyGroup>
|
||||||
|
```
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.Compliance.Redaction;
|
||||||
|
using Microsoft.Shared.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.Agents.AI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Redactor"/> that replaces the entire input with a fixed replacement string.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ReplacingRedactor : Redactor
|
||||||
|
{
|
||||||
|
private readonly string _replacementText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ReplacingRedactor"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="replacementText">The text to substitute for any input value.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="replacementText"/> is <see langword="null"/>.</exception>
|
||||||
|
public ReplacingRedactor(string replacementText)
|
||||||
|
{
|
||||||
|
this._replacementText = Throw.IfNull(replacementText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetRedactedLength(ReadOnlySpan<char> input) => this._replacementText.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int Redact(ReadOnlySpan<char> source, Span<char> destination)
|
||||||
|
{
|
||||||
|
this._replacementText.AsSpan().CopyTo(destination);
|
||||||
|
return this._replacementText.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -148,11 +148,15 @@ public sealed class Mem0ProviderTests : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false, false, 4)]
|
[InlineData(false, false, false, 4)]
|
||||||
[InlineData(true, false, 4)]
|
[InlineData(false, false, true, 4)]
|
||||||
[InlineData(false, true, 2)]
|
[InlineData(true, false, false, 4)]
|
||||||
[InlineData(true, true, 2)]
|
[InlineData(true, false, true, 4)]
|
||||||
public async Task InvokingAsync_LogsUserIdBasedOnEnableSensitiveTelemetryDataAsync(bool enableSensitiveTelemetryData, bool requestThrows, int expectedLogInvocations)
|
[InlineData(false, true, false, 2)]
|
||||||
|
[InlineData(false, true, true, 2)]
|
||||||
|
[InlineData(true, true, false, 2)]
|
||||||
|
[InlineData(true, true, true, 2)]
|
||||||
|
public async Task InvokingAsync_RedactsLogDataBasedOnOptionsAsync(bool enableSensitiveTelemetryData, bool requestThrows, bool useCustomRedactor, int expectedLogInvocations)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
if (requestThrows)
|
if (requestThrows)
|
||||||
@@ -171,7 +175,11 @@ public sealed class Mem0ProviderTests : IDisposable
|
|||||||
ThreadId = "session",
|
ThreadId = "session",
|
||||||
UserId = "user"
|
UserId = "user"
|
||||||
};
|
};
|
||||||
var options = new Mem0ProviderOptions { EnableSensitiveTelemetryData = enableSensitiveTelemetryData };
|
var options = new Mem0ProviderOptions
|
||||||
|
{
|
||||||
|
EnableSensitiveTelemetryData = enableSensitiveTelemetryData,
|
||||||
|
Redactor = useCustomRedactor ? new ReplacingRedactor("***") : null
|
||||||
|
};
|
||||||
var mockSession = new TestAgentSession();
|
var mockSession = new TestAgentSession();
|
||||||
|
|
||||||
var sut = new Mem0Provider(this._httpClient, _ => new Mem0Provider.State(storageScope), options: options, loggerFactory: this._loggerFactoryMock.Object);
|
var sut = new Mem0Provider(this._httpClient, _ => new Mem0Provider.State(storageScope), options: options, loggerFactory: this._loggerFactoryMock.Object);
|
||||||
@@ -180,7 +188,8 @@ public sealed class Mem0ProviderTests : IDisposable
|
|||||||
// Act
|
// Act
|
||||||
await sut.InvokingAsync(invokingContext, CancellationToken.None);
|
await sut.InvokingAsync(invokingContext, CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert — EnableSensitiveTelemetryData takes precedence over Redactor
|
||||||
|
string expectedRedaction = enableSensitiveTelemetryData ? "user" : (useCustomRedactor ? "***" : "<redacted>");
|
||||||
Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count);
|
Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count);
|
||||||
foreach (var logInvocation in this._loggerMock.Invocations)
|
foreach (var logInvocation in this._loggerMock.Invocations)
|
||||||
{
|
{
|
||||||
@@ -191,18 +200,18 @@ public sealed class Mem0ProviderTests : IDisposable
|
|||||||
|
|
||||||
var state = Assert.IsType<IReadOnlyList<KeyValuePair<string, object?>>>(logInvocation.Arguments[2], exactMatch: false);
|
var state = Assert.IsType<IReadOnlyList<KeyValuePair<string, object?>>>(logInvocation.Arguments[2], exactMatch: false);
|
||||||
var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
|
var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
|
||||||
Assert.Equal(enableSensitiveTelemetryData ? "user" : "<redacted>", userIdValue);
|
Assert.Equal(expectedRedaction, userIdValue);
|
||||||
|
|
||||||
var inputValue = state.FirstOrDefault(kvp => kvp.Key == "Input").Value;
|
var inputValue = state.FirstOrDefault(kvp => kvp.Key == "Input").Value;
|
||||||
if (inputValue != null)
|
if (inputValue != null)
|
||||||
{
|
{
|
||||||
Assert.Equal(enableSensitiveTelemetryData ? "Who am I?" : "<redacted>", inputValue);
|
Assert.Equal(enableSensitiveTelemetryData ? "Who am I?" : expectedRedaction, inputValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageTextValue = state.FirstOrDefault(kvp => kvp.Key == "MessageText").Value;
|
var messageTextValue = state.FirstOrDefault(kvp => kvp.Key == "MessageText").Value;
|
||||||
if (messageTextValue != null)
|
if (messageTextValue != null)
|
||||||
{
|
{
|
||||||
Assert.Equal(enableSensitiveTelemetryData ? "## Memories\nConsider the following memories when answering user questions:\nName is Caoimhe" : "<redacted>", messageTextValue);
|
Assert.Equal(enableSensitiveTelemetryData ? "## Memories\nConsider the following memories when answering user questions:\nName is Caoimhe" : expectedRedaction, messageTextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ public sealed class TextSearchProviderTests
|
|||||||
{
|
{
|
||||||
SearchTime = TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke,
|
SearchTime = TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke,
|
||||||
ContextPrompt = overrideContextPrompt,
|
ContextPrompt = overrideContextPrompt,
|
||||||
CitationsPrompt = overrideCitationsPrompt
|
CitationsPrompt = overrideCitationsPrompt,
|
||||||
|
EnableSensitiveTelemetryData = true
|
||||||
};
|
};
|
||||||
var provider = new TextSearchProvider(SearchDelegateAsync, options, withLogging ? this._loggerFactoryMock.Object : null);
|
var provider = new TextSearchProvider(SearchDelegateAsync, options, withLogging ? this._loggerFactoryMock.Object : null);
|
||||||
|
|
||||||
@@ -164,6 +165,65 @@ public sealed class TextSearchProviderTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false, false)]
|
||||||
|
[InlineData(false, true)]
|
||||||
|
[InlineData(true, false)]
|
||||||
|
[InlineData(true, true)]
|
||||||
|
public async Task InvokingAsync_RedactsLogDataBasedOnOptionsAsync(bool enableSensitiveTelemetryData, bool useCustomRedactor)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
List<TextSearchProvider.TextSearchResult> results =
|
||||||
|
[
|
||||||
|
new() { SourceName = "Doc1", SourceLink = "http://example.com/doc1", Text = "Content of Doc1" }
|
||||||
|
];
|
||||||
|
|
||||||
|
Task<IEnumerable<TextSearchProvider.TextSearchResult>> SearchDelegateAsync(string input, CancellationToken ct)
|
||||||
|
{
|
||||||
|
return Task.FromResult<IEnumerable<TextSearchProvider.TextSearchResult>>(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new TextSearchProviderOptions
|
||||||
|
{
|
||||||
|
SearchTime = TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke,
|
||||||
|
EnableSensitiveTelemetryData = enableSensitiveTelemetryData,
|
||||||
|
Redactor = useCustomRedactor ? new ReplacingRedactor("***") : null
|
||||||
|
};
|
||||||
|
var provider = new TextSearchProvider(SearchDelegateAsync, options, this._loggerFactoryMock.Object);
|
||||||
|
|
||||||
|
var invokingContext = new AIContextProvider.InvokingContext(
|
||||||
|
s_mockAgent,
|
||||||
|
new TestAgentSession(),
|
||||||
|
new AIContext { Messages = new List<ChatMessage> { new(ChatRole.User, "Sample user question?") } });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await provider.InvokingAsync(invokingContext, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert — EnableSensitiveTelemetryData takes precedence over Redactor
|
||||||
|
var traceInvocation = this._loggerMock.Invocations
|
||||||
|
.Where(i => i.Method.Name == nameof(ILogger.Log))
|
||||||
|
.FirstOrDefault(i => (LogLevel)i.Arguments[0]! == LogLevel.Trace);
|
||||||
|
Assert.NotNull(traceInvocation);
|
||||||
|
|
||||||
|
var state = Assert.IsType<IReadOnlyList<KeyValuePair<string, object?>>>(traceInvocation.Arguments[2], exactMatch: false);
|
||||||
|
var inputValue = state.First(kvp => kvp.Key == "Input").Value;
|
||||||
|
var messageTextValue = state.First(kvp => kvp.Key == "MessageText").Value;
|
||||||
|
|
||||||
|
if (enableSensitiveTelemetryData)
|
||||||
|
{
|
||||||
|
// EnableSensitiveTelemetryData=true: raw data passes through regardless of Redactor
|
||||||
|
Assert.Equal("Sample user question?", inputValue);
|
||||||
|
Assert.Contains("Content of Doc1", messageTextValue?.ToString()!);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// EnableSensitiveTelemetryData=false: custom redactor or default placeholder
|
||||||
|
string expectedRedaction = useCustomRedactor ? "***" : "<redacted>";
|
||||||
|
Assert.Equal(expectedRedaction, inputValue);
|
||||||
|
Assert.Equal(expectedRedaction, messageTextValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(null, null, "Search", "Allows searching for additional information to help answer the user question.")]
|
[InlineData(null, null, "Search", "Allows searching for additional information to help answer the user question.")]
|
||||||
[InlineData("CustomSearch", "CustomDescription", "CustomSearch", "CustomDescription")]
|
[InlineData("CustomSearch", "CustomDescription", "CustomSearch", "CustomDescription")]
|
||||||
|
|||||||
+30
-18
@@ -270,16 +270,21 @@ public class ChatHistoryMemoryProviderTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false, false, 0)]
|
[InlineData(false, false, false, 0)]
|
||||||
[InlineData(true, false, 0)]
|
[InlineData(false, false, true, 0)]
|
||||||
[InlineData(false, true, 2)]
|
[InlineData(true, false, false, 0)]
|
||||||
[InlineData(true, true, 2)]
|
[InlineData(true, false, true, 0)]
|
||||||
public async Task InvokedAsync_LogsUserIdBasedOnEnableSensitiveTelemetryDataAsync(bool enableSensitiveTelemetryData, bool requestThrows, int expectedLogInvocations)
|
[InlineData(false, true, false, 2)]
|
||||||
|
[InlineData(false, true, true, 2)]
|
||||||
|
[InlineData(true, true, false, 2)]
|
||||||
|
[InlineData(true, true, true, 2)]
|
||||||
|
public async Task InvokedAsync_RedactsLogDataBasedOnOptionsAsync(bool enableSensitiveTelemetryData, bool requestThrows, bool useCustomRedactor, int expectedLogInvocations)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var options = new ChatHistoryMemoryProviderOptions
|
var options = new ChatHistoryMemoryProviderOptions
|
||||||
{
|
{
|
||||||
EnableSensitiveTelemetryData = enableSensitiveTelemetryData
|
EnableSensitiveTelemetryData = enableSensitiveTelemetryData,
|
||||||
|
Redactor = useCustomRedactor ? new ReplacingRedactor("***") : null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requestThrows)
|
if (requestThrows)
|
||||||
@@ -309,7 +314,7 @@ public class ChatHistoryMemoryProviderTests
|
|||||||
// Act
|
// Act
|
||||||
await provider.InvokedAsync(invokedContext, CancellationToken.None);
|
await provider.InvokedAsync(invokedContext, CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert — EnableSensitiveTelemetryData takes precedence over Redactor
|
||||||
Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count);
|
Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count);
|
||||||
foreach (var logInvocation in this._loggerMock.Invocations)
|
foreach (var logInvocation in this._loggerMock.Invocations)
|
||||||
{
|
{
|
||||||
@@ -320,7 +325,8 @@ public class ChatHistoryMemoryProviderTests
|
|||||||
|
|
||||||
var state = Assert.IsType<IReadOnlyList<KeyValuePair<string, object?>>>(logInvocation.Arguments[2], exactMatch: false);
|
var state = Assert.IsType<IReadOnlyList<KeyValuePair<string, object?>>>(logInvocation.Arguments[2], exactMatch: false);
|
||||||
var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
|
var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
|
||||||
Assert.Equal(enableSensitiveTelemetryData ? "user1" : "<redacted>", userIdValue);
|
string expectedRedaction = enableSensitiveTelemetryData ? "user1" : (useCustomRedactor ? "***" : "<redacted>");
|
||||||
|
Assert.Equal(expectedRedaction, userIdValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,17 +532,22 @@ public class ChatHistoryMemoryProviderTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false, false, 2)]
|
[InlineData(false, false, false, 2)]
|
||||||
[InlineData(true, false, 2)]
|
[InlineData(false, false, true, 2)]
|
||||||
[InlineData(false, true, 2)]
|
[InlineData(true, false, false, 2)]
|
||||||
[InlineData(true, true, 2)]
|
[InlineData(true, false, true, 2)]
|
||||||
public async Task InvokingAsync_LogsUserIdBasedOnEnableSensitiveTelemetryDataAsync(bool enableSensitiveTelemetryData, bool requestThrows, int expectedLogInvocations)
|
[InlineData(false, true, false, 2)]
|
||||||
|
[InlineData(false, true, true, 2)]
|
||||||
|
[InlineData(true, true, false, 2)]
|
||||||
|
[InlineData(true, true, true, 2)]
|
||||||
|
public async Task InvokingAsync_RedactsLogDataBasedOnOptionsAsync(bool enableSensitiveTelemetryData, bool requestThrows, bool useCustomRedactor, int expectedLogInvocations)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var options = new ChatHistoryMemoryProviderOptions
|
var options = new ChatHistoryMemoryProviderOptions
|
||||||
{
|
{
|
||||||
SearchTime = ChatHistoryMemoryProviderOptions.SearchBehavior.BeforeAIInvoke,
|
SearchTime = ChatHistoryMemoryProviderOptions.SearchBehavior.BeforeAIInvoke,
|
||||||
EnableSensitiveTelemetryData = enableSensitiveTelemetryData
|
EnableSensitiveTelemetryData = enableSensitiveTelemetryData,
|
||||||
|
Redactor = useCustomRedactor ? new ReplacingRedactor("***") : null
|
||||||
};
|
};
|
||||||
|
|
||||||
var scope = new ChatHistoryMemoryProviderScope
|
var scope = new ChatHistoryMemoryProviderScope
|
||||||
@@ -578,7 +589,8 @@ public class ChatHistoryMemoryProviderTests
|
|||||||
// Act
|
// Act
|
||||||
await provider.InvokingAsync(invokingContext, CancellationToken.None);
|
await provider.InvokingAsync(invokingContext, CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert — EnableSensitiveTelemetryData takes precedence over Redactor
|
||||||
|
string expectedRedaction = enableSensitiveTelemetryData ? "user1" : (useCustomRedactor ? "***" : "<redacted>");
|
||||||
Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count);
|
Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count);
|
||||||
foreach (var logInvocation in this._loggerMock.Invocations)
|
foreach (var logInvocation in this._loggerMock.Invocations)
|
||||||
{
|
{
|
||||||
@@ -589,18 +601,18 @@ public class ChatHistoryMemoryProviderTests
|
|||||||
|
|
||||||
var state = Assert.IsType<IReadOnlyList<KeyValuePair<string, object?>>>(logInvocation.Arguments[2], exactMatch: false);
|
var state = Assert.IsType<IReadOnlyList<KeyValuePair<string, object?>>>(logInvocation.Arguments[2], exactMatch: false);
|
||||||
var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
|
var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
|
||||||
Assert.Equal(enableSensitiveTelemetryData ? "user1" : "<redacted>", userIdValue);
|
Assert.Equal(expectedRedaction, userIdValue);
|
||||||
|
|
||||||
var inputValue = state.FirstOrDefault(kvp => kvp.Key == "Input").Value;
|
var inputValue = state.FirstOrDefault(kvp => kvp.Key == "Input").Value;
|
||||||
if (inputValue != null)
|
if (inputValue != null)
|
||||||
{
|
{
|
||||||
Assert.Equal(enableSensitiveTelemetryData ? "Who am I?" : "<redacted>", inputValue);
|
Assert.Equal(enableSensitiveTelemetryData ? "Who am I?" : expectedRedaction, inputValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageTextValue = state.FirstOrDefault(kvp => kvp.Key == "MessageText").Value;
|
var messageTextValue = state.FirstOrDefault(kvp => kvp.Key == "MessageText").Value;
|
||||||
if (messageTextValue != null)
|
if (messageTextValue != null)
|
||||||
{
|
{
|
||||||
Assert.Equal(enableSensitiveTelemetryData ? "## Memories\nConsider the following memories when answering user questions:\nName is Caoimhe" : "<redacted>", messageTextValue);
|
Assert.Equal(enableSensitiveTelemetryData ? "## Memories\nConsider the following memories when answering user questions:\nName is Caoimhe" : expectedRedaction, messageTextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user