From bc6573f47539e631311c8093197a7e9dbca89bcc Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Thu, 10 Jul 2025 14:06:45 +0100
Subject: [PATCH] Remove instructions from base agent type, and ensure
description is only required where necessary. (#154)
* Remove instructions from base agent type, and ensure description is only required where necessary.
* Fix code issue.
* Remove unused import.
* Remove typo
* Make the description parameter optional
* Also use name for handoffs if no description is provided.
---
.../AgentActor.cs | 8 +-----
.../Handoff/Handoffs.cs | 8 +++++-
.../OrchestrationActor.cs | 2 +-
.../Agent.cs | 7 +----
.../ActorMetadata.cs | 4 +--
.../RuntimeActor.cs | 2 +-
.../ChatCompletion/ChatClientAgent.cs | 6 +++--
.../HandoffsTests.cs | 26 ++++++++++++++++---
8 files changed, 39 insertions(+), 24 deletions(-)
diff --git a/dotnet/src/Microsoft.Agents.Orchestration/AgentActor.cs b/dotnet/src/Microsoft.Agents.Orchestration/AgentActor.cs
index fb83e266d3..e78638d959 100644
--- a/dotnet/src/Microsoft.Agents.Orchestration/AgentActor.cs
+++ b/dotnet/src/Microsoft.Agents.Orchestration/AgentActor.cs
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -30,7 +29,7 @@ public abstract class AgentActor : OrchestrationActor
id,
runtime,
context,
- VerifyDescription(agent),
+ agent.Description,
logger)
{
this.Agent = agent;
@@ -162,9 +161,4 @@ public abstract class AgentActor : OrchestrationActor
}
}
}
-
- private static string VerifyDescription(Agent agent)
- {
- return agent.Description ?? throw new ArgumentException($"Missing agent description: {agent.Name ?? agent.Id}", nameof(agent));
- }
}
diff --git a/dotnet/src/Microsoft.Agents.Orchestration/Handoff/Handoffs.cs b/dotnet/src/Microsoft.Agents.Orchestration/Handoff/Handoffs.cs
index d779abbe27..797ee965fa 100644
--- a/dotnet/src/Microsoft.Agents.Orchestration/Handoff/Handoffs.cs
+++ b/dotnet/src/Microsoft.Agents.Orchestration/Handoff/Handoffs.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Collections.Generic;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime;
@@ -84,7 +85,12 @@ public static class OrchestrationHandoffsExtensions
foreach (Agent target in targets)
{
- agentHandoffs[target.Name ?? target.Id] = target.Description ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(target.Description) && string.IsNullOrWhiteSpace(target.Name))
+ {
+ throw new InvalidOperationException($"The provided target agent with Id '{target.Id}' has no description or name, and no handoff description has been provided. At least one of these are required to register a handoff so that the appropriate target agent can be chosen.");
+ }
+
+ agentHandoffs[target.Name ?? target.Id] = target.Description ?? target.Name!;
}
return handoffs;
diff --git a/dotnet/src/Microsoft.Agents.Orchestration/OrchestrationActor.cs b/dotnet/src/Microsoft.Agents.Orchestration/OrchestrationActor.cs
index d3f3f53629..165a83f766 100644
--- a/dotnet/src/Microsoft.Agents.Orchestration/OrchestrationActor.cs
+++ b/dotnet/src/Microsoft.Agents.Orchestration/OrchestrationActor.cs
@@ -15,7 +15,7 @@ public abstract class OrchestrationActor : RuntimeActor
///
/// Initializes a new instance of the class.
///
- protected OrchestrationActor(ActorId id, IAgentRuntime runtime, OrchestrationContext context, string description, ILogger? logger = null)
+ protected OrchestrationActor(ActorId id, IAgentRuntime runtime, OrchestrationContext context, string? description = null, ILogger? logger = null)
: base(id, runtime, description, logger)
{
this.Context = context;
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Abstractions/Agent.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Abstractions/Agent.cs
index 62ad1d35ac..5e7d942477 100644
--- a/dotnet/src/Microsoft.Extensions.AI.Agents.Abstractions/Agent.cs
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Abstractions/Agent.cs
@@ -15,7 +15,7 @@ namespace Microsoft.Extensions.AI.Agents;
public abstract class Agent
{
///
- /// Gets the identifier of the agent (optional).
+ /// Gets the identifier of the agent.
///
///
/// 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.
@@ -32,11 +32,6 @@ public abstract class Agent
///
public virtual string? Description { get; }
- ///
- /// Gets the instructions for the agent (optional).
- ///
- public virtual string? Instructions { get; }
-
///
/// Get a new instance that is compatible with the agent.
///
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMetadata.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMetadata.cs
index 6e28c69f93..317907deaa 100644
--- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMetadata.cs
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMetadata.cs
@@ -15,7 +15,7 @@ public readonly struct ActorMetadata : IEquatable
/// The type of the actor.
/// The unique key associated with the actor.
/// A brief description of the actor.
- public ActorMetadata(ActorType type, string key, string description)
+ public ActorMetadata(ActorType type, string key, string? description = null)
{
if (!ActorId.IsValidKey(key))
{
@@ -46,7 +46,7 @@ public readonly struct ActorMetadata : IEquatable
///
/// A brief description of the actor's purpose or functionality.
///
- public string Description { get; }
+ public string? Description { get; }
///
public override readonly bool Equals(object? obj) =>
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/RuntimeActor.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/RuntimeActor.cs
index 44187a196b..81da06e9f0 100644
--- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/RuntimeActor.cs
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/RuntimeActor.cs
@@ -53,7 +53,7 @@ public abstract class RuntimeActor : IRuntimeActor
protected RuntimeActor(
ActorId id,
IAgentRuntime runtime,
- string description,
+ string? description = null,
ILogger? logger = null)
{
this.Id = id;
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs b/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs
index 482ea7da30..d530f27cf2 100644
--- a/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs
@@ -56,8 +56,10 @@ public sealed class ChatClientAgent : Agent
///
public override string? Description => this._agentOptions?.Description;
- ///
- public override string? Instructions => this._agentOptions?.Instructions;
+ ///
+ /// Gets the instructions for the agent (optional).
+ ///
+ public string? Instructions => this._agentOptions?.Instructions;
///
/// Gets of the default used by the agent.
diff --git a/dotnet/tests/Microsoft.Agents.Orchestration.UnitTests/HandoffsTests.cs b/dotnet/tests/Microsoft.Agents.Orchestration.UnitTests/HandoffsTests.cs
index b2878e0676..52357167ed 100644
--- a/dotnet/tests/Microsoft.Agents.Orchestration.UnitTests/HandoffsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.Orchestration.UnitTests/HandoffsTests.cs
@@ -201,23 +201,41 @@ public class HandoffsTests
}
[Fact]
- public void AddWithTargetWithNoDescriptionUsesEmptyString()
+ public void AddWithAgentWithNoDescriptionUsesName()
{
// Arrange
OrchestrationHandoffs handoffs = new("source");
Agent sourceAgent = CreateAgent("source", "Source Agent");
- Agent targetAgent = CreateAgent("target", null);
+ Agent targetAgent1 = CreateAgent("target1", name: "target 1");
// Act
- handoffs.Add(sourceAgent, targetAgent);
+ handoffs.Add(sourceAgent, targetAgent1);
// Assert
Assert.Single(handoffs);
Assert.Equal("source", handoffs.FirstAgentName);
+ Assert.True(handoffs.ContainsKey("source"));
+
AgentHandoffs sourceHandoffs = handoffs["source"];
Assert.Single(sourceHandoffs);
- Assert.Equal(string.Empty, sourceHandoffs["target"]);
+ Assert.Equal("target 1", sourceHandoffs["target 1"]);
+ }
+
+ [Fact]
+ public void AddWithAgentWithNoDescriptionOrNameThrows()
+ {
+ // Arrange
+ OrchestrationHandoffs handoffs = new("source");
+
+ Agent sourceAgent = CreateAgent("source", "Source Agent");
+ Agent targetAgent1 = CreateAgent("target1");
+
+ // Act
+ InvalidOperationException ex = Assert.Throws(() => handoffs.Add(sourceAgent, targetAgent1));
+
+ // Assert
+ Assert.Equal("The provided target agent with Id 'target1' has no description or name, and no handoff description has been provided. At least one of these are required to register a handoff so that the appropriate target agent can be chosen.", ex.Message);
}
private static ChatClientAgent CreateAgent(string id, string? description = null, string? name = null)