mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
* Initial plan * Refactor AIAgent: Make RunAsync and RunStreamingAsync non-abstract, add RunCoreAsync and RunCoreStreamingAsync Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Fix infinite recursion in test implementations Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Make RunAsync and RunStreamingAsync non-virtual as requested Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Fix DelegatingAIAgent subclasses to use RunCoreAsync/RunCoreStreamingAsync Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Fix XML documentation references in AnonymousDelegatingAIAgent Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Restore <see cref> tags with proper qualified signatures in AnonymousDelegatingAIAgent Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Rollback unnecessary XML documentation changes in AnonymousDelegatingAIAgent Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Remove pragma and update crefs to RunCoreAsync/RunCoreStreamingAsync Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Fix EntityAgentWrapper to call base.RunCoreAsync/RunCoreStreamingAsync Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * fix compilation issues * fix compilatio issue * fix tests * fix unit tests * fix unit test --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com> Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
210 lines
7.5 KiB
C#
210 lines
7.5 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Shared.Diagnostics;
|
|
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
|
|
|
namespace Microsoft.Agents.AI;
|
|
|
|
/// <summary>
|
|
/// A delegating AI agent that logs agent operations to an <see cref="ILogger"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The provided implementation of <see cref="AIAgent"/> is thread-safe for concurrent use so long as the
|
|
/// <see cref="ILogger"/> employed is also thread-safe for concurrent use.
|
|
/// </para>
|
|
/// <para>
|
|
/// When the employed <see cref="ILogger"/> enables <see cref="LogLevel.Trace"/>, the contents of
|
|
/// messages, options, and responses are logged. These may contain sensitive application data.
|
|
/// <see cref="LogLevel.Trace"/> is disabled by default and should never be enabled in a production environment.
|
|
/// Messages and options are not logged at other logging levels.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed partial class LoggingAgent : DelegatingAIAgent
|
|
{
|
|
/// <summary>An <see cref="ILogger"/> instance used for all logging.</summary>
|
|
private readonly ILogger _logger;
|
|
|
|
/// <summary>The <see cref="JsonSerializerOptions"/> to use for serialization of state written to the logger.</summary>
|
|
private JsonSerializerOptions _jsonSerializerOptions;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="LoggingAgent"/> class.</summary>
|
|
/// <param name="innerAgent">The underlying <see cref="AIAgent"/>.</param>
|
|
/// <param name="logger">An <see cref="ILogger"/> instance that will be used for all logging.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="innerAgent"/> or <paramref name="logger"/> is <see langword="null"/>.</exception>
|
|
public LoggingAgent(AIAgent innerAgent, ILogger logger)
|
|
: base(innerAgent)
|
|
{
|
|
this._logger = Throw.IfNull(logger);
|
|
this._jsonSerializerOptions = AgentJsonUtilities.DefaultOptions;
|
|
}
|
|
|
|
/// <summary>Gets or sets JSON serialization options to use when serializing logging data.</summary>
|
|
public JsonSerializerOptions JsonSerializerOptions
|
|
{
|
|
get => this._jsonSerializerOptions;
|
|
set => this._jsonSerializerOptions = Throw.IfNull(value);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override async Task<AgentRunResponse> RunCoreAsync(
|
|
IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
|
{
|
|
if (this._logger.IsEnabled(LogLevel.Debug))
|
|
{
|
|
if (this._logger.IsEnabled(LogLevel.Trace))
|
|
{
|
|
this.LogInvokedSensitive(nameof(RunAsync), this.AsJson(messages), this.AsJson(options), this.AsJson(this.GetService<AIAgentMetadata>()));
|
|
}
|
|
else
|
|
{
|
|
this.LogInvoked(nameof(RunAsync));
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
AgentRunResponse response = await base.RunCoreAsync(messages, thread, options, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (this._logger.IsEnabled(LogLevel.Debug))
|
|
{
|
|
if (this._logger.IsEnabled(LogLevel.Trace))
|
|
{
|
|
this.LogCompletedSensitive(nameof(RunAsync), this.AsJson(response));
|
|
}
|
|
else
|
|
{
|
|
this.LogCompleted(nameof(RunAsync));
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
this.LogInvocationCanceled(nameof(RunAsync));
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.LogInvocationFailed(nameof(RunAsync), ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
|
IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
|
{
|
|
if (this._logger.IsEnabled(LogLevel.Debug))
|
|
{
|
|
if (this._logger.IsEnabled(LogLevel.Trace))
|
|
{
|
|
this.LogInvokedSensitive(nameof(RunStreamingAsync), this.AsJson(messages), this.AsJson(options), this.AsJson(this.GetService<AIAgentMetadata>()));
|
|
}
|
|
else
|
|
{
|
|
this.LogInvoked(nameof(RunStreamingAsync));
|
|
}
|
|
}
|
|
|
|
IAsyncEnumerator<AgentRunResponseUpdate> e;
|
|
try
|
|
{
|
|
e = base.RunCoreStreamingAsync(messages, thread, options, cancellationToken).GetAsyncEnumerator(cancellationToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
this.LogInvocationCanceled(nameof(RunStreamingAsync));
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.LogInvocationFailed(nameof(RunStreamingAsync), ex);
|
|
throw;
|
|
}
|
|
|
|
try
|
|
{
|
|
AgentRunResponseUpdate? update = null;
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
if (!await e.MoveNextAsync().ConfigureAwait(false))
|
|
{
|
|
break;
|
|
}
|
|
|
|
update = e.Current;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
this.LogInvocationCanceled(nameof(RunStreamingAsync));
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.LogInvocationFailed(nameof(RunStreamingAsync), ex);
|
|
throw;
|
|
}
|
|
|
|
if (this._logger.IsEnabled(LogLevel.Trace))
|
|
{
|
|
this.LogStreamingUpdateSensitive(this.AsJson(update));
|
|
}
|
|
|
|
yield return update;
|
|
}
|
|
|
|
this.LogCompleted(nameof(RunStreamingAsync));
|
|
}
|
|
finally
|
|
{
|
|
await e.DisposeAsync().ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private string AsJson<T>(T value)
|
|
{
|
|
try
|
|
{
|
|
return JsonSerializer.Serialize(value, this._jsonSerializerOptions.GetTypeInfo(typeof(T)));
|
|
}
|
|
catch
|
|
{
|
|
// If serialization fails, return a simple string representation
|
|
return value?.ToString() ?? "null";
|
|
}
|
|
}
|
|
|
|
[LoggerMessage(LogLevel.Debug, "{MethodName} invoked.")]
|
|
private partial void LogInvoked(string methodName);
|
|
|
|
[LoggerMessage(LogLevel.Trace, "{MethodName} invoked: {Messages}. Options: {Options}. Metadata: {Metadata}.")]
|
|
private partial void LogInvokedSensitive(string methodName, string messages, string options, string metadata);
|
|
|
|
[LoggerMessage(LogLevel.Debug, "{MethodName} completed.")]
|
|
private partial void LogCompleted(string methodName);
|
|
|
|
[LoggerMessage(LogLevel.Trace, "{MethodName} completed: {Response}.")]
|
|
private partial void LogCompletedSensitive(string methodName, string response);
|
|
|
|
[LoggerMessage(LogLevel.Trace, "RunStreamingAsync received update: {Update}")]
|
|
private partial void LogStreamingUpdateSensitive(string update);
|
|
|
|
[LoggerMessage(LogLevel.Debug, "{MethodName} canceled.")]
|
|
private partial void LogInvocationCanceled(string methodName);
|
|
|
|
[LoggerMessage(LogLevel.Error, "{MethodName} failed.")]
|
|
private partial void LogInvocationFailed(string methodName, Exception error);
|
|
}
|