mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.Net: Added Agent abstractions from SK repository (#56)
* Fixed project build in Visual Studio * Added Agent abstractions * Remove features we are not porting over, addressing PR comments and making updates as per agreed design. * Add create thread method. * Add unit tests and update invoke response type to async * Address PR comments and fix formatting. * Switch to shared null checker to fix build failures. * Add additional tests to increase code coverage * Seal mockagent * Fix line coverage failure * Improve coverage check formatting --------- Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
a990866901
commit
af2295f130
@@ -28,11 +28,15 @@ function Get-FormattedValue {
|
||||
return "$formattedNumber% $icon"
|
||||
}
|
||||
|
||||
$totallines = $jsonContent.summary.totallines
|
||||
$totalbranches = $jsonContent.summary.totalbranches
|
||||
$lineCoverage = $jsonContent.summary.linecoverage
|
||||
$branchCoverage = $jsonContent.summary.branchcoverage
|
||||
|
||||
$totalTableData = [PSCustomObject]@{
|
||||
'Metric' = 'Total Coverage'
|
||||
'Total Lines' = $totallines
|
||||
'Total Branches' = $totalbranches
|
||||
'Line Coverage' = Get-FormattedValue -Coverage $lineCoverage
|
||||
'Branch Coverage' = Get-FormattedValue -Coverage $branchCoverage
|
||||
}
|
||||
@@ -43,19 +47,26 @@ $assemblyTableData = @()
|
||||
|
||||
foreach ($assembly in $jsonContent.coverage.assemblies) {
|
||||
$assemblyName = $assembly.name
|
||||
$assemblyTotallines = $assembly.totallines
|
||||
$assemblyTotalbranches = $assembly.totalbranches
|
||||
$assemblyLineCoverage = $assembly.coverage
|
||||
$assemblyBranchCoverage = $assembly.branchcoverage
|
||||
|
||||
|
||||
$isNonExperimentalAssembly = $nonExperimentalAssemblies -contains $assemblyName
|
||||
|
||||
if ($isNonExperimentalAssembly -and ($assemblyLineCoverage -lt $CoverageThreshold -or $assemblyBranchCoverage -lt $CoverageThreshold)) {
|
||||
$lineCoverageFailed = $assemblyLineCoverage -lt $CoverageThreshold -and $assemblyTotallines -gt 0
|
||||
$branchCoverageFailed = $assemblyBranchCoverage -lt $CoverageThreshold -and $assemblyTotalbranches -gt 0
|
||||
|
||||
if ($isNonExperimentalAssembly -and ($lineCoverageFailed -or $branchCoverageFailed)) {
|
||||
$coverageBelowThreshold = $true
|
||||
}
|
||||
|
||||
$assemblyTableData += [PSCustomObject]@{
|
||||
'Assembly Name' = $assemblyName
|
||||
'Line' = Get-FormattedValue -Coverage $assemblyLineCoverage -UseIcon $isNonExperimentalAssembly
|
||||
'Branch' = Get-FormattedValue -Coverage $assemblyBranchCoverage -UseIcon $isNonExperimentalAssembly
|
||||
'Total Lines' = $assemblyTotallines
|
||||
'Total Branches' = $assemblyTotalbranches
|
||||
'Line Coverage' = Get-FormattedValue -Coverage $assemblyLineCoverage -UseIcon $isNonExperimentalAssembly
|
||||
'Branch Coverage' = Get-FormattedValue -Coverage $assemblyBranchCoverage -UseIcon $isNonExperimentalAssembly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- System.* -->
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<!-- Microsoft.Extensions.* -->
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.5.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.5" />
|
||||
<!-- Test -->
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageVersion Include="Moq" Version="[4.18.4]" />
|
||||
|
||||
@@ -1,10 +1,259 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder class.
|
||||
/// Base abstraction for all agents. An agent instance may participate in one or more conversations.
|
||||
/// A conversation may include one or more agents.
|
||||
/// </summary>
|
||||
public class Agent
|
||||
public abstract class Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the identifier of the agent (optional).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The identifier of the agent. The default is a random GUID value, but for service agents, it will match the id of the agent in the service.
|
||||
/// </value>
|
||||
public virtual string Id => Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the agent (optional).
|
||||
/// </summary>
|
||||
public virtual string? Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the agent (optional).
|
||||
/// </summary>
|
||||
public virtual string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instructions for the agent (optional).
|
||||
/// </summary>
|
||||
public virtual string? Instructions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AgentThread"/> that is compatible with the agent.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="AgentThread"/> instance that is in the created state.</returns>
|
||||
/// <remarks>
|
||||
/// If an agent supports multiple thread types, this method should return the default thread
|
||||
/// type for the agent or whatever the agent was configured to use.
|
||||
/// </remarks>
|
||||
public abstract AgentThread CreateThreadAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with no message assuming that all required instructions are already provided to the agent or on the thread.
|
||||
/// </summary>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatResponse"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
public virtual Task<ChatResponse> RunAsync(
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunAsync((IReadOnlyCollection<ChatMessage>)[], thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to pass to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatResponse"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
/// <remarks>
|
||||
/// The provided message string will be treated as a user message.
|
||||
/// </remarks>
|
||||
public virtual Task<ChatResponse> RunAsync(
|
||||
string message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNullOrWhitespace(message);
|
||||
|
||||
return this.RunAsync(new ChatMessage(ChatRole.User, message), thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to pass to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatResponse"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
public virtual Task<ChatResponse> RunAsync(
|
||||
ChatMessage message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(message);
|
||||
|
||||
return this.RunAsync([message], thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages to pass to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatResponse"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
public abstract Task<ChatResponse> RunAsync(
|
||||
IReadOnlyCollection<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with no message assuming that all required instructions are already provided to the agent or on the thread.
|
||||
/// </summary>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An async list of response items that each contain a <see cref="ChatResponseUpdate"/>.</returns>
|
||||
public virtual IAsyncEnumerable<ChatResponseUpdate> RunStreamingAsync(
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunStreamingAsync((IReadOnlyCollection<ChatMessage>)[], thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to pass to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An async list of response items that each contain a <see cref="ChatResponseUpdate"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The provided message string will be treated as a user message.
|
||||
/// </remarks>
|
||||
public virtual IAsyncEnumerable<ChatResponseUpdate> RunStreamingAsync(
|
||||
string message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNullOrWhitespace(message);
|
||||
|
||||
return this.RunStreamingAsync(new ChatMessage(ChatRole.User, message), thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to pass to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An async list of response items that each contain a <see cref="ChatResponseUpdate"/>.</returns>
|
||||
public virtual IAsyncEnumerable<ChatResponseUpdate> RunStreamingAsync(
|
||||
ChatMessage message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(message);
|
||||
|
||||
return this.RunStreamingAsync([message], thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages to pass to the agent.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent reponse.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An async list of response items that each contain a <see cref="ChatResponseUpdate"/>.</returns>
|
||||
public abstract IAsyncEnumerable<ChatResponseUpdate> RunStreamingAsync(
|
||||
IReadOnlyCollection<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the thread exists, is of the expected type, and is active, plus adds the provided message to the thread.
|
||||
/// </summary>
|
||||
/// <typeparam name="TThreadType">The expected type of the thead.</typeparam>
|
||||
/// <param name="messages">The messages to add to the thread once it is setup.</param>
|
||||
/// <param name="thread">The thread to create if it's null, validate it's type if not null, and start if it is not active.</param>
|
||||
/// <param name="constructThread">A callback to use to construct the thread if it's null.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An async task that completes once all update are complete.</returns>
|
||||
protected virtual async Task<TThreadType> EnsureThreadExistsWithMessagesAsync<TThreadType>(
|
||||
IReadOnlyCollection<ChatMessage> messages,
|
||||
AgentThread? thread,
|
||||
Func<TThreadType> constructThread,
|
||||
CancellationToken cancellationToken)
|
||||
where TThreadType : AgentThread
|
||||
{
|
||||
Throw.IfNull(messages);
|
||||
|
||||
thread ??= constructThread is not null ? constructThread() : throw new ArgumentNullException(nameof(constructThread));
|
||||
|
||||
if (thread is not TThreadType concreteThreadType)
|
||||
{
|
||||
throw new NotSupportedException($"{this.GetType().Name} currently only supports agent threads of type {nameof(TThreadType)}.");
|
||||
}
|
||||
|
||||
// We have to explicitly call create here to ensure that the thread is created
|
||||
// before we run using the thread. While threads will be created when
|
||||
// notified of new messages, some agents support invoking without a message,
|
||||
// and in that case no messages will be sent in the next step.
|
||||
await thread.CreateAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Notify the thread that new messages are available.
|
||||
foreach (var message in messages)
|
||||
{
|
||||
await this.NotifyThreadOfNewMessage(thread, message, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return concreteThreadType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notfiy the given thread that a new message is available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Note that while all agents should notify their threads of new messages,
|
||||
/// not all threads will necessarily take action. For some treads, this may be
|
||||
/// the only way that they would know that a new message is available to be added
|
||||
/// to their history.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For other thread types, where history is managed by the service, the thread may
|
||||
/// not need to take any action.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Where threads manage other memory components that need access to new messages,
|
||||
/// notifying the thread will be important, even if the thread itself does not
|
||||
/// require the message.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="thread">The thread to notify of the new message.</param>
|
||||
/// <param name="message">The message to pass to the thread.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An async task that completes once the notification is complete.</returns>
|
||||
protected Task NotifyThreadOfNewMessage(AgentThread thread, ChatMessage message, CancellationToken cancellationToken)
|
||||
{
|
||||
return thread.OnNewMessageAsync(message, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents;
|
||||
|
||||
/// <summary>
|
||||
/// Optional parameters when running an agent.
|
||||
/// </summary>
|
||||
public class AgentRunOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRunOptions"/> class.
|
||||
/// </summary>
|
||||
public AgentRunOptions()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRunOptions"/> class by cloning the provided options.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to clone.</param>
|
||||
public AgentRunOptions(AgentRunOptions options)
|
||||
{
|
||||
Throw.IfNull(options);
|
||||
|
||||
this.AdditionalInstructions = options.AdditionalInstructions;
|
||||
this.OnIntermediateMessage = options.OnIntermediateMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets any instructions, in addition to those that were provided to the agent
|
||||
/// initially, that need to be added to the prompt for this invocation only.
|
||||
/// </summary>
|
||||
public string? AdditionalInstructions { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a function to be called when a complete new message is generated by the agent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This callback is particularly useful in cases where the caller wants to receive complete messages
|
||||
/// when invoking the agent with streaming.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Func<ChatMessage, Task>? OnIntermediateMessage { get; set; } = null;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents;
|
||||
|
||||
/// <summary>
|
||||
/// Base abstraction for all agent threads.
|
||||
/// A thread represents a specific conversation with an agent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is used to manage the lifecycle of an agent thread.
|
||||
/// The thread can be not-start, started or ended.
|
||||
/// </remarks>
|
||||
public abstract class AgentThread
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the id of the current thread.
|
||||
/// </summary>
|
||||
public string? Id { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the thread has been deleted.
|
||||
/// </summary>
|
||||
public bool IsDeleted { get; protected set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the thread and returns the thread id.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that completes when the thread has been created.</returns>
|
||||
/// <exception cref="InvalidOperationException">The thread has been deleted.</exception>
|
||||
internal async Task CreateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsDeleted)
|
||||
{
|
||||
throw new InvalidOperationException("This thread has been deleted and cannot be recreated.");
|
||||
}
|
||||
|
||||
if (this.Id is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Id = await this.CreateCoreAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the current thread.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that completes when the thread has been deleted.</returns>
|
||||
/// <exception cref="InvalidOperationException">The thread was never created.</exception>
|
||||
public async Task DeleteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsDeleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.Id is null)
|
||||
{
|
||||
throw new InvalidOperationException("This thread cannot be deleted, since it has not been created.");
|
||||
}
|
||||
|
||||
await this.DeleteCoreAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
this.IsDeleted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called when a new message has been contributed to the chat by any participant.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Inheritors can use this method to update their context based on the new message.
|
||||
/// </remarks>
|
||||
/// <param name="newMessage">The new message.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that completes when the context has been updated.</returns>
|
||||
/// <exception cref="InvalidOperationException">The thread has been deleted.</exception>
|
||||
internal async Task OnNewMessageAsync(ChatMessage newMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsDeleted)
|
||||
{
|
||||
throw new InvalidOperationException("This thread has been deleted and cannot be used anymore.");
|
||||
}
|
||||
|
||||
if (this.Id is null)
|
||||
{
|
||||
await this.CreateAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await this.OnNewMessageCoreAsync(newMessage, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the thread and returns the thread id.
|
||||
/// Checks have already been completed in the <see cref="CreateAsync"/> method to ensure that the thread can be created.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The id of the thread that was created if one is available.</returns>
|
||||
protected abstract Task<string?> CreateCoreAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the current thread.
|
||||
/// Checks have already been completed in the <see cref="DeleteAsync"/> method to ensure that the thread can be deleted.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that completes when the thread has been deleted.</returns>
|
||||
protected abstract Task DeleteCoreAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// This method is called when a new message has been contributed to the chat by any participant.
|
||||
/// Checks have already been completed in the <see cref="OnNewMessageAsync"/> method to ensure that the thread can be updated.
|
||||
/// </summary>
|
||||
/// <param name="newMessage">The new message.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that completes when the context has been updated.</returns>
|
||||
protected abstract Task OnNewMessageCoreAsync(ChatMessage newMessage, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -22,4 +22,13 @@
|
||||
<Description>Contains the Microsoft Agent Framework interfaces and abstractions.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.Agents.Abstractions.UnitTests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Agents.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="AgentRunOptions"/> class.
|
||||
/// </summary>
|
||||
public class AgentRunOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void CloningConstructorCopiesProperties()
|
||||
{
|
||||
// Arrange
|
||||
var options = new AgentRunOptions
|
||||
{
|
||||
AdditionalInstructions = "Test instructions",
|
||||
OnIntermediateMessage = msg => Task.CompletedTask
|
||||
};
|
||||
|
||||
// Act
|
||||
var clone = new AgentRunOptions(options);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(options.AdditionalInstructions, clone.AdditionalInstructions);
|
||||
Assert.Equal(options.OnIntermediateMessage, clone.OnIntermediateMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloningConstructorThrowsIfNull()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AgentRunOptions(null!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="Agent"/> class.
|
||||
/// </summary>
|
||||
public class AgentTests
|
||||
{
|
||||
private readonly Mock<Agent> _agentMock;
|
||||
private readonly Mock<AgentThread> _agentThreadMock;
|
||||
private readonly ChatResponse _invokeResponse = new();
|
||||
private readonly List<ChatResponseUpdate> _invokeStreamingResponses = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentTests"/> class.
|
||||
/// </summary>
|
||||
public AgentTests()
|
||||
{
|
||||
this._agentThreadMock = new Mock<AgentThread>(MockBehavior.Strict);
|
||||
|
||||
this._invokeResponse = new ChatResponse(new ChatMessage(ChatRole.Assistant, "Hi"));
|
||||
this._invokeStreamingResponses.Add(new ChatResponseUpdate(ChatRole.Assistant, "Hi"));
|
||||
|
||||
this._agentMock = new Mock<Agent>() { CallBase = true };
|
||||
this._agentMock
|
||||
.Setup(x => x.RunAsync(
|
||||
It.IsAny<IReadOnlyCollection<ChatMessage>>(),
|
||||
this._agentThreadMock.Object,
|
||||
It.IsAny<AgentRunOptions?>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(this._invokeResponse);
|
||||
this._agentMock
|
||||
.Setup(x => x.RunStreamingAsync(
|
||||
It.IsAny<IReadOnlyCollection<ChatMessage>>(),
|
||||
this._agentThreadMock.Object,
|
||||
It.IsAny<AgentRunOptions?>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(this._invokeStreamingResponses.ToAsyncEnumerable());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that invoking without a message calls the mocked invoke method with an empty array.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeWithoutMessageCallsMockedInvokeWithEmptyArrayAsync()
|
||||
{
|
||||
// Arrange
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = new CancellationToken();
|
||||
|
||||
// Act
|
||||
var response = await this._agentMock.Object.RunAsync(this._agentThreadMock.Object, options, cancellationToken);
|
||||
Assert.Equal(this._invokeResponse, response);
|
||||
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 0),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that invoking with a string message calls the mocked invoke method with the message in the ICollection of messages.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeWithStringMessageCallsMockedInvokeWithMessageInCollectionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var message = "Hello, Agent!";
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = new CancellationToken();
|
||||
|
||||
// Act
|
||||
var response = await this._agentMock.Object.RunAsync(message, this._agentThreadMock.Object, options, cancellationToken);
|
||||
Assert.Equal(this._invokeResponse, response);
|
||||
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First().Text == message),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that invoking with a single message calls the mocked invoke method with the message in the ICollection of messages.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeWithSingleMessageCallsMockedInvokeWithMessageInCollectionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var message = new ChatMessage(ChatRole.User, "Hello, Agent!");
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = new CancellationToken();
|
||||
|
||||
// Act
|
||||
var response = await this._agentMock.Object.RunAsync(message, this._agentThreadMock.Object, options, cancellationToken);
|
||||
Assert.Equal(this._invokeResponse, response);
|
||||
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First() == message),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that invoking streaming without a message calls the mocked invoke method with an empty array.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeStreamingWithoutMessageCallsMockedInvokeWithEmptyArrayAsync()
|
||||
{
|
||||
// Arrange
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = new CancellationToken();
|
||||
|
||||
// Act
|
||||
await foreach (var response in this._agentMock.Object.RunStreamingAsync(this._agentThreadMock.Object, options, cancellationToken))
|
||||
{
|
||||
// Assert
|
||||
Assert.Contains(response, this._invokeStreamingResponses);
|
||||
}
|
||||
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunStreamingAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 0),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that invoking streaming with a string message calls the mocked invoke method with the message in the ICollection of messages.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeStreamingWithStringMessageCallsMockedInvokeWithMessageInCollectionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var message = "Hello, Agent!";
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = new CancellationToken();
|
||||
|
||||
// Act
|
||||
await foreach (var response in this._agentMock.Object.RunStreamingAsync(message, this._agentThreadMock.Object, options, cancellationToken))
|
||||
{
|
||||
// Assert
|
||||
Assert.Contains(response, this._invokeStreamingResponses);
|
||||
}
|
||||
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunStreamingAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First().Text == message),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that invoking streaming with a single message calls the mocked invoke method with the message in the ICollection of messages.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeStreamingWithSingleMessageCallsMockedInvokeWithMessageInCollectionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var message = new ChatMessage(ChatRole.User, "Hello, Agent!");
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = new CancellationToken();
|
||||
|
||||
// Act
|
||||
await foreach (var response in this._agentMock.Object.RunStreamingAsync(message, this._agentThreadMock.Object, options, cancellationToken))
|
||||
{
|
||||
// Assert
|
||||
Assert.Contains(response, this._invokeStreamingResponses);
|
||||
}
|
||||
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunStreamingAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First() == message),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnsureThreadExistsWithMessagesVerifiesAndCreatesThreadAndNotifiesThreadAsync()
|
||||
{
|
||||
var messages = new[] { new ChatMessage(ChatRole.User, "msg1"), new ChatMessage(ChatRole.User, "msg2") };
|
||||
var cancellationToken = new CancellationToken();
|
||||
|
||||
// Custom thread type for type checking
|
||||
var threadMock = new Mock<TestAgentThread>() { CallBase = true };
|
||||
|
||||
var agent = new MockAgent();
|
||||
|
||||
// Should create and notify
|
||||
var result = await agent.EnsureThreadExistsWithMessagesAsync<TestAgentThread>(messages, null, () => threadMock.Object, cancellationToken);
|
||||
Assert.Same(threadMock.Object, result);
|
||||
threadMock.Protected().Verify("OnNewMessageCoreAsync", Times.Once(), messages[0], CancellationToken.None);
|
||||
threadMock.Protected().Verify("OnNewMessageCoreAsync", Times.Once(), messages[1], CancellationToken.None);
|
||||
|
||||
// Should throw if wrong type
|
||||
var wrongThread = new Mock<AgentThread>().Object;
|
||||
await Assert.ThrowsAsync<NotSupportedException>(() => agent.EnsureThreadExistsWithMessagesAsync<TestAgentThread>(messages, wrongThread, () => threadMock.Object, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Typed mock thread.
|
||||
/// </summary>
|
||||
public abstract class TestAgentThread : AgentThread
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock class to test the <see cref="Agent.EnsureThreadExistsWithMessagesAsync{TThreadType}"/> method.
|
||||
/// </summary>
|
||||
private sealed class MockAgent : Agent
|
||||
{
|
||||
public new Task<TThreadType> EnsureThreadExistsWithMessagesAsync<TThreadType>(
|
||||
IReadOnlyCollection<ChatMessage> messages,
|
||||
AgentThread? thread,
|
||||
Func<TThreadType> constructThread,
|
||||
CancellationToken cancellationToken)
|
||||
where TThreadType : AgentThread
|
||||
{
|
||||
return base.EnsureThreadExistsWithMessagesAsync<TThreadType>(
|
||||
messages,
|
||||
thread,
|
||||
constructThread,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public override AgentThread CreateThreadAsync()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<ChatResponse> RunAsync(IReadOnlyCollection<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override IAsyncEnumerable<ChatResponseUpdate> RunStreamingAsync(IReadOnlyCollection<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="AgentThread"/> class.
|
||||
/// </summary>
|
||||
public class AgentThreadTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that the CreateAsync method sets the Id and invokes CreateInternalAsync once.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CreateShouldSetIdAndInvokeCreateInternalOnceAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thread = new TestAgentThread();
|
||||
|
||||
// Act
|
||||
await thread.CreateAsync();
|
||||
await thread.CreateAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-thread-id", thread.Id);
|
||||
Assert.Equal(1, thread.CreateInternalAsyncCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the CreateAsync method throws an InvalidOperationException if the thread is deleted.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CreateShouldThrowIfThreadDeletedAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thread = new TestAgentThread();
|
||||
await thread.CreateAsync();
|
||||
await thread.DeleteAsync();
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.CreateAsync());
|
||||
Assert.Equal(1, thread.CreateInternalAsyncCount);
|
||||
Assert.Equal(1, thread.DeleteInternalAsyncCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the DeleteAsync method sets IsDeleted and invokes DeleteInternalAsync.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DeleteShouldSetIsDeletedAndInvokeDeleteInternalAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thread = new TestAgentThread();
|
||||
await thread.CreateAsync();
|
||||
|
||||
// Act
|
||||
await thread.DeleteAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(thread.IsDeleted);
|
||||
Assert.Equal(1, thread.CreateInternalAsyncCount);
|
||||
Assert.Equal(1, thread.DeleteInternalAsyncCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the DeleteAsync method does not invoke DeleteInternalAsync if the thread is already deleted.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DeleteShouldNotInvokeDeleteInternalIfAlreadyDeletedAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thread = new TestAgentThread();
|
||||
await thread.CreateAsync();
|
||||
await thread.DeleteAsync();
|
||||
|
||||
// Act
|
||||
await thread.DeleteAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(thread.IsDeleted);
|
||||
Assert.Equal(1, thread.CreateInternalAsyncCount);
|
||||
Assert.Equal(1, thread.DeleteInternalAsyncCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the DeleteAsync method throws an InvalidOperationException if the thread was never created.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DeleteShouldThrowIfNeverCreatedAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thread = new TestAgentThread();
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.DeleteAsync());
|
||||
Assert.Equal(0, thread.CreateInternalAsyncCount);
|
||||
Assert.Equal(0, thread.DeleteInternalAsyncCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the OnNewMessageAsync method creates the thread if it is not already created.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task OnNewMessageShouldCreateThreadIfNotCreatedAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thread = new TestAgentThread();
|
||||
var message = new ChatMessage();
|
||||
|
||||
// Act
|
||||
await thread.OnNewMessageAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-thread-id", thread.Id);
|
||||
Assert.Equal(1, thread.CreateInternalAsyncCount);
|
||||
Assert.Equal(1, thread.OnNewMessageInternalAsyncCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the OnNewMessageAsync method throws an InvalidOperationException if the thread is deleted.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task OnNewMessageShouldThrowIfThreadDeletedAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thread = new TestAgentThread();
|
||||
await thread.CreateAsync();
|
||||
await thread.DeleteAsync();
|
||||
var message = new ChatMessage();
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.OnNewMessageAsync(message));
|
||||
Assert.Equal(1, thread.CreateInternalAsyncCount);
|
||||
Assert.Equal(1, thread.DeleteInternalAsyncCount);
|
||||
Assert.Equal(0, thread.OnNewMessageInternalAsyncCount);
|
||||
}
|
||||
|
||||
private sealed class TestAgentThread : AgentThread
|
||||
{
|
||||
public int CreateInternalAsyncCount { get; private set; }
|
||||
public int DeleteInternalAsyncCount { get; private set; }
|
||||
public int OnNewMessageInternalAsyncCount { get; private set; }
|
||||
|
||||
public new Task CreateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return base.CreateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override Task<string?> CreateCoreAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.CreateInternalAsyncCount++;
|
||||
return Task.FromResult<string?>("test-thread-id");
|
||||
}
|
||||
|
||||
protected override Task DeleteCoreAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.DeleteInternalAsyncCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override Task OnNewMessageCoreAsync(ChatMessage newMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.OnNewMessageInternalAsyncCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.Abstractions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder.
|
||||
/// </summary>
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user