Skip to content

Commit 9784e59

Browse files
Merge Shawn's changes and support ChatCompletion or AzureAI agents
1 parent d2ba0d8 commit 9784e59

File tree

6 files changed

+248
-30
lines changed

6 files changed

+248
-30
lines changed

dotnet/samples/Demos/A2AClientServer/A2AClient/HostClientAgent.cs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,48 @@
77
using SharpA2A.Core;
88

99
namespace A2A;
10+
internal sealed class ConsoleOutputFunctionInvocationFilter() : IFunctionInvocationFilter
11+
{
12+
private static string IndentMultilineString(string multilineText, int indentLevel = 1, int spacesPerIndent = 4)
13+
{
14+
// Create the indentation string
15+
string indentation = new string(' ', indentLevel * spacesPerIndent);
16+
17+
// Split the text into lines, add indentation, and rejoin
18+
char[] NewLineChars = { '\r', '\n' };
19+
string[] lines = multilineText.Split(NewLineChars, StringSplitOptions.None);
20+
21+
return string.Join(Environment.NewLine, lines.Select(line => indentation + line));
22+
}
23+
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
24+
{
25+
Console.ForegroundColor = ConsoleColor.DarkGray;
26+
27+
Console.WriteLine($"\nCalling Agent {context.Function.Name} with arguments:");
28+
Console.ForegroundColor = ConsoleColor.Gray;
29+
30+
foreach (var kvp in context.Arguments)
31+
{
32+
Console.WriteLine(IndentMultilineString($" {kvp.Key}: {kvp.Value}"));
33+
}
34+
35+
await next(context);
36+
37+
if (context.Result.GetValue<object>() is ChatMessageContent[] chatMessages)
38+
{
39+
Console.ForegroundColor = ConsoleColor.DarkGray;
40+
41+
Console.WriteLine($"Got Response from Agent {context.Function.Name}:");
42+
foreach (var message in chatMessages)
43+
{
44+
Console.ForegroundColor = ConsoleColor.Gray;
45+
46+
Console.WriteLine(IndentMultilineString($"{message}"));
47+
}
48+
}
49+
Console.ResetColor();
50+
}
51+
}
1052

1153
internal sealed class HostClientAgent
1254
{
@@ -23,23 +65,32 @@ internal async Task InitializeAgentAsync(string modelId, string apiKey, string b
2365
// Connect to the remote agents via A2A
2466
var agentPlugin = KernelPluginFactory.CreateFromFunctions("AgentPlugin",
2567
[
26-
AgentKernelFunctionFactory.CreateFromAgent(await this.CreateAgentAsync($"{baseAddress}/currency/")),
27-
AgentKernelFunctionFactory.CreateFromAgent(await this.CreateAgentAsync($"{baseAddress}/invoice/"))
68+
AgentKernelFunctionFactory.CreateFromAgent(await this.CreateAgentAsync($"{baseAddress}/invoice/")),
69+
AgentKernelFunctionFactory.CreateFromAgent(await this.CreateAgentAsync($"{baseAddress}/logistics/")),
70+
AgentKernelFunctionFactory.CreateFromAgent(await this.CreateAgentAsync($"{baseAddress}/policy/"))
71+
2872
]);
2973

30-
// Define the TravelPlannerAgent
74+
// Define the Agent
3175
var builder = Kernel.CreateBuilder();
3276
builder.AddOpenAIChatCompletion(modelId, apiKey);
3377
builder.Plugins.Add(agentPlugin);
78+
var consoleOutputFilter = new ConsoleOutputFunctionInvocationFilter();
3479
var kernel = builder.Build();
80+
kernel.FunctionInvocationFilters.Add(consoleOutputFilter);
3581

3682
this.Agent = new ChatCompletionAgent()
3783
{
3884
Kernel = kernel,
3985
Name = "HostClient",
4086
Instructions =
4187
"""
42-
You specialize in handling queries for users and using your tools to provide answers.
88+
You specialize in handling queries for users and using your tools to provide answers.
89+
90+
Think carefully about the question and the tools you have available and execute a
91+
multistep plan to get the answer.
92+
93+
4394
""",
4495
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
4596
};

dotnet/samples/Demos/A2AClientServer/A2AClient/Program.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ private static async System.Threading.Tasks.Task RunCliAsync(string agentUrl)
6262
// Create the Host agent
6363
var hostAgent = new HostClientAgent(logger);
6464
await hostAgent.InitializeAgentAsync(modelId, apiKey, baseAddress);
65-
65+
AgentThread thread = new ChatHistoryAgentThread();
6666
try
6767
{
6868
while (true)
@@ -81,12 +81,14 @@ private static async System.Threading.Tasks.Task RunCliAsync(string agentUrl)
8181
break;
8282
}
8383

84-
Console.ForegroundColor = ConsoleColor.Cyan;
85-
await foreach (AgentResponseItem<ChatMessageContent> response in hostAgent.Agent!.InvokeAsync(message))
84+
await foreach (AgentResponseItem<ChatMessageContent> response in hostAgent.Agent!.InvokeAsync(message, thread))
8685
{
86+
Console.ForegroundColor = ConsoleColor.Cyan;
8787
Console.WriteLine($"Agent: {response.Message.Content}");
88+
Console.ResetColor();
89+
90+
thread = response.Thread;
8891
}
89-
Console.ResetColor();
9092
}
9193
}
9294
catch (Exception ex)

