Skip to content

.Net: Initial check-in for the A2A Agent implementation #12050

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: main
Choose a base branch
from

Conversation

markwallace-microsoft
Copy link
Member

Motivation and Context

Description

Contribution Checklist

@markwallace-microsoft markwallace-microsoft requested a review from a team as a code owner May 14, 2025 07:32
@markwallace-microsoft markwallace-microsoft added the .NET Issue or Pull requests regarding .NET code label May 14, 2025
@github-actions github-actions bot changed the title Initial check-in for the A2A Agent implementation .Net: Initial check-in for the A2A Agent implementation May 14, 2025
@markwallace-microsoft
Copy link
Member Author

@markwallace-microsoft great that you're working on this, I'm eager to try it!

Are there any major things that people should be aware of if they want to try it? When would this be likely to be merged to main?

Hi @hansmbakker
I'm hoping to get this merged and release early next week. The A2A protocol is still being developed so I would expect changes. So far the sample only supports the most basic functionality i.e., there is no support yet for long running tasks. But your feedback on what is the next most important thing to add would be much appreciated.


<ItemGroup>
<PackageReference Include="SharpA2A.Core" />
<PackageReference Include="SharpA2A.AspNetCore" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the asp.net package in the client? I would have expected it's only required for the server?


// Connect to the remote agents via A2A
var createAgentTasks = agentUrls.Select(agentUrl => this.CreateAgentAsync(agentUrl));
var agents = await Task.WhenAll(createAgentTasks);
Copy link
Contributor

@westey-m westey-m Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to also have a basic example where we are interacting directly with a single A2AAgent instance.
This multiagent sample is also great and we should keep it. I'm just thinking it might be easier for people to grasp a single agent one first.

Copy link
Contributor

@westey-m westey-m Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you have a single agent demonstration under samples, so maybe this isn't necessary here then after all.

resolution in SAP CRM and notify the customer via email within 2 business days, referencing the
original invoice and the credit memo number. Use the 'Formal Credit Notification' email
template."
Always reply with exactly this text:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this text intentionally repeated?


1. Run the A2A client:
```bash
cd A2AClient
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are already in the A2AClient directory

Comment on lines +21 to +22
/// <param name="client">A2AClient instance to associate with the agent.</param>
/// <param name="agentCard">AgentCard instance associated ith the agent.</param>
Copy link
Contributor

@westey-m westey-m Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <param name="client">A2AClient instance to associate with the agent.</param>
/// <param name="agentCard">AgentCard instance associated ith the agent.</param>
/// <param name="client"><see cref="A2AClient"/> instance to associate with the agent.</param>
/// <param name="agentCard"><see cref="AgentCard"/> instance associated with the agent.</param>

}
}

private async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> InvokeAgentAsync(string name, ChatMessageContent message, A2AAgentThread thread, AgentInvokeOptions options, [EnumeratorCancellation] CancellationToken cancellationToken)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the name param being used in the method. Am I missing something?

Comment on lines +29 to +31
this.AgentCard = agentCard;
this.Name = agentCard.Name;
this.Description = agentCard.Description;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like these are required to ensure the functioning of the Agent. Could we make agentCard optional?

.Build();
var apiKey = configRoot["A2AClient:ApiKey"] ?? throw new ArgumentException("A2AClient:ApiKey must be provided");
var modelId = configRoot["A2AClient:ModelId"] ?? "gpt-4.1";
var agentUrls = configRoot["A2AClient:AgentUrls"] ?? "http://localhost:5000/ http://localhost:5001/ http://localhost:5002/";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Wondering if comma separator or ; will work as well here, since space separator is a little bit unclear.

this._httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));

// Create a retry policy for transient HTTP errors
this._retryPolicy = Policy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would configure retry policy on HttpClient level, so CurrencyPlugin will be decoupled from HTTP-related logic, but since it's a demo app, it's not critical, so feel free to skip.

{
Verify.NotNull(messages);

foreach (var message in messages)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, this is interesting. In the CopilotChatAgent we concatenate the input messages and invoke once where here we invoke for each message.
For inference type agents we invoke once with all messages too, so they are processed in one go. Not sure if we want to follow that approach everywhere.

};

A2AResponse response = await this.Client.SendMessageAsync(messageSendParams).ConfigureAwait(false);
if (response is AgentTask agentTask)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's an AgentTask?

/// <summary>
/// Gets the session id of the current thread.
/// </summary>
public string SessionId { get; init; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need SessionId as well if we already have id?

/// Attach the <see cref="A2AAgent"/> to the provided <see cref="ITaskManager"/>
/// </summary>
/// <param name="taskManager"></param>
public void Attach(ITaskManager taskManager)
Copy link
Contributor

@westey-m westey-m Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you need a new TaskManager instance for each agent instance. Do we think it would be necessary to add the same agent to multiple task managers? It doesn't look like that's supported since we only have one taskmanager field here.
It might be easier for the consumer if we just wrapped the TaskManager in the A2AHostAgent, so that the consumer doesn't have to know about it.
And maybe add an extension method for just mapping an Agent like this:

app.MapA2A(myA2AHostAgent);


namespace A2A;

internal abstract class AzureAIHostAgent : A2AHostAgent
Copy link
Contributor

@westey-m westey-m Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of inheriting from A2AHostAgent also consider instantiating an A2AHostAgent with an Agent instance, so it is just a standalone utility. Forcing users to inherit from A2AHostAgent to get the adapter functionality seems somewhat opinionated to me.
I can see a case where we may just want to support:

var myA2AAgent = myAgent.ToA2AAgent(card);
app.MapA2A(myA2AAgent);

using System.Diagnostics.CodeAnalysis;

// This assembly is currently experimental.
[assembly: Experimental("SKEXP0110")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this implied by it being alpha?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation .NET Issue or Pull requests regarding .NET code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants