From 3f835c8118e980f3f7fe1d722fc43fa83d77f967 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 5 Nov 2025 14:16:53 -0800 Subject: [PATCH] .NET: AgentHostExecutor should use agent DisplayName as Executor ID (#1840) * AgentHostExecutor should use agent DisplayName as Executor ID * Address comments * Remove unintended changes * Remove unintended changes * Fix tests and address comments --- .../AIAgentBinding.cs | 2 +- .../Specialized/AIAgentHostExecutor.cs | 2 +- .../SpecializedExecutorSmokeTests.cs | 47 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/AIAgentBinding.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/AIAgentBinding.cs index 171f7d9eec..4897189d90 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/AIAgentBinding.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/AIAgentBinding.cs @@ -11,7 +11,7 @@ namespace Microsoft.Agents.AI.Workflows; /// The AI agent. /// Specifies whether the agent should emit events. If null, the default behavior is applied. public record AIAgentBinding(AIAgent Agent, bool EmitEvents = false) - : ExecutorBinding(Throw.IfNull(Agent).Name ?? Throw.IfNull(Agent.Id), + : ExecutorBinding(Throw.IfNull(Agent).GetDescriptiveId(), (_) => new(new AIAgentHostExecutor(Agent, EmitEvents)), typeof(AIAgentHostExecutor), Agent) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs index cd8386a268..0a887013a3 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs @@ -14,7 +14,7 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor private readonly AIAgent _agent; private AgentThread? _thread; - public AIAgentHostExecutor(AIAgent agent, bool emitEvents = false) : base(id: agent.Id) + public AIAgentHostExecutor(AIAgent agent, bool emitEvents = false) : base(id: agent.GetDescriptiveId()) { this._agent = agent; this._emitEvents = emitEvents; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs index 87a29690db..b93d7862d5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs @@ -8,6 +8,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Agents.AI.Workflows.Specialized; using Microsoft.Extensions.AI; @@ -196,4 +197,50 @@ public class SpecializedExecutorSmokeTests collected.Text.Should().Be(expectedText); } } + + [Fact] + public async Task Test_AIAgent_ExecutorId_Use_Agent_NameAsync() + { + const string AgentAName = "TestAgentAName"; + const string AgentBName = "TestAgentBName"; + TestAIAgent agentA = new(name: AgentAName); + TestAIAgent agentB = new(name: AgentBName); + var workflow = new WorkflowBuilder(agentA).AddEdge(agentA, agentB).Build(); + var definition = workflow.ToWorkflowInfo(); + + // Verify that the agent host executor registration IDs in the workflow definition + // match the agent names when agent names are provided. + // The property DisplayName falls back to using the agent ID when Name is not set. + agentA.GetDescriptiveId().Should().Contain(AgentAName); + agentB.GetDescriptiveId().Should().Contain(AgentBName); + definition.Executors[agentA.GetDescriptiveId()].ExecutorId.Should().Be(agentA.GetDescriptiveId()); + definition.Executors[agentB.GetDescriptiveId()].ExecutorId.Should().Be(agentB.GetDescriptiveId()); + + // This will create an instance of the start agent and verify that the ID + // of the executor instance matches the ID of the registration. + var protocolDescriptor = await workflow.DescribeProtocolAsync(); + protocolDescriptor.Accepts.Should().Contain(typeof(ChatMessage)); + } + + [Fact] + public async Task Test_AIAgent_ExecutorId_Use_Agent_ID_When_Name_Not_ProvidedAsync() + { + TestAIAgent agentA = new(); + TestAIAgent agentB = new(); + var workflow = new WorkflowBuilder(agentA).AddEdge(agentA, agentB).Build(); + var definition = workflow.ToWorkflowInfo(); + + // Verify that the agent host executor registration IDs in the workflow definition + // match the agent IDs when agent names are not provided. + // The property DisplayName falls back to using the agent ID when Name is not set. + agentA.GetDescriptiveId().Should().Contain(agentA.Id); + agentB.GetDescriptiveId().Should().Contain(agentB.Id); + definition.Executors[agentA.GetDescriptiveId()].ExecutorId.Should().Be(agentA.GetDescriptiveId()); + definition.Executors[agentB.GetDescriptiveId()].ExecutorId.Should().Be(agentB.GetDescriptiveId()); + + // This will create an instance of the start agent and verify that the ID + // of the executor instance matches the ID of the registration. + var protocolDescriptor = await workflow.DescribeProtocolAsync(); + protocolDescriptor.Accepts.Should().Contain(typeof(ChatMessage)); + } }