dotnet/samples/Demos/A2AClientServer/A2AServer/InvoiceAgent.cs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
using Microsoft.SemanticKernel;
55
using Microsoft.SemanticKernel.Agents;
66
using Microsoft.SemanticKernel.Agents.A2A;
7+
using Azure.AI.Projects;
8+
79
using SharpA2A.Core;
810

11+
using Azure.Identity;
12+
using Microsoft.SemanticKernel.Agents.AzureAI;
13+
914
namespace A2A;
1015

1116
internal sealed class InvoiceAgent : A2AHostAgent
@@ -52,7 +57,7 @@ public override AgentCard GetAgentCard(string agentUrl)
5257
#region private
5358
private readonly ILogger _logger;
5459

55-
private ChatCompletionAgent InitializeAgent(string modelId, string apiKey)
60+
private Microsoft.SemanticKernel.Agents.Agent InitializeAgent(string modelId, string apiKey)
5661
{
5762
try
5863
{
@@ -63,16 +68,34 @@ private ChatCompletionAgent InitializeAgent(string modelId, string apiKey)
6368
builder.AddOpenAIChatCompletion(modelId, apiKey);
6469
builder.Plugins.AddFromType<InvoiceQueryPlugin>();
6570
var kernel = builder.Build();
66-
return new ChatCompletionAgent()
67-
{
68-
Kernel = kernel,
69-
Name = "InvoiceAgent",
70-
Instructions =
71-
"""
72-
You specialize in handling queries related to invoices.
73-
""",
74-
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
75-
};
71+
72+
var connectionString = "eastus.api.azureml.ms;921496dc-987f-410f-bd57-426eb2611356;ai-agents-karthik-eu;project-demo-eu-fw7g";
73+
AgentsClient client = new(connectionString, new DefaultAzureCredential());
74+
var azureAgent = client.GetAgent("asst_KDXzkTJsdWg9acJNdaQkhozV");
75+
AzureAIAgent agent = new AzureAIAgent(
76+
azureAgent,
77+
client
78+
);
79+
80+
return agent;
81+
82+
// return new ChatCompletionAgent()
83+
// {
84+
// Kernel = kernel,
85+
// Name = "InvoiceAgent",
86+
// Instructions =
87+
// """
88+
// You specialize in handling queries related to invoices.
89+
90+
// Always reply with exactly this text:
91+
92+
// - Invoice Number: INV789
93+
// - Customer Name: John Doe
94+
// - Item: TSHIRT-RED-L
95+
// - Quantity Ordered: 1000
96+
// """,
97+
// Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
98+
// };
7699
}
77100
catch (Exception ex)
78101
{
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
using Microsoft.Extensions.Logging;
3+
using Microsoft.SemanticKernel;
4+
using Microsoft.SemanticKernel.Agents;
5+
using Microsoft.SemanticKernel.Agents.A2A;
6+
using SharpA2A.Core;
7+
using Azure.AI.Projects;
8+
using Azure.Identity;
9+
using Microsoft.SemanticKernel.Agents.AzureAI;
10+
11+
namespace A2A;
12+
13+
internal sealed class LogisticsAgent : A2AHostAgent
14+
{
15+
internal LogisticsAgent(string modelId, string apiKey, ILogger logger) : base(logger)
16+
{
17+
this._logger = logger;
18+
19+
// Add TextSearch over the shipping policies
20+
21+
this.Agent = this.InitializeAgent(modelId, apiKey);
22+
}
23+
24+
public override AgentCard GetAgentCard(string agentUrl)
25+
{
26+
var capabilities = new AgentCapabilities()
27+
{
28+
Streaming = false,
29+
PushNotifications = false,
30+
};
31+
32+
var invoiceQuery = new AgentSkill()
33+
{
34+
Id = "id_logistics_agent",
35+
Name = "LogisticsAgent",
36+
Description = "Handle logistics queries",
37+
Tags = ["logistics", "semantic-kernel"],
38+
Examples =
39+
[
40+
"What is the status for SHPMT-SAP-001",
41+
],
42+
};
43+
44+
return new AgentCard()
45+
{
46+
Name = "LogisticsAgent",
47+
Description = "Handle logistics queries",
48+
Url = agentUrl,
49+
Version = "1.0.0",
50+
DefaultInputModes = ["text"],
51+
DefaultOutputModes = ["text"],
52+
Capabilities = capabilities,
53+
Skills = [invoiceQuery],
54+
};
55+
}
56+
57+
#region private
58+
private readonly ILogger _logger;
59+
60+
private Microsoft.SemanticKernel.Agents.Agent InitializeAgent(string modelId, string apiKey)
61+
{
62+
try
63+
{
64+
this._logger.LogInformation("Initializing Semantic Kernel agent with model {ModelId}", modelId);
65+
66+
// Define the TravelPlannerAgent
67+
var builder = Kernel.CreateBuilder();
68+
builder.AddOpenAIChatCompletion(modelId, apiKey);
69+
//builder.Plugins.AddFromObject(this._policyPlugin);
70+
var kernel = builder.Build();
71+
var connectionString = "eastus.api.azureml.ms;921496dc-987f-410f-bd57-426eb2611356;ai-agents-karthik-eu;project-demo-eu-fw7g";
72+
AgentsClient client = new(connectionString, new DefaultAzureCredential());
73+
var azureAgent = client.GetAgent("asst_K9bNwKFjlvJggAMCT0dHrnS4");
74+
AzureAIAgent agent = new AzureAIAgent(
75+
azureAgent,
76+
client
77+
);
78+
return agent;
79+
80+
// return new ChatCompletionAgent()
81+
// {
82+
// Kernel = kernel,
83+
// Name = "LogisticsAgent",
84+
// Instructions =
85+
// """
86+
// You specialize in handling queries related to logisitics
87+
88+
// Always reply with exactly:
89+
90+
// Shipment number: SHPMT-SAP-001
91+
// Item: TSHIRT-RED-L
92+
// Quantity: 900
93+
// """,
94+
// Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
95+
// };
96+
}
97+
catch (Exception ex)
98+
{
99+
this._logger.LogError(ex, "Failed to initialize InvoiceAgent");
100+
throw;
101+
}
102+
}
103+
#endregion
104+
}
105+

dotnet/samples/Demos/A2AClientServer/A2AServer/PolicyAgent.cs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
using Microsoft.SemanticKernel.Agents;
55
using Microsoft.SemanticKernel.Agents.A2A;
66
using SharpA2A.Core;
7+
using Azure.AI.Projects;
8+
using Azure.Identity;
9+
using Microsoft.SemanticKernel.Agents.AzureAI;
710

811
namespace A2A;
912

@@ -54,7 +57,7 @@ public override AgentCard GetAgentCard(string agentUrl)
5457
#region private
5558
private readonly ILogger _logger;
5659

57-
private ChatCompletionAgent InitializeAgent(string modelId, string apiKey)
60+
private Microsoft.SemanticKernel.Agents.Agent InitializeAgent(string modelId, string apiKey)
5861
{
5962
try
6063
{
@@ -65,16 +68,38 @@ private ChatCompletionAgent InitializeAgent(string modelId, string apiKey)
6568
builder.AddOpenAIChatCompletion(modelId, apiKey);
6669
//builder.Plugins.AddFromObject(this._policyPlugin);
6770
var kernel = builder.Build();
68-
return new ChatCompletionAgent()
69-
{
70-
Kernel = kernel,
71-
Name = "PolicyAgent",
72-
Instructions =
73-
"""
74-
You specialize in handling queries related to policies and customer communications.
75-
""",
76-
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
77-
};
71+
72+
var connectionString = "eastus.api.azureml.ms;921496dc-987f-410f-bd57-426eb2611356;ai-agents-karthik-eu;project-demo-eu-fw7g";
73+
AgentsClient client = new(connectionString, new DefaultAzureCredential());
74+
var azureAgent = client.GetAgent("asst_oMdRHokGALoSY48V20uIxxhB");
75+
AzureAIAgent agent = new AzureAIAgent(
76+
azureAgent,
77+
client
78+
);
79+
80+
return agent;
81+
82+
// return new ChatCompletionAgent()
83+
// {
84+
// Kernel = kernel,
85+
// Name = "PolicyAgent",
86+
// Instructions =
87+
// """
88+
// You specialize in handling queries related to policies and customer communications.
89+
90+
// Always reply with exactly this text:
91+
92+
// Policy: Short Shipment Dispute Handling Policy V2.1
93+
94+
// Summary: "For short shipments reported by customers, first verify internal shipment records
95+
// (SAP) and physical logistics scan data (BigQuery). If discrepancy is confirmed and logistics data
96+
// shows fewer items packed than invoiced, issue a credit for the missing items. Document the
97+
// resolution in SAP CRM and notify the customer via email within 2 business days, referencing the
98+
// original invoice and the credit memo number. Use the 'Formal Credit Notification' email
99+
// template."
100+
// """,
101+
// Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
102+
// };
78103
}
79104
catch (Exception ex)
80105
{

dotnet/samples/Demos/A2AClientServer/A2AServer/Program.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@
2222
invoiceAgent.Attach(invoiceTaskManager);
2323
app.MapA2A(invoiceTaskManager, "/invoice");
2424

25+
var logisticsAgent = new LogisticsAgent(modelId, apiKey, logger);
26+
var logisiticsTaskManager = new TaskManager();
27+
logisticsAgent.Attach(logisiticsTaskManager);
28+
app.MapA2A(logisiticsTaskManager, "/logistics");
29+
30+
31+
var policyAgent = new PolicyAgent(modelId, apiKey, logger);
32+
var policyTaskManager = new TaskManager();
33+
policyAgent.Attach(policyTaskManager);
34+
app.MapA2A(policyTaskManager, "/policy");
35+
36+
2537
var currencyAgent = new CurrencyAgent(modelId, apiKey, logger);
2638
var currencyTaskManager = new TaskManager();
2739
currencyAgent.Attach(currencyTaskManager);

0 commit comments

Comments
 (0)