Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@

<ItemGroup>
<PackageReference Include="A2A" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>

</Project>

This file was deleted.

92 changes: 28 additions & 64 deletions dotnet/samples/05-end-to-end/A2AClientServer/A2AClient/Program.cs
Original file line number Diff line number Diff line change
@@ -1,77 +1,41 @@
// Copyright (c) Microsoft. All rights reserved.

using System.CommandLine;
using System.Reflection;
// This sample shows how to discover and invoke an agent hosted by an A2A server.

using A2A;
using Microsoft.Agents.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace A2A;
// Initialize an A2ACardResolver to discover the policy agent.
var agentUrl = Environment.GetEnvironmentVariable("A2A_AGENT_URL") ?? "http://localhost:5000/";
var agentCardResolver = new A2ACardResolver(new Uri(agentUrl));

// Create an AIAgent from the A2A agent card.
AIAgent policyAgent = await agentCardResolver.GetAIAgentAsync();

// Create a session so requests share the same A2A protocol context.
AgentSession session = await policyAgent.CreateSessionAsync();

public static class Program
while (true)
{
public static async Task<int> Main(string[] args)
{
// Create root command with options
var rootCommand = new RootCommand("A2AClient");
rootCommand.SetAction((_, ct) => HandleCommandsAsync(ct));
// Read the next request from the console.
Console.Write("\nUser (:q or quit to exit): ");
string? message = Console.ReadLine();

// Run the command
return await rootCommand.Parse(args).InvokeAsync();
if (string.IsNullOrWhiteSpace(message))
{
Console.WriteLine("Request cannot be empty.");
continue;
}

private static async Task HandleCommandsAsync(CancellationToken cancellationToken)
if (message is ":q" or "quit")
{
// Set up the logging
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Information);
});
var logger = loggerFactory.CreateLogger("A2AClient");

// Retrieve configuration settings
IConfigurationRoot configRoot = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddUserSecrets(Assembly.GetExecutingAssembly())
.Build();
var apiKey = configRoot["A2AClient:ApiKey"] ?? throw new ArgumentException("A2AClient:ApiKey must be provided");
var modelId = configRoot["A2AClient:ModelId"] ?? "gpt-5.4-mini";
var agentUrls = configRoot["A2AClient:AgentUrls"] ?? "http://localhost:5000/;http://localhost:5001/;http://localhost:5002/";

// Create the Host agent
var hostAgent = new HostClientAgent(loggerFactory);
await hostAgent.InitializeAgentAsync(modelId, apiKey, agentUrls!.Split(";"));
AgentSession session = await hostAgent.Agent!.CreateSessionAsync(cancellationToken);
try
{
while (true)
{
// Get user message
Console.Write("\nUser (:q or quit to exit): ");
string? message = Console.ReadLine();
if (string.IsNullOrWhiteSpace(message))
{
Console.WriteLine("Request cannot be empty.");
continue;
}

if (message is ":q" or "quit")
{
break;
}
break;
}

var agentResponse = await hostAgent.Agent!.RunAsync(message, session, cancellationToken: cancellationToken);
// Invoke the remote policy agent over A2A and display its response.
AgentResponse response = await policyAgent.RunAsync(message, session);

Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\nAgent: {agentResponse.Text}");
Console.ResetColor();
}
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while running the A2AClient");
return;
}
}
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\nPolicy agent: {response.Text}");
Console.ResetColor();
}
66 changes: 49 additions & 17 deletions dotnet/samples/05-end-to-end/A2AClientServer/A2AClient/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,58 @@

# A2A Client Sample
Show how to create an A2A Client with a command line interface which invokes agents using the A2A protocol.
# A2A client

## Run the Sample
This console client discovers and invokes the policy agent using the A2A protocol.

To run the sample, follow these steps:
## Run the sample

1. Run the A2A client:
```bash
cd A2AClient
dotnet run
```
2. Enter your request e.g. "Show me all invoices for Contoso?"
Start the server in a separate terminal:

