Skip to content

Commit c1e6ca0

Browse files
author
Jesus Terrazas
committed
Merge branch 'users/jterrazas/update-google-adk' of https://github.com/microsoft/Agent365-Samples into users/jterrazas/update-google-adk
2 parents 719fc85 + 95366b5 commit c1e6ca0

2 files changed

Lines changed: 338 additions & 0 deletions

File tree

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Configuration;
6+
using System.Diagnostics;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Agent365SemanticKernelSampleAgent.Agents;
10+
using AgentNotification;
11+
using Microsoft.Agents.A365.Notifications.Models;
12+
using Microsoft.Agents.A365.Observability.Caching;
13+
using Microsoft.Agents.A365.Observability.Runtime.Common;
14+
using Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Services;
15+
using Microsoft.Agents.Builder;
16+
using Microsoft.Agents.Builder.App;
17+
using Microsoft.Agents.Builder.State;
18+
using Microsoft.Agents.Core.Models;
19+
using Microsoft.Extensions.Configuration;
20+
using Microsoft.Extensions.DependencyInjection;
21+
using Microsoft.Extensions.DependencyInjection.Extensions;
22+
using Microsoft.Extensions.Logging;
23+
using Microsoft.SemanticKernel;
24+
using Microsoft.SemanticKernel.Agents;
25+
using Microsoft.SemanticKernel.ChatCompletion;
26+
27+
namespace Agent365SemanticKernelSampleAgent;
28+
29+
public class MyAgent : AgentApplication
30+
{
31+
private const string primaryAuthHandler = "agentic";
32+
private readonly IConfiguration _configuration;
33+
private readonly Kernel _kernel;
34+
private readonly IMcpToolRegistrationService _toolsService;
35+
private readonly IExporterTokenCache<AgenticTokenStruct> _agentTokenCache;
36+
private readonly ILogger<MyAgent> _logger;
37+
38+
public MyAgent(AgentApplicationOptions options, IConfiguration configuration, Kernel kernel, IMcpToolRegistrationService toolService, IExporterTokenCache<AgenticTokenStruct> agentTokenCache, ILogger<MyAgent> logger) : base(options)
39+
{
40+
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
41+
_kernel = kernel ?? throw new ArgumentNullException(nameof(kernel));
42+
_toolsService = toolService ?? throw new ArgumentNullException(nameof(toolService));
43+
_agentTokenCache = agentTokenCache ?? throw new ArgumentNullException(nameof(agentTokenCache));
44+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
45+
46+
var autoSignInHandlers = new[] { primaryAuthHandler };
47+
48+
// Register Agentic specific Activity routes. These will only be used if the incoming Activity is Agentic.
49+
this.OnAgentNotification("*", AgentNotificationActivityAsync,RouteRank.Last, autoSignInHandlers: autoSignInHandlers);
50+
51+
OnActivity(ActivityTypes.InstallationUpdate, OnHireMessageAsync);
52+
OnActivity(ActivityTypes.Message, MessageActivityAsync, rank: RouteRank.Last, autoSignInHandlers: autoSignInHandlers);
53+
}
54+
55+
internal static bool IsApplicationInstalled { get; set; } = false;
56+
internal static bool TermsAndConditionsAccepted { get; set; } = false;
57+
58+
protected async Task MessageActivityAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
59+
{
60+
// Resolve the tenant and agent id being used to communicate with A365 services.
61+
(string agentId, string tenantId) = await ResolveTenantAndAgentId(turnContext).ConfigureAwait(false);
62+
63+
using var baggageScope = new BaggageBuilder()
64+
.TenantId(tenantId)
65+
.AgentId(agentId)
66+
.Build();
67+
try
68+
{
69+
_agentTokenCache.RegisterObservability(agentId, tenantId, new AgenticTokenStruct
70+
{
71+
UserAuthorization = UserAuthorization,
72+
TurnContext = turnContext,
73+
AuthHandlerName = primaryAuthHandler
74+
}, EnvironmentUtils.GetObservabilityAuthenticationScope());
75+
}
76+
catch (Exception ex)
77+
{
78+
_logger.LogWarning($"There was an error registering for observability: {ex.Message}");
79+
}
80+
81+
// Setup local service connection
82+
ServiceCollection serviceCollection = [
83+
new ServiceDescriptor(typeof(ITurnState), turnState),
84+
new ServiceDescriptor(typeof(ITurnContext), turnContext),
85+
new ServiceDescriptor(typeof(Kernel), _kernel),
86+
];
87+
88+
if (!IsApplicationInstalled)
89+
{
90+
await turnContext.SendActivityAsync(MessageFactory.Text("Please install the application before sending messages."), cancellationToken);
91+
return;
92+
}
93+
94+
var agent365Agent = await this.GetAgent365AgentAsync(serviceCollection, turnContext, primaryAuthHandler);
95+
if (!TermsAndConditionsAccepted)
96+
{
97+
if (turnContext.Activity.ChannelId.Channel == Channels.Msteams)
98+
{
99+
var response = await agent365Agent.InvokeAgentAsync(turnContext.Activity.Text, new ChatHistory());
100+
await OutputResponseAsync(turnContext, turnState, response, cancellationToken);
101+
return;
102+
}
103+
}
104+
if (turnContext.Activity.ChannelId.Channel == Channels.Msteams)
105+
{
106+
await TeamsMessageActivityAsync(agent365Agent, turnContext, turnState, cancellationToken);
107+
}
108+
else
109+
{
110+
await turnContext.SendActivityAsync(MessageFactory.Text($"Sorry, I do not know how to respond to messages from channel '{turnContext.Activity.ChannelId}'."), cancellationToken);
111+
}
112+
}
113+
114+
private async Task AgentNotificationActivityAsync(ITurnContext turnContext, ITurnState turnState, AgentNotificationActivity activity, CancellationToken cancellationToken)
115+
{
116+
// Resolve the tenant and agent id being used to communicate with A365 services.
117+
(string agentId, string tenantId) = await ResolveTenantAndAgentId(turnContext).ConfigureAwait(false);
118+
119+
using var baggageScope = new BaggageBuilder()
120+
.TenantId(tenantId)
121+
.AgentId(agentId)
122+
.Build();
123+
124+
try
125+
{
126+
_agentTokenCache.RegisterObservability(agentId, tenantId, new AgenticTokenStruct
127+
{
128+
UserAuthorization = UserAuthorization,
129+
TurnContext = turnContext,
130+
AuthHandlerName = primaryAuthHandler
131+
}, EnvironmentUtils.GetObservabilityAuthenticationScope());
132+
}
133+
catch (Exception ex)
134+
{
135+
_logger.LogWarning($"There was an error registering for observability: {ex.Message}");
136+
}
137+
138+
// Setup local service connection
139+
ServiceCollection serviceCollection = [
140+
new ServiceDescriptor(typeof(ITurnState), turnState),
141+
new ServiceDescriptor(typeof(ITurnContext), turnContext),
142+
new ServiceDescriptor(typeof(Kernel), _kernel),
143+
];
144+
145+
if (!IsApplicationInstalled)
146+
{
147+
await turnContext.SendActivityAsync(MessageFactory.Text("Please install the application before sending notifications."), cancellationToken);
148+
return;
149+
}
150+
151+
var agent365Agent = await this.GetAgent365AgentAsync(serviceCollection, turnContext, primaryAuthHandler);
152+
if (!TermsAndConditionsAccepted)
153+
{
154+
var response = await agent365Agent.InvokeAgentAsync(turnContext.Activity.Text, new ChatHistory());
155+
await OutputResponseAsync(turnContext, turnState, response, cancellationToken);
156+
return;
157+
}
158+
159+
switch (activity.NotificationType)
160+
{
161+
case NotificationTypeEnum.EmailNotification:
162+
await turnContext.StreamingResponse.QueueInformativeUpdateAsync($"Thanks for the email notification! Working on a response...");
163+
if (activity.EmailNotification == null)
164+
{
165+
turnContext.StreamingResponse.QueueTextChunk("I could not find the email notification details.");
166+
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken);
167+
return;
168+
}
169+
170+
var chatHistory = new ChatHistory();
171+
var emailContent = await agent365Agent.InvokeAgentAsync($"You have a new email from {activity.From.Name} with id '{activity.EmailNotification.Id}', ConversationId '{activity.EmailNotification.ConversationId}'. Please retrieve this message and return it in text format.", chatHistory);
172+
var response = await agent365Agent.InvokeAgentAsync($"You have received the following email. Please follow any instructions in it. {emailContent.Content}", chatHistory);
173+
var responseEmailActivity = MessageFactory.Text("");
174+
responseEmailActivity.Entities.Add(new EmailResponse(response.Content));
175+
await turnContext.SendActivityAsync(responseEmailActivity, cancellationToken);
176+
//await OutputResponseAsync(turnContext, turnState, response, cancellationToken);
177+
return;
178+
case NotificationTypeEnum.WpxComment:
179+
await turnContext.StreamingResponse.QueueInformativeUpdateAsync($"Thanks for the Word notification! Working on a response...", cancellationToken);
180+
if (activity.WpxCommentNotification == null)
181+
{
182+
turnContext.StreamingResponse.QueueTextChunk("I could not find the Word notification details.");
183+
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken);
184+
return;
185+
}
186+
var driveId = "default";
187+
chatHistory = new ChatHistory();
188+
var wordContent = await agent365Agent.InvokeAgentAsync($"You have a new comment on the Word document with id '{activity.WpxCommentNotification.DocumentId}', comment id '{activity.WpxCommentNotification.ParentCommentId}', drive id '{driveId}'. Please retrieve the Word document as well as the comments in the Word document and return it in text format.", chatHistory);
189+
190+
var commentToAgent = activity.Text;
191+
response = await agent365Agent.InvokeAgentAsync($"You have received the following Word document content and comments. Please follow refer to these when responding to comment '{commentToAgent}'. {wordContent.Content}", chatHistory);
192+
var responseWpxActivity = MessageFactory.Text(response.Content!);
193+
await turnContext.SendActivityAsync(responseWpxActivity, cancellationToken);
194+
//await OutputResponseAsync(turnContext, turnState, response, cancellationToken);
195+
return;
196+
}
197+
198+
throw new NotImplementedException();
199+
}
200+
201+
protected async Task TeamsMessageActivityAsync(Agent365Agent agent365Agent, ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
202+
{
203+
// Start a Streaming Process
204+
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Working on a response for you", cancellationToken);
205+
206+
ChatHistory chatHistory = turnState.GetValue("conversation.chatHistory", () => new ChatHistory());
207+
208+
// Invoke the Agent365Agent to process the message
209+
Agent365AgentResponse response = await agent365Agent.InvokeAgentAsync(turnContext.Activity.Text, chatHistory);
210+
await OutputResponseAsync(turnContext, turnState, response, cancellationToken);
211+
}
212+
213+
protected async Task OutputResponseAsync(ITurnContext turnContext, ITurnState turnState, Agent365AgentResponse response, CancellationToken cancellationToken)
214+
{
215+
if (response == null)
216+
{
217+
turnContext.StreamingResponse.QueueTextChunk("Sorry, I couldn't get an answer at the moment.");
218+
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken);
219+
return;
220+
}
221+
222+
// Create a response message based on the response content type from the Agent365Agent
223+
// Send the response message back to the user.
224+
switch (response.ContentType)
225+
{
226+
case Agent365AgentResponseContentType.Text:
227+
turnContext.StreamingResponse.QueueTextChunk(response.Content!);
228+
break;
229+
default:
230+
break;
231+
}
232+
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken); // End the streaming response
233+
}
234+
235+
protected async Task OnHireMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
236+
{
237+
// Resolve the tenant and agent id being used to communicate with A365 services.
238+
(string agentId, string tenantId) = await ResolveTenantAndAgentId(turnContext).ConfigureAwait(false);
239+
240+
using var baggageScope = new BaggageBuilder()
241+
.TenantId(tenantId)
242+
.AgentId(agentId)
243+
.Build();
244+
245+
try
246+
{
247+
_agentTokenCache.RegisterObservability(agentId, tenantId, new AgenticTokenStruct
248+
{
249+
UserAuthorization = UserAuthorization,
250+
TurnContext = turnContext,
251+
AuthHandlerName = primaryAuthHandler
252+
}, EnvironmentUtils.GetObservabilityAuthenticationScope());
253+
}
254+
catch (Exception ex)
255+
{
256+
_logger.LogWarning($"There was an error registering for observability: {ex.Message}");
257+
}
258+
259+
if (turnContext.Activity.Action == InstallationUpdateActionTypes.Add)
260+
{
261+
bool useAgenticAuth = Environment.GetEnvironmentVariable("USE_AGENTIC_AUTH") == "true";
262+
263+
IsApplicationInstalled = true;
264+
TermsAndConditionsAccepted = useAgenticAuth ? true : false;
265+
266+
string message = $"Thank you for hiring me! Looking forward to assisting you in your professional journey!";
267+
if (!useAgenticAuth)
268+
{
269+
message += "Before I begin, could you please confirm that you accept the terms and conditions?";
270+
}
271+
272+
await turnContext.SendActivityAsync(MessageFactory.Text(message), cancellationToken);
273+
}
274+
else if (turnContext.Activity.Action == InstallationUpdateActionTypes.Remove)
275+
{
276+
IsApplicationInstalled = false;
277+
TermsAndConditionsAccepted = false;
278+
await turnContext.SendActivityAsync(MessageFactory.Text("Thank you for your time, I enjoyed working with you."), cancellationToken);
279+
}
280+
}
281+
282+
/// <summary>
283+
/// Resolve Tenant and Agent Id from the turn context.
284+
/// </summary>
285+
/// <param name="turnContext"></param>
286+
/// <returns></returns>
287+
private async Task<(string agentId, string tenantId)> ResolveTenantAndAgentId(ITurnContext turnContext)
288+
{
289+
string agentId = "";
290+
if (turnContext.Activity.IsAgenticRequest())
291+
{
292+
agentId = turnContext.Activity.GetAgenticInstanceId();
293+
}
294+
else
295+
{
296+
agentId = Microsoft.Agents.A365.Runtime.Utils.Utility.GetAppIdFromToken(await UserAuthorization.GetTurnTokenAsync(turnContext, primaryAuthHandler));
297+
}
298+
string tenantId = turnContext.Activity.Conversation.TenantId ?? turnContext.Activity.Recipient.TenantId;
299+
return (agentId, tenantId);
300+
}
301+
302+
private async Task<Agent365Agent> GetAgent365AgentAsync(ServiceCollection serviceCollection, ITurnContext turnContext, string authHandlerName)
303+
{
304+
return await Agent365Agent.CreateA365AgentWrapper(
305+
_kernel,
306+
serviceCollection.BuildServiceProvider(),
307+
_toolsService,
308+
authHandlerName,
309+
UserAuthorization,
310+
turnContext,
311+
_configuration).ConfigureAwait(false);
312+
}
313+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.14.36414.22
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SemanticKernelSampleAgent", "SemanticKernelSampleAgent.csproj", "{6BE5ABEC-BEAF-9CF4-A98A-6C73FD961C90}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{6BE5ABEC-BEAF-9CF4-A98A-6C73FD961C90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{6BE5ABEC-BEAF-9CF4-A98A-6C73FD961C90}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{6BE5ABEC-BEAF-9CF4-A98A-6C73FD961C90}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{6BE5ABEC-BEAF-9CF4-A98A-6C73FD961C90}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {4BB18351-B926-45B2-918B-D5E337BA126F}
24+
EndGlobalSection
25+
EndGlobal

0 commit comments

Comments
 (0)