mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [BREAKING] Prevent nulls in AIAgent property (#2719)
* prevent nulls in AIAgent property * address feedback
This commit is contained in:
committed by
GitHub
Unverified
parent
3481914981
commit
989b6ebe71
@@ -198,7 +198,7 @@ internal sealed class A2AAgent : AIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Id => this._id ?? base.Id;
|
||||
protected override string? IdCore => this._id;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? Name => this._name ?? base.Name;
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Microsoft.Agents.AI;
|
||||
[DebuggerDisplay("{DisplayName,nq}")]
|
||||
public abstract class AIAgent
|
||||
{
|
||||
/// <summary>Default ID of this agent instance.</summary>
|
||||
private readonly string _id = Guid.NewGuid().ToString("N");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this agent instance.
|
||||
/// </summary>
|
||||
@@ -37,7 +34,19 @@ public abstract class AIAgent
|
||||
/// agent instances in multi-agent scenarios. They should remain stable for the lifetime
|
||||
/// of the agent instance.
|
||||
/// </remarks>
|
||||
public virtual string Id => this._id;
|
||||
public string Id { get => this.IdCore ?? field; } = Guid.NewGuid().ToString("N");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a custom identifier for the agent, which can be overridden by derived classes.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A string representing the agent's identifier, or <see langword="null"/> if the default ID should be used.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Derived classes can override this property to provide a custom identifier.
|
||||
/// When <see langword="null"/> is returned, the <see cref="Id"/> property will use the default randomly-generated identifier.
|
||||
/// </remarks>
|
||||
protected virtual string? IdCore => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the human-readable name of the agent.
|
||||
@@ -61,7 +70,7 @@ public abstract class AIAgent
|
||||
/// This property provides a guaranteed non-null string suitable for display in user interfaces,
|
||||
/// logs, or other contexts where a readable identifier is needed.
|
||||
/// </remarks>
|
||||
public virtual string DisplayName => this.Name ?? this.Id ?? this._id; // final fallback to _id in case Id override returns null
|
||||
public virtual string DisplayName => this.Name ?? this.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a description of the agent's purpose, capabilities, or behavior.
|
||||
|
||||
@@ -54,7 +54,7 @@ public class DelegatingAIAgent : AIAgent
|
||||
protected AIAgent InnerAgent { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Id => this.InnerAgent.Id;
|
||||
protected override string? IdCore => this.InnerAgent.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string? Name => this.InnerAgent.Name;
|
||||
|
||||
@@ -19,7 +19,7 @@ internal sealed class EntityAgentWrapper(
|
||||
private readonly IServiceProvider? _entityScopedServices = entityScopedServices;
|
||||
|
||||
// The ID of the agent is always the entity ID.
|
||||
public override string Id => this._entityContext.Id.ToString();
|
||||
protected override string? IdCore => this._entityContext.Id.ToString();
|
||||
|
||||
public override async Task<AgentRunResponse> RunAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
|
||||
@@ -39,7 +39,7 @@ internal sealed class WorkflowHostAgent : AIAgent
|
||||
this._describeTask = this._workflow.DescribeProtocolAsync().AsTask();
|
||||
}
|
||||
|
||||
public override string Id => this._id ?? base.Id;
|
||||
protected override string? IdCore => this._id;
|
||||
public override string? Name { get; }
|
||||
public override string? Description { get; }
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
public IChatClient ChatClient { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Id => this._agentOptions?.Id ?? base.Id;
|
||||
protected override string? IdCore => this._agentOptions?.Id;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? Name => this._agentOptions?.Name;
|
||||
|
||||
@@ -214,13 +214,31 @@ public class AIAgentTests
|
||||
[Fact]
|
||||
public void ValidateAgentIDIsIdempotent()
|
||||
{
|
||||
// Arrange
|
||||
var agent = new MockAgent();
|
||||
|
||||
// Act
|
||||
string id = agent.Id;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(id);
|
||||
Assert.Equal(id, agent.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateAgentIDCanBeProvidedByDerivedAgentClass()
|
||||
{
|
||||
// Arrange
|
||||
var agent = new MockAgent(id: "test-agent-id");
|
||||
|
||||
// Act
|
||||
string id = agent.Id;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(id);
|
||||
Assert.Equal("test-agent-id", id);
|
||||
}
|
||||
|
||||
#region GetService Method Tests
|
||||
|
||||
/// <summary>
|
||||
@@ -344,6 +362,13 @@ public class AIAgentTests
|
||||
|
||||
private sealed class MockAgent : AIAgent
|
||||
{
|
||||
public MockAgent(string? id = null)
|
||||
{
|
||||
this.IdCore = id;
|
||||
}
|
||||
|
||||
protected override string? IdCore { get; }
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
@@ -31,7 +32,7 @@ public class DelegatingAIAgentTests
|
||||
this._testThread = new TestAgentThread();
|
||||
|
||||
// Setup inner agent mock
|
||||
this._innerAgentMock.Setup(x => x.Id).Returns("test-agent-id");
|
||||
this._innerAgentMock.Protected().SetupGet<string>("IdCore").Returns("test-agent-id");
|
||||
this._innerAgentMock.Setup(x => x.Name).Returns("Test Agent");
|
||||
this._innerAgentMock.Setup(x => x.Description).Returns("Test Description");
|
||||
this._innerAgentMock.Setup(x => x.GetNewThread()).Returns(this._testThread);
|
||||
@@ -93,7 +94,7 @@ public class DelegatingAIAgentTests
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-agent-id", id);
|
||||
this._innerAgentMock.Verify(x => x.Id, Times.Once);
|
||||
this._innerAgentMock.Protected().VerifyGet<string>("IdCore", Times.Once());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+4
-16
@@ -276,15 +276,9 @@ public sealed class BasicStreamingTests : IAsyncDisposable
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated via dependency injection")]
|
||||
internal sealed class FakeChatClientAgent : AIAgent
|
||||
{
|
||||
public FakeChatClientAgent()
|
||||
{
|
||||
this.Id = "fake-agent";
|
||||
this.Description = "A fake agent for testing";
|
||||
}
|
||||
protected override string? IdCore => "fake-agent";
|
||||
|
||||
public override string Id { get; }
|
||||
|
||||
public override string? Description { get; }
|
||||
public override string? Description => "A fake agent for testing";
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
{
|
||||
@@ -350,15 +344,9 @@ internal sealed class FakeChatClientAgent : AIAgent
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated via dependency injection")]
|
||||
internal sealed class FakeMultiMessageAgent : AIAgent
|
||||
{
|
||||
public FakeMultiMessageAgent()
|
||||
{
|
||||
this.Id = "fake-multi-message-agent";
|
||||
this.Description = "A fake agent that sends multiple messages for testing";
|
||||
}
|
||||
protected override string? IdCore => "fake-multi-message-agent";
|
||||
|
||||
public override string Id { get; }
|
||||
|
||||
public override string? Description { get; }
|
||||
public override string? Description => "A fake agent that sends multiple messages for testing";
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
{
|
||||
|
||||
+2
-2
@@ -421,7 +421,7 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests
|
||||
|
||||
private sealed class MultiResponseAgent : AIAgent
|
||||
{
|
||||
public override string Id => "multi-response-agent";
|
||||
protected override string? IdCore => "multi-response-agent";
|
||||
|
||||
public override string? Description => "Agent that produces multiple text chunks";
|
||||
|
||||
@@ -510,7 +510,7 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests
|
||||
|
||||
private sealed class TestAgent : AIAgent
|
||||
{
|
||||
public override string Id => "test-agent";
|
||||
protected override string? IdCore => "test-agent";
|
||||
|
||||
public override string? Description => "Test agent";
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ internal sealed class HelloAgent(string id = nameof(HelloAgent)) : AIAgent
|
||||
public const string Greeting = "Hello World!";
|
||||
public const string DefaultId = nameof(HelloAgent);
|
||||
|
||||
public override string Id => id;
|
||||
protected override string? IdCore => id;
|
||||
public override string? Name => id;
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public class SpecializedExecutorSmokeTests
|
||||
{
|
||||
public class TestAIAgent(List<ChatMessage>? messages = null, string? id = null, string? name = null) : AIAgent
|
||||
{
|
||||
public override string Id => id ?? base.Id;
|
||||
protected override string? IdCore => id;
|
||||
public override string? Name => name;
|
||||
|
||||
public static List<ChatMessage> ToChatMessages(params string[] messages)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.Agents.AI.Workflows.UnitTests;
|
||||
|
||||
internal class TestEchoAgent(string? id = null, string? name = null, string? prefix = null) : AIAgent
|
||||
{
|
||||
public override string Id => id ?? base.Id;
|
||||
protected override string? IdCore => id;
|
||||
public override string? Name => name ?? base.Name;
|
||||
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
|
||||
Reference in New Issue
Block a user