## Set Environment Variables
```powershell
az login
$env:FOUNDRY_PROJECT_ENDPOINT="<your-project-endpoint>"
$env:FOUNDRY_MODEL="gpt-5.4-mini"
cd dotnet\samples\05-end-to-end\A2AClientServer\A2AServer
dotnet run
```

Keep the server running, then start the client:

```powershell
cd dotnet\samples\05-end-to-end\A2AClientServer\A2AClient
dotnet run
```

The agent urls are provided as a ` ` delimited list of strings
Ask a question such as `What is the policy for short shipments?`.

The client connects to `http://localhost:5000/` by default. To use another
endpoint, restart the server with its listening and advertised URLs set:

```powershell
cd dotnet/samples/05-end-to-end/A2AClientServer/A2AClient
$env:ASPNETCORE_URLS="http://localhost:6000"
$env:A2A_AGENT_URL="http://localhost:6000/"
Comment thread
SergeyMenshykh marked this conversation as resolved.
dotnet run
```

$env:OPENAI_CHAT_MODEL_NAME="gpt-5.4-mini"
$env:OPENAI_API_KEY="<Your OPENAI api key>"
$env:AGENT_URLS="http://localhost:5000/policy;http://localhost:5000/invoice;http://localhost:5000/logistics"
Then set the discovery URL before starting the client:

```powershell
$env:A2A_AGENT_URL="http://localhost:6000/"
dotnet run
```

## Test the server with the HTTP file

With the server running, open `..\A2AServer\A2AServer.http` in an editor that
supports HTTP files, such as Visual Studio or Visual Studio Code with an HTTP
client extension. Run the first request to retrieve the agent card, or the
second request to invoke the policy agent directly.

The file targets `http://localhost:5000` by default. Update its `@host` variable
if the server listens at another address.

## Inspect the server with A2A Inspector

Follow the [A2A Inspector setup instructions](https://github.com/a2aproject/a2a-inspector)
and connect it to the running server at `http://localhost:5000`.

The Inspector provides another A2A client for viewing the agent card and sending
messages without running this console client.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>

Expand All @@ -24,7 +23,6 @@

<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,85 +1,25 @@
### Each A2A agent is available at a different host address
@hostInvoice = http://localhost:5000
@hostPolicy = http://localhost:5001
@hostLogistics = http://localhost:5002
@host = http://localhost:5000

### Query agent card for the invoice agent
GET {{hostInvoice}}/.well-known/agent-card.json

### Send a message to the invoice agent
POST {{hostInvoice}}
Content-Type: application/json

{
"id": "1",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"id": "12345",
"message": {
"kind": "message",
"role": "user",
"messageId": "msg_1",
"parts": [
{
"kind": "text",
"text": "Show me all invoices for Contoso?"
}
]
}
}
}

### Query agent card for the policy agent
GET {{hostPolicy}}/.well-known/agent-card.json
### Query the policy agent card
GET {{host}}/.well-known/agent-card.json

### Send a message to the policy agent
POST {{hostPolicy}}
POST {{host}}
Content-Type: application/json

{
"id": "1",
"jsonrpc": "2.0",
"method": "message/send",
"method": "SendMessage",
"params": {
"id": "12345",
"message": {
"kind": "message",
"role": "user",
"role": "ROLE_USER",
"messageId": "msg_1",
"parts": [
{
"kind": "text",
"text": "What is the policy for short shipments?"
}
]
}
}
}

### Query agent card for the logistics agent
GET {{hostLogistics}}/.well-known/agent-card.json

### Send a message to the logistics agent
POST {{hostLogistics}}
Content-Type: application/json

{
"id": "1",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"id": "12345",
"message": {
"kind": "message",
"role": "user",
"messageId": "msg_1",
"parts": [
{
"kind": "text",
"text": "What is the status for SHPMT-SAP-001?"
}
]
}
}
}
Loading
Loading