mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into feature-session-statebag
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);CA1812</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use a local MCP (Model Context Protocol) client with Azure Foundry Agents.
|
||||
// The MCP tools are resolved locally by connecting directly to the MCP server via HTTP,
|
||||
// and then passed to the Foundry agent as client-side tools.
|
||||
// This sample uses the Microsoft Learn MCP endpoint to search documentation.
|
||||
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||
|
||||
const string AgentInstructions = "You are a helpful assistant that can help with Microsoft documentation questions. Use the Microsoft Learn MCP tool to search for documentation.";
|
||||
const string AgentName = "DocsAgent";
|
||||
|
||||
// Connect to the MCP server locally via HTTP (Streamable HTTP transport).
|
||||
// The MCP server is hosted at Microsoft Learn and provides documentation search capabilities.
|
||||
Console.WriteLine("Connecting to MCP server at https://learn.microsoft.com/api/mcp ...");
|
||||
|
||||
await using McpClient mcpClient = await McpClient.CreateAsync(new HttpClientTransport(new()
|
||||
{
|
||||
Endpoint = new Uri("https://learn.microsoft.com/api/mcp"),
|
||||
Name = "Microsoft Learn MCP",
|
||||
}));
|
||||
|
||||
// Retrieve the list of tools available on the MCP server (resolved locally).
|
||||
IList<McpClientTool> mcpTools = await mcpClient.ListToolsAsync();
|
||||
Console.WriteLine($"MCP tools available: {string.Join(", ", mcpTools.Select(t => t.Name))}");
|
||||
|
||||
// Wrap each MCP tool with a DelegatingAIFunction to log local invocations.
|
||||
List<AITool> wrappedTools = mcpTools.Select(tool => (AITool)new LoggingMcpTool(tool)).ToList();
|
||||
|
||||
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential());
|
||||
|
||||
// Create the agent with the locally-resolved MCP tools.
|
||||
AIAgent agent = await aiProjectClient.CreateAIAgentAsync(
|
||||
model: deploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: wrappedTools);
|
||||
|
||||
Console.WriteLine($"Agent '{agent.Name}' created successfully.");
|
||||
|
||||
try
|
||||
{
|
||||
// First query
|
||||
const string Prompt1 = "How does one create an Azure storage account using az cli?";
|
||||
Console.WriteLine($"\nUser: {Prompt1}\n");
|
||||
AgentResponse response1 = await agent.RunAsync(Prompt1);
|
||||
Console.WriteLine($"Agent: {response1}");
|
||||
|
||||
Console.WriteLine("\n=======================================\n");
|
||||
|
||||
// Second query
|
||||
const string Prompt2 = "What is Microsoft Agent Framework?";
|
||||
Console.WriteLine($"User: {Prompt2}\n");
|
||||
AgentResponse response2 = await agent.RunAsync(Prompt2);
|
||||
Console.WriteLine($"Agent: {response2}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup by removing the agent when done
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
|
||||
Console.WriteLine($"\nAgent '{agent.Name}' deleted.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps an MCP tool to log when it is invoked locally,
|
||||
/// confirming that the MCP call is happening client-side.
|
||||
/// </summary>
|
||||
internal sealed class LoggingMcpTool(AIFunction innerFunction) : DelegatingAIFunction(innerFunction)
|
||||
{
|
||||
protected override ValueTask<object?> InvokeCoreAsync(AIFunctionArguments arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine($" >> [LOCAL MCP] Invoking tool '{this.Name}' locally...");
|
||||
return base.InvokeCoreAsync(arguments, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# Using Local MCP Client with Azure Foundry Agents
|
||||
|
||||
This sample demonstrates how to use a local MCP (Model Context Protocol) client with Azure Foundry Agents. Unlike the hosted MCP approach where Azure Foundry invokes the MCP server on the service side, this sample connects to the MCP server directly from the client via HTTP (Streamable HTTP transport) and passes the resolved tools to the agent.
|
||||
|
||||
## What this sample demonstrates
|
||||
|
||||
- Connecting to an MCP server locally using `HttpClientTransport`
|
||||
- Discovering available tools from the MCP server client-side
|
||||
- Passing locally-resolved MCP tools to a Foundry agent
|
||||
- Using the Microsoft Learn MCP endpoint for documentation search
|
||||
- Managing agent lifecycle (creation and deletion)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10 SDK or later
|
||||
- Azure Foundry service endpoint and deployment configured
|
||||
- Azure CLI installed and authenticated (for Azure credential authentication)
|
||||
|
||||
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
|
||||
|
||||
Set the following environment variables:
|
||||
|
||||
```powershell
|
||||
$env:AZURE_FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Azure Foundry resource endpoint
|
||||
$env:AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
|
||||
```
|
||||
|
||||
## Run the sample
|
||||
|
||||
Navigate to the FoundryAgents sample directory and run:
|
||||
|
||||
```powershell
|
||||
cd dotnet/samples/GettingStarted/FoundryAgents
|
||||
dotnet run --project .\FoundryAgents_Step27_LocalMCP
|
||||
```
|
||||
|
||||
## Expected behavior
|
||||
|
||||
The sample will:
|
||||
|
||||
1. Connect to the Microsoft Learn MCP server via HTTP and list available tools
|
||||
2. Create an agent with the locally-resolved MCP tools
|
||||
3. Ask two questions about Microsoft documentation
|
||||
4. The agent will use the MCP tools (invoked locally) to search Microsoft Learn documentation
|
||||
5. Display the agent's responses with information from the documentation
|
||||
6. Clean up resources by deleting the agent
|
||||
@@ -58,6 +58,7 @@ Before you begin, ensure you have the following prerequisites:
|
||||
|[Using plugins](./FoundryAgents_Step13_Plugins/)|This sample demonstrates how to use plugins with a Foundry agent|
|
||||
|[Code interpreter](./FoundryAgents_Step14_CodeInterpreter/)|This sample demonstrates how to use the code interpreter tool with a Foundry agent|
|
||||
|[Computer use](./FoundryAgents_Step15_ComputerUse/)|This sample demonstrates how to use computer use capabilities with a Foundry agent|
|
||||
|[Local MCP](./FoundryAgents_Step27_LocalMCP/)|This sample demonstrates how to use a local MCP client with a Foundry agent|
|
||||
|
||||
## Running the samples from the console
|
||||
|
||||
|
||||
@@ -31,23 +31,23 @@ namespace Microsoft.Agents.AI;
|
||||
/// </remarks>
|
||||
public abstract class AIContextProvider
|
||||
{
|
||||
private readonly string _sourceName;
|
||||
private readonly string _sourceId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AIContextProvider"/> class.
|
||||
/// </summary>
|
||||
protected AIContextProvider()
|
||||
{
|
||||
this._sourceName = this.GetType().FullName!;
|
||||
this._sourceId = this.GetType().FullName!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AIContextProvider"/> class with the specified source name.
|
||||
/// Initializes a new instance of the <see cref="AIContextProvider"/> class with the specified source id.
|
||||
/// </summary>
|
||||
/// <param name="sourceName">The source name to stamp on <see cref="ChatMessage.AdditionalProperties"/> for each messages produced by the <see cref="AIContextProvider"/>.</param>
|
||||
protected AIContextProvider(string sourceName)
|
||||
/// <param name="sourceId">The source id to stamp on <see cref="ChatMessage.AdditionalProperties"/> for each messages produced by the <see cref="AIContextProvider"/>.</param>
|
||||
protected AIContextProvider(string sourceId)
|
||||
{
|
||||
this._sourceName = sourceName;
|
||||
this._sourceId = sourceId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,27 +85,9 @@ public abstract class AIContextProvider
|
||||
return aiContext;
|
||||
}
|
||||
|
||||
aiContext.Messages = aiContext.Messages.Select(message =>
|
||||
{
|
||||
if (message.AdditionalProperties != null
|
||||
// Check if the message was already tagged with this provider's source type
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out var messageSourceType)
|
||||
&& messageSourceType is AgentRequestMessageSourceType typedMessageSourceType
|
||||
&& typedMessageSourceType == AgentRequestMessageSourceType.AIContextProvider
|
||||
// Check if the message was already tagged with this provider's source
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out var messageSource)
|
||||
&& messageSource is string typedMessageSource
|
||||
&& typedMessageSource == this._sourceName)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
message = message.Clone();
|
||||
message.AdditionalProperties ??= new();
|
||||
message.AdditionalProperties[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.AIContextProvider;
|
||||
message.AdditionalProperties[AgentRequestMessageSource.AdditionalPropertiesKey] = this._sourceName;
|
||||
return message;
|
||||
}).ToList();
|
||||
aiContext.Messages = aiContext.Messages
|
||||
.Select(message => message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.AIContextProvider, this._sourceId))
|
||||
.ToList();
|
||||
|
||||
return aiContext;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a constant for the key used to store the source of the agent request message.
|
||||
/// </summary>
|
||||
public static class AgentRequestMessageSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the key used in <see cref="ChatMessage.AdditionalProperties"/> to store the source of the agent request message.
|
||||
/// </summary>
|
||||
public static readonly string AdditionalPropertiesKey = "Agent.RequestMessageSource";
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Represents attribution information for the source of an agent request message for a specific run, including the component type and
|
||||
/// identifier.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this struct to identify which component provided a message during an agent run.
|
||||
/// This is useful to allow filtering of messages based on their source, such as distinguishing between user input, middleware-generated messages, and chat history.
|
||||
/// </remarks>
|
||||
public readonly struct AgentRequestMessageSourceAttribution : IEquatable<AgentRequestMessageSourceAttribution>
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the key used in <see cref="ChatMessage.AdditionalProperties"/> to store the <see cref="AgentRequestMessageSourceAttribution"/>
|
||||
/// associated with the agent request message.
|
||||
/// </summary>
|
||||
public static readonly string AdditionalPropertiesKey = "_attribution";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRequestMessageSourceAttribution"/> struct with the specified source type and identifier.
|
||||
/// </summary>
|
||||
/// <param name="sourceType">The <see cref="AgentRequestMessageSourceType"/> of the component that provided the message.</param>
|
||||
/// <param name="sourceId">The unique identifier of the component that provided the message.</param>
|
||||
public AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType sourceType, string? sourceId)
|
||||
{
|
||||
this.SourceType = sourceType;
|
||||
this.SourceId = sourceId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of component that provided the message for the current agent run.
|
||||
/// </summary>
|
||||
public AgentRequestMessageSourceType SourceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the component that provided the message for the current agent run.
|
||||
/// </summary>
|
||||
public string? SourceId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="AgentRequestMessageSourceAttribution"/> is equal to the current instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="AgentRequestMessageSourceAttribution"/> to compare with the current instance.</param>
|
||||
/// <returns><see langword="true"/> if the specified instance is equal to the current instance; otherwise, <see langword="false"/>.</returns>
|
||||
public bool Equals(AgentRequestMessageSourceAttribution other)
|
||||
{
|
||||
return this.SourceType == other.SourceType &&
|
||||
string.Equals(this.SourceId, other.SourceId, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare with the current instance.</param>
|
||||
/// <returns><see langword="true"/> if the specified object is equal to the current instance; otherwise, <see langword="false"/>.</returns>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is AgentRequestMessageSourceAttribution other && this.Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for the current instance.
|
||||
/// </summary>
|
||||
/// <returns>A hash code for the current instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = (hash * 31) + this.SourceType.GetHashCode();
|
||||
hash = (hash * 31) + (this.SourceId?.GetHashCode() ?? 0);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether two <see cref="AgentRequestMessageSourceAttribution"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance to compare.</param>
|
||||
/// <param name="right">The second instance to compare.</param>
|
||||
/// <returns><see langword="true"/> if the instances are equal; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool operator ==(AgentRequestMessageSourceAttribution left, AgentRequestMessageSourceAttribution right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether two <see cref="AgentRequestMessageSourceAttribution"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance to compare.</param>
|
||||
/// <param name="right">The second instance to compare.</param>
|
||||
/// <returns><see langword="true"/> if the instances are not equal; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool operator !=(AgentRequestMessageSourceAttribution left, AgentRequestMessageSourceAttribution right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
@@ -14,15 +13,10 @@ namespace Microsoft.Agents.AI;
|
||||
/// This type helps to identify whether a message came from outside the agent pipeline,
|
||||
/// whether it was produced by middleware, or came from chat history.
|
||||
/// </remarks>
|
||||
public sealed class AgentRequestMessageSourceType : IEquatable<AgentRequestMessageSourceType>
|
||||
public readonly struct AgentRequestMessageSourceType : IEquatable<AgentRequestMessageSourceType>
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the key used in <see cref="ChatMessage.AdditionalProperties"/> to store the source type of the agent request message.
|
||||
/// </summary>
|
||||
public static readonly string AdditionalPropertiesKey = "Agent.RequestMessageSourceType";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRequestMessageSourceType"/> class.
|
||||
/// Initializes a new instance of the <see cref="AgentRequestMessageSourceType"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value representing the source of the agent request message.</param>
|
||||
public AgentRequestMessageSourceType(string value) => this.Value = Throw.IfNullOrWhitespace(value);
|
||||
@@ -30,7 +24,7 @@ public sealed class AgentRequestMessageSourceType : IEquatable<AgentRequestMessa
|
||||
/// <summary>
|
||||
/// Get the string value representing the source of the agent request message.
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
public string Value { get { return field ?? External.Value; } }
|
||||
|
||||
/// <summary>
|
||||
/// The message came from outside the agent pipeline (e.g., user input).
|
||||
@@ -52,18 +46,8 @@ public sealed class AgentRequestMessageSourceType : IEquatable<AgentRequestMessa
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="AgentRequestMessageSourceType"/> to compare to this instance.</param>
|
||||
/// <returns><see langword="true"/> if the value of the <paramref name="other"/> parameter is the same as the value of this instance; otherwise, <see langword="false"/>.</returns>
|
||||
public bool Equals(AgentRequestMessageSourceType? other)
|
||||
public bool Equals(AgentRequestMessageSourceType other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return string.Equals(this.Value, other.Value, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
@@ -72,7 +56,7 @@ public sealed class AgentRequestMessageSourceType : IEquatable<AgentRequestMessa
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare to this instance.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="obj"/> is a <see cref="AgentRequestMessageSourceType"/> and its value is the same as this instance; otherwise, <see langword="false"/>.</returns>
|
||||
public override bool Equals(object? obj) => this.Equals(obj as AgentRequestMessageSourceType);
|
||||
public override bool Equals(object? obj) => obj is AgentRequestMessageSourceType other && this.Equals(other);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
@@ -86,13 +70,8 @@ public sealed class AgentRequestMessageSourceType : IEquatable<AgentRequestMessa
|
||||
/// <param name="left">The first <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <param name="right">The second <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool operator ==(AgentRequestMessageSourceType? left, AgentRequestMessageSourceType? right)
|
||||
public static bool operator ==(AgentRequestMessageSourceType left, AgentRequestMessageSourceType right)
|
||||
{
|
||||
if (left is null)
|
||||
{
|
||||
return right is null;
|
||||
}
|
||||
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
@@ -102,5 +81,5 @@ public sealed class AgentRequestMessageSourceType : IEquatable<AgentRequestMessa
|
||||
/// <param name="left">The first <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <param name="right">The second <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is different from the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool operator !=(AgentRequestMessageSourceType? left, AgentRequestMessageSourceType? right) => !(left == right);
|
||||
public static bool operator !=(AgentRequestMessageSourceType left, AgentRequestMessageSourceType right) => !(left == right);
|
||||
}
|
||||
|
||||
@@ -40,23 +40,23 @@ namespace Microsoft.Agents.AI;
|
||||
/// </remarks>
|
||||
public abstract class ChatHistoryProvider
|
||||
{
|
||||
private readonly string _sourceName;
|
||||
private readonly string _sourceId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHistoryProvider"/> class.
|
||||
/// </summary>
|
||||
protected ChatHistoryProvider()
|
||||
{
|
||||
this._sourceName = this.GetType().FullName!;
|
||||
this._sourceId = this.GetType().FullName!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHistoryProvider"/> class with the specified source name.
|
||||
/// Initializes a new instance of the <see cref="ChatHistoryProvider"/> class with the specified source id.
|
||||
/// </summary>
|
||||
/// <param name="sourceName">The source name to stamp on <see cref="ChatMessage.AdditionalProperties"/> for each messages produced by the <see cref="ChatHistoryProvider"/>.</param>
|
||||
protected ChatHistoryProvider(string sourceName)
|
||||
/// <param name="sourceId">The source id to stamp on <see cref="ChatMessage.AdditionalProperties"/> for each messages produced by the <see cref="ChatHistoryProvider"/>.</param>
|
||||
protected ChatHistoryProvider(string sourceId)
|
||||
{
|
||||
this._sourceName = sourceName;
|
||||
this._sourceId = sourceId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -98,27 +98,7 @@ public abstract class ChatHistoryProvider
|
||||
{
|
||||
var messages = await this.InvokingCoreAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return messages.Select(message =>
|
||||
{
|
||||
if (message.AdditionalProperties != null
|
||||
// Check if the message was already tagged with this provider's source type
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out var messageSourceType)
|
||||
&& messageSourceType is AgentRequestMessageSourceType typedMessageSourceType
|
||||
&& typedMessageSourceType == AgentRequestMessageSourceType.ChatHistory
|
||||
// Check if the message was already tagged with this provider's source
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out var messageSource)
|
||||
&& messageSource is string typedMessageSource
|
||||
&& typedMessageSource == this._sourceName)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
message = message.Clone();
|
||||
message.AdditionalProperties ??= new();
|
||||
message.AdditionalProperties[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.ChatHistory;
|
||||
message.AdditionalProperties[AgentRequestMessageSource.AdditionalPropertiesKey] = this._sourceName;
|
||||
return message;
|
||||
});
|
||||
return messages.Select(message => message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.ChatHistory, this._sourceId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -45,7 +45,7 @@ public static class ChatHistoryProviderExtensions
|
||||
innerProvider: provider,
|
||||
invokedMessagesFilter: (ctx) =>
|
||||
{
|
||||
ctx.RequestMessages = ctx.RequestMessages.Where(x => x.GetAgentRequestMessageSource() != AgentRequestMessageSourceType.AIContextProvider);
|
||||
ctx.RequestMessages = ctx.RequestMessages.Where(x => x.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.AIContextProvider);
|
||||
return ctx;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,18 +10,66 @@ namespace Microsoft.Agents.AI;
|
||||
public static class ChatMessageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the source of the provided <see cref="ChatMessage"/> in the context of messages passed into an agent run.
|
||||
/// Gets the source type of the provided <see cref="ChatMessage"/> in the context of messages passed into an agent run.
|
||||
/// </summary>
|
||||
/// <param name="message">The <see cref="ChatMessage"/> for which we need the source.</param>
|
||||
/// <returns>An <see cref="AgentRequestMessageSourceType"/> value indicating the source of the <see cref="ChatMessage"/>. Defaults to <see
|
||||
/// <param name="message">The <see cref="ChatMessage"/> for which we need the source type.</param>
|
||||
/// <returns>An <see cref="AgentRequestMessageSourceType"/> value indicating the source type of the <see cref="ChatMessage"/>. Defaults to <see
|
||||
/// cref="AgentRequestMessageSourceType.External"/> if no explicit source is defined.</returns>
|
||||
public static AgentRequestMessageSourceType GetAgentRequestMessageSource(this ChatMessage message)
|
||||
public static AgentRequestMessageSourceType GetAgentRequestMessageSourceType(this ChatMessage message)
|
||||
{
|
||||
if (message.AdditionalProperties?.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out var source) is true && source is AgentRequestMessageSourceType typedSource)
|
||||
if (message.AdditionalProperties?.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out var attribution) is true
|
||||
&& attribution is AgentRequestMessageSourceAttribution typedAttribution)
|
||||
{
|
||||
return typedSource;
|
||||
return typedAttribution.SourceType;
|
||||
}
|
||||
|
||||
return AgentRequestMessageSourceType.External;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source id of the provided <see cref="ChatMessage"/> in the context of messages passed into an agent run.
|
||||
/// </summary>
|
||||
/// <param name="message">The <see cref="ChatMessage"/> for which we need the source id.</param>
|
||||
/// <returns>An <see cref="string"/> value indicating the source id of the <see cref="ChatMessage"/>. Defaults to <see langword="null"/>
|
||||
/// if no explicit source id is defined.</returns>
|
||||
public static string? GetAgentRequestMessageSourceId(this ChatMessage message)
|
||||
{
|
||||
if (message.AdditionalProperties?.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out var attribution) is true
|
||||
&& attribution is AgentRequestMessageSourceAttribution typedAttribution)
|
||||
{
|
||||
return typedAttribution.SourceId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the provided message is tagged with the provided source type and source id in the context of a specific agent run.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to tag.</param>
|
||||
/// <param name="sourceType">The source type to tag the message with.</param>
|
||||
/// <param name="sourceId">The source id to tag the message with.</param>
|
||||
/// <returns>The tagged message.</returns>
|
||||
/// <remarks>
|
||||
/// If the message is already tagged with the provided source type and source id, it is returned as is.
|
||||
/// Otherwise, a cloned message is returned with the appropriate tagging in the AdditionalProperties.
|
||||
/// </remarks>
|
||||
public static ChatMessage AsAgentRequestMessageSourcedMessage(this ChatMessage message, AgentRequestMessageSourceType sourceType, string? sourceId = null)
|
||||
{
|
||||
if (message.AdditionalProperties != null
|
||||
// Check if the message was already tagged with the required source type and source id
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out var messageSourceAttribution)
|
||||
&& messageSourceAttribution is AgentRequestMessageSourceAttribution typedMessageSourceAttribution
|
||||
&& typedMessageSourceAttribution.SourceType == sourceType
|
||||
&& typedMessageSourceAttribution.SourceId == sourceId)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
message = message.Clone();
|
||||
message.AdditionalProperties ??= new();
|
||||
message.AdditionalProperties[AgentRequestMessageSourceAttribution.AdditionalPropertiesKey] =
|
||||
new AgentRequestMessageSourceAttribution(sourceType, sourceId);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
string queryText = string.Join(
|
||||
Environment.NewLine,
|
||||
context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => !string.IsNullOrWhiteSpace(m.Text))
|
||||
.Select(m => m.Text));
|
||||
|
||||
@@ -197,7 +197,7 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
await this.PersistMessagesAsync(
|
||||
storageScope,
|
||||
context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External)
|
||||
.Concat(context.ResponseMessages ?? []),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -21,12 +21,14 @@ internal abstract class ProcessContentMetadataBase : GraphDataTypeBase
|
||||
/// <param name="identifier">The unique identifier for the content.</param>
|
||||
/// <param name="isTruncated">Indicates if the content is truncated.</param>
|
||||
/// <param name="name">The name of the content.</param>
|
||||
protected ProcessContentMetadataBase(ContentBase content, string identifier, bool isTruncated, string name) : base(ProcessConversationMetadataDataType)
|
||||
/// <param name="correlationId">The correlation ID for the content.</param>
|
||||
protected ProcessContentMetadataBase(ContentBase content, string identifier, bool isTruncated, string name, string correlationId) : base(ProcessConversationMetadataDataType)
|
||||
{
|
||||
this.Identifier = identifier;
|
||||
this.IsTruncated = isTruncated;
|
||||
this.Content = content;
|
||||
this.Name = name;
|
||||
this.CorrelationId = correlationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,7 +57,7 @@ internal abstract class ProcessContentMetadataBase : GraphDataTypeBase
|
||||
/// Identifier to group multiple contents.
|
||||
/// </summary>
|
||||
[JsonPropertyName("correlationId")]
|
||||
public string? CorrelationId { get; set; }
|
||||
public string CorrelationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sequenceNumber.
|
||||
|
||||
@@ -15,7 +15,7 @@ internal sealed class ProcessConversationMetadata : ProcessContentMetadataBase
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProcessConversationMetadata"/> class.
|
||||
/// </summary>
|
||||
public ProcessConversationMetadata(ContentBase contentBase, string identifier, bool isTruncated, string name) : base(contentBase, identifier, isTruncated, name)
|
||||
public ProcessConversationMetadata(ContentBase contentBase, string identifier, bool isTruncated, string name, string correlationId) : base(contentBase, identifier, isTruncated, name, correlationId)
|
||||
{
|
||||
this.DataType = ProcessConversationMetadataDataType;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ internal sealed class ProcessFileMetadata : ProcessContentMetadataBase
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProcessFileMetadata"/> class.
|
||||
/// </summary>
|
||||
public ProcessFileMetadata(ContentBase contentBase, string identifier, bool isTruncated, string name) : base(contentBase, identifier, isTruncated, name)
|
||||
public ProcessFileMetadata(ContentBase contentBase, string identifier, bool isTruncated, string name, string correlationId) : base(contentBase, identifier, isTruncated, name, correlationId)
|
||||
{
|
||||
this.DataType = ProcessFileMetadataDataType;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class PurviewSettings
|
||||
/// <param name="appName">The publicly visible name of the application.</param>
|
||||
public PurviewSettings(string appName)
|
||||
{
|
||||
this.AppName = appName;
|
||||
this.AppName = string.IsNullOrWhiteSpace(appName) ? throw new ArgumentException("AppName cannot be null or whitespace.", nameof(appName)) : appName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -53,7 +53,7 @@ internal sealed class PurviewWrapper : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
return Guid.NewGuid().ToString();
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,12 +136,15 @@ internal sealed class PurviewWrapper : IDisposable
|
||||
/// <returns>The agent's response. This could be the response from the agent or a message indicating that Purview has blocked the prompt or response.</returns>
|
||||
public async Task<AgentResponse> ProcessAgentContentAsync(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
string sessionId = GetSessionIdFromAgentSession(session, messages);
|
||||
|
||||
string? resolvedUserId = null;
|
||||
|
||||
string sessionId = string.Empty;
|
||||
try
|
||||
{
|
||||
sessionId = GetSessionIdFromAgentSession(session, messages);
|
||||
if (string.IsNullOrEmpty(sessionId))
|
||||
{
|
||||
sessionId = Guid.NewGuid().ToString();
|
||||
}
|
||||
(bool shouldBlockPrompt, resolvedUserId) = await this._scopedProcessor.ProcessMessagesAsync(messages, sessionId, Activity.UploadText, this._purviewSettings, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (shouldBlockPrompt)
|
||||
@@ -171,7 +174,19 @@ internal sealed class PurviewWrapper : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
(bool shouldBlockResponse, _) = await this._scopedProcessor.ProcessMessagesAsync(response.Messages, sessionId, Activity.UploadText, this._purviewSettings, resolvedUserId, cancellationToken).ConfigureAwait(false);
|
||||
string sessionIdResponse = GetSessionIdFromAgentSession(session, messages);
|
||||
if (string.IsNullOrEmpty(sessionIdResponse))
|
||||
{
|
||||
if (string.IsNullOrEmpty(sessionId))
|
||||
{
|
||||
sessionIdResponse = Guid.NewGuid().ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
sessionIdResponse = sessionId;
|
||||
}
|
||||
}
|
||||
(bool shouldBlockResponse, _) = await this._scopedProcessor.ProcessMessagesAsync(response.Messages, sessionIdResponse, Activity.UploadText, this._purviewSettings, resolvedUserId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (shouldBlockResponse)
|
||||
{
|
||||
|
||||
@@ -121,9 +121,10 @@ internal sealed class ScopedContentProcessor : IScopedContentProcessor
|
||||
{
|
||||
string messageId = message.MessageId ?? Guid.NewGuid().ToString();
|
||||
ContentBase content = new PurviewTextContent(message.Text);
|
||||
ProcessConversationMetadata conversationmetadata = new(content, messageId, false, $"Agent Framework Message {messageId}")
|
||||
string correlationId = (sessionId ?? Guid.NewGuid().ToString()) + "@AF";
|
||||
ProcessConversationMetadata conversationMetadata = new(content, messageId, false, $"Agent Framework Message {messageId}", correlationId)
|
||||
{
|
||||
CorrelationId = sessionId ?? Guid.NewGuid().ToString()
|
||||
SequenceNumber = DateTime.UtcNow.Ticks,
|
||||
};
|
||||
ActivityMetadata activityMetadata = new(activity);
|
||||
PolicyLocation policyLocation;
|
||||
@@ -162,7 +163,7 @@ internal sealed class ScopedContentProcessor : IScopedContentProcessor
|
||||
OperatingSystemVersion = "Unknown"
|
||||
}
|
||||
};
|
||||
ContentToProcess contentToProcess = new([conversationmetadata], activityMetadata, deviceMetadata, integratedAppMetadata, protectedAppMetadata);
|
||||
ContentToProcess contentToProcess = new([conversationMetadata], activityMetadata, deviceMetadata, integratedAppMetadata, protectedAppMetadata);
|
||||
|
||||
if (userId == null &&
|
||||
tokenInfo?.UserId != null)
|
||||
|
||||
@@ -13,7 +13,13 @@ internal sealed class JsonMarshaller : IWireMarshaller<JsonElement>
|
||||
|
||||
public JsonMarshaller(JsonSerializerOptions? serializerOptions = null)
|
||||
{
|
||||
this._internalOptions = new JsonSerializerOptions(WorkflowsJsonUtilities.DefaultOptions);
|
||||
this._internalOptions = new JsonSerializerOptions(WorkflowsJsonUtilities.DefaultOptions)
|
||||
{
|
||||
// Propagate from the user-provided options if set; enables support for databases
|
||||
// like PostgreSQL jsonb that do not preserve property order.
|
||||
AllowOutOfOrderMetadataProperties = serializerOptions?.AllowOutOfOrderMetadataProperties is true,
|
||||
};
|
||||
|
||||
this._internalOptions.Converters.Add(new PortableValueConverter(this));
|
||||
this._internalOptions.Converters.Add(new ExecutorIdentityConverter());
|
||||
this._internalOptions.Converters.Add(new ScopeKeyConverter());
|
||||
|
||||
@@ -239,7 +239,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
try
|
||||
{
|
||||
// Using the enumerator to ensure we consider the case where no updates are returned for notification.
|
||||
responseUpdatesEnumerator = chatClient.GetStreamingResponseAsync(inputMessagesForProviders, chatOptions, cancellationToken).GetAsyncEnumerator(cancellationToken);
|
||||
responseUpdatesEnumerator = chatClient.GetStreamingResponseAsync(inputMessagesForChatClient, chatOptions, cancellationToken).GetAsyncEnumerator(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -166,7 +166,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
{
|
||||
// Get the text from the current request messages
|
||||
var requestText = string.Join("\n", context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m != null && !string.IsNullOrWhiteSpace(m.Text))
|
||||
.Select(m => m.Text));
|
||||
|
||||
@@ -225,7 +225,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
var collection = await this.EnsureCollectionExistsAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
List<Dictionary<string, object?>> itemsToStore = context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External)
|
||||
.Concat(context.ResponseMessages ?? [])
|
||||
.Select(message => new Dictionary<string, object?>
|
||||
{
|
||||
|
||||
@@ -102,7 +102,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
// Aggregate text from memory + current request messages.
|
||||
var sbInput = new StringBuilder();
|
||||
var requestMessagesText = context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x?.Text)).Select(x => x.Text);
|
||||
foreach (var messageText in recentMessagesText.Concat(requestMessagesText))
|
||||
{
|
||||
@@ -175,7 +175,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
?? [];
|
||||
|
||||
var newMessagesText = context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External)
|
||||
.Concat(context.ResponseMessages ?? [])
|
||||
.Where(m =>
|
||||
this._recentMessageRolesIncluded.Contains(m.Role) &&
|
||||
|
||||
@@ -19,7 +19,7 @@ public class AIContextProviderTests
|
||||
#region InvokingAsync Message Stamping Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_StampsMessagesWithSourceTypeAndSourceAsync()
|
||||
public async Task InvokingAsync_StampsMessagesWithSourceTypeAndSourceIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProviderWithMessages();
|
||||
@@ -32,18 +32,18 @@ public class AIContextProviderTests
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
ChatMessage message = aiContext.Messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestAIContextProviderWithMessages).FullName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, typedAttribution.SourceType);
|
||||
Assert.Equal(typeof(TestAIContextProviderWithMessages).FullName, typedAttribution.SourceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_WithCustomSourceName_StampsMessagesWithCustomSourceAsync()
|
||||
public async Task InvokingAsync_WithCustomSourceId_StampsMessagesWithCustomSourceIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string CustomSourceName = "CustomContextSource";
|
||||
var provider = new TestAIContextProviderWithCustomSource(CustomSourceName);
|
||||
const string CustomSourceId = "CustomContextSource";
|
||||
var provider = new TestAIContextProviderWithCustomSource(CustomSourceId);
|
||||
var context = new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
@@ -53,10 +53,10 @@ public class AIContextProviderTests
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
ChatMessage message = aiContext.Messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(CustomSourceName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, typedAttribution.SourceType);
|
||||
Assert.Equal(CustomSourceId, typedAttribution.SourceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -73,10 +73,10 @@ public class AIContextProviderTests
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
ChatMessage message = aiContext.Messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestAIContextProviderWithPreStampedMessages).FullName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, typedAttribution.SourceType);
|
||||
Assert.Equal(typeof(TestAIContextProviderWithPreStampedMessages).FullName, typedAttribution.SourceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -97,10 +97,10 @@ public class AIContextProviderTests
|
||||
foreach (ChatMessage message in messageList)
|
||||
{
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestAIContextProviderWithMultipleMessages).FullName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, typedAttribution.SourceType);
|
||||
Assert.Equal(typeof(TestAIContextProviderWithMultipleMessages).FullName, typedAttribution.SourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ public class AIContextProviderTests
|
||||
|
||||
private sealed class TestAIContextProviderWithCustomSource : AIContextProvider
|
||||
{
|
||||
public TestAIContextProviderWithCustomSource(string sourceName) : base(sourceName)
|
||||
public TestAIContextProviderWithCustomSource(string sourceId) : base(sourceId)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -491,8 +491,7 @@ public class AIContextProviderTests
|
||||
var message = new ChatMessage(ChatRole.System, "Pre-stamped Message");
|
||||
message.AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.AIContextProvider,
|
||||
[AgentRequestMessageSource.AdditionalPropertiesKey] = this.GetType().FullName!
|
||||
[AgentRequestMessageSourceAttribution.AdditionalPropertiesKey] = new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)
|
||||
};
|
||||
return new(new AIContext
|
||||
{
|
||||
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="AgentRequestMessageSourceAttribution"/> struct.
|
||||
/// </summary>
|
||||
public sealed class AgentRequestMessageSourceAttributionTests
|
||||
{
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsSourceTypeAndSourceId()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType expectedType = AgentRequestMessageSourceType.AIContextProvider;
|
||||
const string ExpectedId = "MyProvider";
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceAttribution attribution = new(expectedType, ExpectedId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedType, attribution.SourceType);
|
||||
Assert.Equal(ExpectedId, attribution.SourceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullSourceId_SetsNullSourceId()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType sourceType = AgentRequestMessageSourceType.ChatHistory;
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceAttribution attribution = new(sourceType, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sourceType, attribution.SourceType);
|
||||
Assert.Null(attribution.SourceId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AdditionalPropertiesKey Tests
|
||||
|
||||
[Fact]
|
||||
public void AdditionalPropertiesKey_IsAttribution()
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal("_attribution", AgentRequestMessageSourceAttribution.AdditionalPropertiesKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Default Value Tests
|
||||
|
||||
[Fact]
|
||||
public void Default_HasDefaultSourceTypeAndNullSourceId()
|
||||
{
|
||||
// Arrange & Act
|
||||
AgentRequestMessageSourceAttribution attribution = default;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default, attribution.SourceType);
|
||||
Assert.Null(attribution.SourceId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equals (IEquatable) Tests
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithSameSourceTypeAndSourceId_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithDifferentSourceType_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.ChatHistory, "Provider1");
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithDifferentSourceId_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider2");
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithDifferentSourceTypeAndSourceId_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.ChatHistory, "Provider2");
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithDifferentCaseSourceId_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "provider");
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_BothDefaultValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = default;
|
||||
AgentRequestMessageSourceAttribution attribution2 = default;
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithBothNullSourceIds_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.External, null!);
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.External, null!);
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithOneNullSourceId_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.External, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.External, null!);
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object.Equals Tests
|
||||
|
||||
[Fact]
|
||||
public void ObjectEquals_WithEqualAttribution_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.ChatHistory, "Provider");
|
||||
object attribution2 = new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "Provider");
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectEquals_WithDifferentType_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution = new(AgentRequestMessageSourceType.ChatHistory, "Provider");
|
||||
object other = "NotAnAttribution";
|
||||
|
||||
// Act
|
||||
bool result = attribution.Equals(other);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectEquals_WithNullObject_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution = new(AgentRequestMessageSourceType.ChatHistory, "Provider");
|
||||
object? other = null;
|
||||
|
||||
// Act
|
||||
bool result = attribution.Equals(other);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectEquals_WithBoxedDifferentAttribution_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.ChatHistory, "Provider1");
|
||||
object attribution2 = new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "Provider2");
|
||||
|
||||
// Act
|
||||
bool result = attribution1.Equals(attribution2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetHashCode Tests
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_WithSameValues_ReturnsSameHashCode()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
|
||||
// Act
|
||||
int hashCode1 = attribution1.GetHashCode();
|
||||
int hashCode2 = attribution2.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(hashCode1, hashCode2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_WithDifferentSourceType_ReturnsDifferentHashCode()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.ChatHistory, "Provider");
|
||||
|
||||
// Act
|
||||
int hashCode1 = attribution1.GetHashCode();
|
||||
int hashCode2 = attribution2.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(hashCode1, hashCode2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_WithDifferentSourceId_ReturnsDifferentHashCode()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider2");
|
||||
|
||||
// Act
|
||||
int hashCode1 = attribution1.GetHashCode();
|
||||
int hashCode2 = attribution2.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(hashCode1, hashCode2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_ConsistentWithEquals()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.External, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.External, "Provider");
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(attribution1.Equals(attribution2));
|
||||
Assert.Equal(attribution1.GetHashCode(), attribution2.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_WithNullSourceId_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution = new(AgentRequestMessageSourceType.External, null!);
|
||||
|
||||
// Act
|
||||
int hashCode = attribution.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.IsType<int>(hashCode);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equality Operator Tests
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithEqualValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 == attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithDifferentValues_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.ChatHistory, "Provider2");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 == attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithBothDefault_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = default;
|
||||
AgentRequestMessageSourceAttribution attribution2 = default;
|
||||
|
||||
// Act
|
||||
bool result = attribution1 == attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithDifferentSourceTypeOnly_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.External, "Provider");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 == attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithDifferentSourceIdOnly_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider2");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 == attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inequality Operator Tests
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithEqualValues_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 != attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithDifferentValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.ChatHistory, "Provider2");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 != attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithBothDefault_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = default;
|
||||
AgentRequestMessageSourceAttribution attribution2 = default;
|
||||
|
||||
// Act
|
||||
bool result = attribution1 != attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithDifferentSourceTypeOnly_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.External, "Provider");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 != attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithDifferentSourceIdOnly_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceAttribution attribution1 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider1");
|
||||
AgentRequestMessageSourceAttribution attribution2 = new(AgentRequestMessageSourceType.AIContextProvider, "Provider2");
|
||||
|
||||
// Act
|
||||
bool result = attribution1 != attribution2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+22
-81
@@ -5,7 +5,7 @@ using System;
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="AgentRequestMessageSourceType"/> class.
|
||||
/// Contains tests for the <see cref="AgentRequestMessageSourceType"/> struct.
|
||||
/// </summary>
|
||||
public sealed class AgentRequestMessageSourceTypeTests
|
||||
{
|
||||
@@ -38,6 +38,16 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
Assert.Throws<ArgumentException>(() => new AgentRequestMessageSourceType(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Default_DefaultsToExternal()
|
||||
{
|
||||
// Act
|
||||
AgentRequestMessageSourceType defaultSource = default;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, defaultSource);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Properties Tests
|
||||
@@ -49,7 +59,6 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
AgentRequestMessageSourceType source = AgentRequestMessageSourceType.External;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(source);
|
||||
Assert.Equal("External", source.Value);
|
||||
}
|
||||
|
||||
@@ -60,7 +69,6 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
AgentRequestMessageSourceType source = AgentRequestMessageSourceType.AIContextProvider;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(source);
|
||||
Assert.Equal("AIContextProvider", source.Value);
|
||||
}
|
||||
|
||||
@@ -71,22 +79,11 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
AgentRequestMessageSourceType source = AgentRequestMessageSourceType.ChatHistory;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(source);
|
||||
Assert.Equal("ChatHistory", source.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdditionalPropertiesKey_ReturnsExpectedValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
string key = AgentRequestMessageSourceType.AdditionalPropertiesKey;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Agent.RequestMessageSourceType", key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticProperties_ReturnSameInstanceOnMultipleCalls()
|
||||
public void StaticProperties_ReturnEqualValuesOnMultipleCalls()
|
||||
{
|
||||
// Arrange & Act
|
||||
AgentRequestMessageSourceType external1 = AgentRequestMessageSourceType.External;
|
||||
@@ -97,9 +94,9 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
AgentRequestMessageSourceType chatHistory2 = AgentRequestMessageSourceType.ChatHistory;
|
||||
|
||||
// Assert
|
||||
Assert.Same(external1, external2);
|
||||
Assert.Same(aiContextProvider1, aiContextProvider2);
|
||||
Assert.Same(chatHistory1, chatHistory2);
|
||||
Assert.Equal(external1, external2);
|
||||
Assert.Equal(aiContextProvider1, aiContextProvider2);
|
||||
Assert.Equal(chatHistory1, chatHistory2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -148,7 +145,7 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithNull_ReturnsFalse()
|
||||
public void Equals_WithNullObject_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source = new("Test");
|
||||
@@ -314,11 +311,11 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithBothNull_ReturnsTrue()
|
||||
public void EqualityOperator_WithDefaultValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
AgentRequestMessageSourceType source1 = default;
|
||||
AgentRequestMessageSourceType source2 = default;
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
@@ -327,34 +324,6 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithLeftNull_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithRightNull_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithStaticInstances_ReturnsTrue()
|
||||
{
|
||||
@@ -416,11 +385,11 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithBothNull_ReturnsFalse()
|
||||
public void InequalityOperator_WithBothDefault_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
AgentRequestMessageSourceType source1 = default;
|
||||
AgentRequestMessageSourceType source2 = default;
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
@@ -429,34 +398,6 @@ public sealed class AgentRequestMessageSourceTypeTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithLeftNull_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithRightNull_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_DifferentStaticInstances_ReturnsTrue()
|
||||
{
|
||||
|
||||
+3
-3
@@ -64,7 +64,7 @@ public sealed class ChatHistoryProviderExtensionsTests
|
||||
Mock<ChatHistoryProvider> providerMock = new();
|
||||
List<ChatMessage> requestMessages =
|
||||
[
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "TestSource") } } },
|
||||
new(ChatRole.User, "Hello")
|
||||
];
|
||||
ChatHistoryProvider.InvokedContext context = new(s_mockAgent, s_mockSession, requestMessages)
|
||||
@@ -114,9 +114,9 @@ public sealed class ChatHistoryProviderExtensionsTests
|
||||
Mock<ChatHistoryProvider> providerMock = new();
|
||||
List<ChatMessage> requestMessages =
|
||||
[
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "TestSource") } } },
|
||||
new(ChatRole.User, "Hello"),
|
||||
new(ChatRole.System, "Context") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.AIContextProvider } } }
|
||||
new(ChatRole.System, "Context") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "TestContextSource") } } }
|
||||
];
|
||||
ChatHistoryProvider.InvokedContext context = new(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
|
||||
+2
-2
@@ -170,7 +170,7 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
var innerProviderMock = new Mock<ChatHistoryProvider>();
|
||||
List<ChatMessage> requestMessages =
|
||||
[
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "TestSource") } } },
|
||||
new(ChatRole.User, "Hello"),
|
||||
];
|
||||
var responseMessages = new List<ChatMessage> { new(ChatRole.Assistant, "Response") };
|
||||
@@ -189,7 +189,7 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
// Filter that modifies the context
|
||||
ChatHistoryProvider.InvokedContext InvokedFilter(ChatHistoryProvider.InvokedContext ctx)
|
||||
{
|
||||
var modifiedRequestMessages = ctx.RequestMessages.Where(x => x.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External).Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")).ToList();
|
||||
var modifiedRequestMessages = ctx.RequestMessages.Where(x => x.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External).Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")).ToList();
|
||||
return new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, modifiedRequestMessages)
|
||||
{
|
||||
ResponseMessages = ctx.ResponseMessages,
|
||||
|
||||
+22
-23
@@ -21,7 +21,7 @@ public class ChatHistoryProviderTests
|
||||
#region InvokingAsync Message Stamping Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_StampsMessagesWithSourceTypeAndSourceAsync()
|
||||
public async Task InvokingAsync_StampsMessagesWithSourceTypeAndSourceIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider();
|
||||
@@ -33,18 +33,18 @@ public class ChatHistoryProviderTests
|
||||
// Assert
|
||||
ChatMessage message = messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestChatHistoryProvider).FullName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, typedAttribution.SourceType);
|
||||
Assert.Equal(typeof(TestChatHistoryProvider).FullName, typedAttribution.SourceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_WithCustomSourceName_StampsMessagesWithCustomSourceAsync()
|
||||
public async Task InvokingAsync_WithCustomSourceId_StampsMessagesWithCustomSourceIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string CustomSourceName = "CustomHistorySource";
|
||||
var provider = new TestChatHistoryProviderWithCustomSource(CustomSourceName);
|
||||
const string CustomSourceId = "CustomHistorySource";
|
||||
var provider = new TestChatHistoryProviderWithCustomSource(CustomSourceId);
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
@@ -53,10 +53,10 @@ public class ChatHistoryProviderTests
|
||||
// Assert
|
||||
ChatMessage message = messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(CustomSourceName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, typedAttribution.SourceType);
|
||||
Assert.Equal(CustomSourceId, typedAttribution.SourceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -72,10 +72,10 @@ public class ChatHistoryProviderTests
|
||||
// Assert
|
||||
ChatMessage message = messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestChatHistoryProviderWithPreStampedMessages).FullName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, typedAttribution.SourceType);
|
||||
Assert.Equal(typeof(TestChatHistoryProviderWithPreStampedMessages).FullName, typedAttribution.SourceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -95,10 +95,10 @@ public class ChatHistoryProviderTests
|
||||
foreach (ChatMessage message in messageList)
|
||||
{
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestChatHistoryProviderWithMultipleMessages).FullName, source);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, out object? attribution));
|
||||
var typedAttribution = Assert.IsType<AgentRequestMessageSourceAttribution>(attribution);
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, typedAttribution.SourceType);
|
||||
Assert.Equal(typeof(TestChatHistoryProviderWithMultipleMessages).FullName, typedAttribution.SourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ public class ChatHistoryProviderTests
|
||||
|
||||
private sealed class TestChatHistoryProviderWithCustomSource : ChatHistoryProvider
|
||||
{
|
||||
public TestChatHistoryProviderWithCustomSource(string sourceName) : base(sourceName)
|
||||
public TestChatHistoryProviderWithCustomSource(string sourceId) : base(sourceId)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -397,8 +397,7 @@ public class ChatHistoryProviderTests
|
||||
var message = new ChatMessage(ChatRole.User, "Pre-stamped Message");
|
||||
message.AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.ChatHistory,
|
||||
[AgentRequestMessageSource.AdditionalPropertiesKey] = this.GetType().FullName!
|
||||
[AgentRequestMessageSourceAttribution.AdditionalPropertiesKey] = new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, this.GetType().FullName!)
|
||||
};
|
||||
return new([message]);
|
||||
}
|
||||
|
||||
+359
-31
@@ -9,23 +9,23 @@ namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
/// </summary>
|
||||
public sealed class ChatMessageExtensionsTests
|
||||
{
|
||||
#region GetAgentRequestMessageSource Tests
|
||||
#region GetAgentRequestMessageSourceType Tests
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithNoAdditionalProperties_ReturnsExternal()
|
||||
public void GetAgentRequestMessageSourceType_WithNoAdditionalProperties_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello");
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithNullAdditionalProperties_ReturnsExternal()
|
||||
public void GetAgentRequestMessageSourceType_WithNullAdditionalProperties_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
@@ -34,14 +34,14 @@ public sealed class ChatMessageExtensionsTests
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithEmptyAdditionalProperties_ReturnsExternal()
|
||||
public void GetAgentRequestMessageSourceType_WithEmptyAdditionalProperties_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
@@ -50,130 +50,130 @@ public sealed class ChatMessageExtensionsTests
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithExternalSource_ReturnsExternal()
|
||||
public void GetAgentRequestMessageSourceType_WithExternalSourceType_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.External }
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.External, "TestSourceId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithAIContextProviderSource_ReturnsAIContextProvider()
|
||||
public void GetAgentRequestMessageSourceType_WithAIContextProviderSourceType_ReturnsAIContextProvider()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.AIContextProvider }
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "TestSourceId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithChatHistorySource_ReturnsChatHistory()
|
||||
public void GetAgentRequestMessageSourceType_WithChatHistorySourceType_ReturnsChatHistory()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory }
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "TestSourceId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithCustomSource_ReturnsCustomSource()
|
||||
public void GetAgentRequestMessageSourceType_WithCustomSourceType_ReturnsCustomSourceType()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType customSource = new("CustomSource");
|
||||
AgentRequestMessageSourceType customSourceType = new("CustomSourceType");
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, customSource }
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(customSourceType, "TestSourceId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(customSource, result);
|
||||
Assert.Equal("CustomSource", result.Value);
|
||||
Assert.Equal(customSourceType, result);
|
||||
Assert.Equal("CustomSourceType", result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithWrongKeyType_ReturnsExternal()
|
||||
public void GetAgentRequestMessageSourceType_WithWrongAttributionType_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, "NotAnAgentRequestMessageSource" }
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, "NotAnAgentRequestMessageSourceAttribution" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithNullValue_ReturnsExternal()
|
||||
public void GetAgentRequestMessageSourceType_WithNullAttributionValue_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, null! }
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, null! }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithMultipleProperties_ReturnsCorrectSource()
|
||||
public void GetAgentRequestMessageSourceType_WithMultipleProperties_ReturnsCorrectSourceType()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
@@ -181,17 +181,345 @@ public sealed class ChatMessageExtensionsTests
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ "OtherProperty", "SomeValue" },
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory },
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "TestSourceId") },
|
||||
{ "AnotherProperty", 123 }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSourceType();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetAgentRequestMessageSourceId Tests
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithNoAdditionalProperties_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello");
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithNullAdditionalProperties_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = null
|
||||
};
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithEmptyAdditionalProperties_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary()
|
||||
};
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithAttribution_ReturnsSourceId()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "MyProvider.FullName") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("MyProvider.FullName", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithDifferentSourceIds_ReturnsCorrectSourceId()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "CustomHistorySourceId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("CustomHistorySourceId", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithWrongAttributionType_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, "NotAnAgentRequestMessageSourceAttribution" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithNullAttributionValue_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, null! }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSourceId_WithMultipleProperties_ReturnsCorrectSourceId()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ "OtherProperty", "SomeValue" },
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.External, "ExpectedSourceId") },
|
||||
{ "AnotherProperty", 123 }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string? result = message.GetAgentRequestMessageSourceId();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ExpectedSourceId", result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AsAgentRequestMessageSourcedMessage Tests
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithNoAdditionalProperties_ReturnsClonesMessageWithAttribution()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello");
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.External, "TestSourceId");
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(message, result);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result.GetAgentRequestMessageSourceType());
|
||||
Assert.Equal("TestSourceId", result.GetAgentRequestMessageSourceId());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithNullAdditionalProperties_ReturnsClonesMessageWithAttribution()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = null
|
||||
};
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.AIContextProvider, "ProviderSourceId");
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(message, result);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, result.GetAgentRequestMessageSourceType());
|
||||
Assert.Equal("ProviderSourceId", result.GetAgentRequestMessageSourceId());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithMatchingSourceTypeAndSourceId_ReturnsSameInstance()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "HistoryId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.ChatHistory, "HistoryId");
|
||||
|
||||
// Assert
|
||||
Assert.Same(message, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithDifferentSourceType_ReturnsClonesMessageWithNewAttribution()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.External, "SourceId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.AIContextProvider, "SourceId");
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(message, result);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, result.GetAgentRequestMessageSourceType());
|
||||
Assert.Equal("SourceId", result.GetAgentRequestMessageSourceId());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithDifferentSourceId_ReturnsClonesMessageWithNewAttribution()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.External, "OriginalId") }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.External, "NewId");
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(message, result);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result.GetAgentRequestMessageSourceType());
|
||||
Assert.Equal("NewId", result.GetAgentRequestMessageSourceId());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithDefaultNullSourceId_ReturnsClonesMessageWithNullSourceId()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello");
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.ChatHistory);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(message, result);
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, result.GetAgentRequestMessageSourceType());
|
||||
Assert.Null(result.GetAgentRequestMessageSourceId());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithMatchingSourceTypeAndNullSourceId_ReturnsSameInstance()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.External, null) }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.External);
|
||||
|
||||
// Assert
|
||||
Assert.Same(message, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_DoesNotModifyOriginalMessage()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello");
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.AIContextProvider, "ProviderId");
|
||||
|
||||
// Assert
|
||||
Assert.Null(message.AdditionalProperties);
|
||||
Assert.NotNull(result.AdditionalProperties);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, result.GetAgentRequestMessageSourceType());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_WithWrongAttributionType_ReturnsClonesMessageWithNewAttribution()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, "NotAnAttribution" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.External, "SourceId");
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(message, result);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result.GetAgentRequestMessageSourceType());
|
||||
Assert.Equal("SourceId", result.GetAgentRequestMessageSourceId());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsAgentRequestMessageSourcedMessage_PreservesMessageContent()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.Assistant, "Test content");
|
||||
|
||||
// Act
|
||||
ChatMessage result = message.AsAgentRequestMessageSourcedMessage(AgentRequestMessageSourceType.ChatHistory, "HistoryId");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ChatRole.Assistant, result.Role);
|
||||
Assert.Equal("Test content", result.Text);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ public class InMemoryChatHistoryProviderTests
|
||||
var requestMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "Hello"),
|
||||
new(ChatRole.System, "additional context") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
new(ChatRole.System, "additional context") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "TestSource") } } },
|
||||
};
|
||||
var responseMessages = new List<ChatMessage>
|
||||
{
|
||||
|
||||
+1
-1
@@ -302,7 +302,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
new ChatMessage(ChatRole.User, "First message"),
|
||||
new ChatMessage(ChatRole.Assistant, "Second message"),
|
||||
new ChatMessage(ChatRole.User, "Third message"),
|
||||
new ChatMessage(ChatRole.System, "System context message") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.AIContextProvider } } }
|
||||
new ChatMessage(ChatRole.System, "System context message") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "TestSource") } } }
|
||||
};
|
||||
var responseMessages = new[]
|
||||
{
|
||||
|
||||
@@ -478,7 +478,7 @@ public sealed class PurviewClientTests : IDisposable
|
||||
private static ContentToProcess CreateValidContentToProcess()
|
||||
{
|
||||
var content = new PurviewTextContent("Test content");
|
||||
var metadata = new ProcessConversationMetadata(content, "msg-123", false, "Test message");
|
||||
var metadata = new ProcessConversationMetadata(content, "msg-123", false, "Test message", "test-correlation-id");
|
||||
var activityMetadata = new ActivityMetadata(Activity.UploadText);
|
||||
var deviceMetadata = new DeviceMetadata
|
||||
{
|
||||
|
||||
@@ -1320,6 +1320,45 @@ public partial class ChatClientAgentTests
|
||||
Assert.Equal("what?", historyMessages[1].Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that RunStreamingAsync includes chat history in messages sent to the chat client on subsequent calls.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunStreamingAsyncIncludesChatHistoryInMessagesToChatClientAsync()
|
||||
{
|
||||
// Arrange
|
||||
List<IEnumerable<ChatMessage>> capturedMessages = [];
|
||||
Mock<IChatClient> mockService = new();
|
||||
ChatResponseUpdate[] returnUpdates =
|
||||
[
|
||||
new ChatResponseUpdate(role: ChatRole.Assistant, content: "response"),
|
||||
];
|
||||
mockService.Setup(
|
||||
s => s.GetStreamingResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(ToAsyncEnumerableAsync(returnUpdates))
|
||||
.Callback<IEnumerable<ChatMessage>, ChatOptions?, CancellationToken>((msgs, _, _) => capturedMessages.Add(msgs.ToList()));
|
||||
ChatClientAgent agent = new(mockService.Object, options: new()
|
||||
{
|
||||
ChatOptions = new() { Instructions = "test instructions" },
|
||||
});
|
||||
|
||||
// Act
|
||||
ChatClientAgentSession? session = await agent.CreateSessionAsync() as ChatClientAgentSession;
|
||||
await agent.RunStreamingAsync([new(ChatRole.User, "first")], session).ToListAsync();
|
||||
await agent.RunStreamingAsync([new(ChatRole.User, "second")], session).ToListAsync();
|
||||
|
||||
// Assert - the second call should include chat history (first user message + first response) plus the new message
|
||||
Assert.Equal(2, capturedMessages.Count);
|
||||
var secondCallMessages = capturedMessages[1].ToList();
|
||||
Assert.Equal(3, secondCallMessages.Count);
|
||||
Assert.Equal("first", secondCallMessages[0].Text);
|
||||
Assert.Equal("response", secondCallMessages[1].Text);
|
||||
Assert.Equal("second", secondCallMessages[2].Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that RunStreamingAsync throws when a <see cref="ChatHistoryProvider"/> is provided and the chat client returns a conversation id.
|
||||
/// </summary>
|
||||
|
||||
@@ -672,4 +672,104 @@ public class JsonSerializationTests
|
||||
|
||||
ValidateCheckpoint(retrievedCheckpoint, prototype);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the default behavior (without AllowOutOfOrderMetadataProperties) fails
|
||||
/// when $type metadata is not the first property, demonstrating the PostgreSQL jsonb issue.
|
||||
/// See: https://github.com/microsoft/agent-framework/issues/2962
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_OutOfOrderMetadataProperties_WithoutOption_Fails()
|
||||
{
|
||||
// Arrange
|
||||
JsonMarshaller marshaller = new();
|
||||
EdgeInfo edgeInfo = TestEdgeInfo_DirectNoCondition;
|
||||
|
||||
// Serialize to JSON
|
||||
JsonElement serialized = marshaller.Marshal(edgeInfo);
|
||||
string json = serialized.GetRawText();
|
||||
|
||||
// Simulate PostgreSQL jsonb behavior: reorder properties so $type is not first
|
||||
string reorderedJson = ReorderJsonPropertiesToMoveTypeDiscriminatorLast(json);
|
||||
|
||||
// Act & Assert - Without the option, deserialization should fail
|
||||
JsonElement reorderedElement = JsonDocument.Parse(reorderedJson).RootElement;
|
||||
Action act = () => marshaller.Marshal<EdgeInfo>(reorderedElement);
|
||||
|
||||
act.Should().Throw<JsonException>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates PostgreSQL jsonb behavior where property order is not preserved,
|
||||
/// causing $type metadata to not be the first property.
|
||||
/// This test verifies that deserialization works when AllowOutOfOrderMetadataProperties is enabled.
|
||||
/// See: https://github.com/microsoft/agent-framework/issues/2962
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_OutOfOrderMetadataProperties_WithOptionEnabled_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
EdgeInfo edgeInfo = TestEdgeInfo_DirectNoCondition;
|
||||
|
||||
// Serialize to JSON using standard marshaller
|
||||
JsonMarshaller marshaller = new();
|
||||
JsonElement serialized = marshaller.Marshal(edgeInfo);
|
||||
string json = serialized.GetRawText();
|
||||
|
||||
// Simulate PostgreSQL jsonb behavior: reorder properties so $type is not first
|
||||
string reorderedJson = ReorderJsonPropertiesToMoveTypeDiscriminatorLast(json);
|
||||
JsonElement reorderedElement = JsonDocument.Parse(reorderedJson).RootElement;
|
||||
|
||||
// Act - Deserialize with AllowOutOfOrderMetadataProperties enabled via JsonSerializerOptions
|
||||
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
|
||||
JsonMarshaller marshallerWithOption = new(options);
|
||||
EdgeInfo deserialized = marshallerWithOption.Marshal<EdgeInfo>(reorderedElement);
|
||||
|
||||
// Assert
|
||||
deserialized.Should().Match(edgeInfo.CreatePolyValidator());
|
||||
}
|
||||
|
||||
private static string ReorderJsonPropertiesToMoveTypeDiscriminatorLast(string json)
|
||||
{
|
||||
// Parse JSON, extract $type, rebuild with $type at end
|
||||
using JsonDocument doc = JsonDocument.Parse(json);
|
||||
JsonElement root = doc.RootElement;
|
||||
|
||||
Dictionary<string, JsonElement> properties = [];
|
||||
JsonElement? typeValue = null;
|
||||
|
||||
foreach (JsonProperty prop in root.EnumerateObject())
|
||||
{
|
||||
if (prop.Name == "$type")
|
||||
{
|
||||
typeValue = prop.Value.Clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
properties[prop.Name] = prop.Value.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild JSON with $type last
|
||||
using System.IO.MemoryStream ms = new();
|
||||
using (Utf8JsonWriter writer = new(ms))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
foreach (KeyValuePair<string, JsonElement> kvp in properties)
|
||||
{
|
||||
writer.WritePropertyName(kvp.Key);
|
||||
kvp.Value.WriteTo(writer);
|
||||
}
|
||||
|
||||
if (typeValue.HasValue)
|
||||
{
|
||||
writer.WritePropertyName("$type");
|
||||
typeValue.Value.WriteTo(writer);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user