Sunday, 10 March 2024

Create a Microsoft 365 Copilot plugin: Extend Microsoft 365 Copilot's knowledge

Microsoft 365 Copilot is an enterprise AI tool that is already trained on your Microsoft 365 data. If you want to "talk" to data such as your emails, Teams chats or SharePoint documents, then all of it is already available as part of it's "knowledge".

However, not all the data you want to work with will live in Microsoft 365. There will be instances when you want to use Copilot's AI on data residing in external systems. So how do we extend the knowledge of Microsoft 365 Copilot with real time data coming from external systems? The answer is by using plugins! Plugins not only help us do Retrieval Augmented Generation (RAG) with Copilot, but they also provide a framework for writing data to external systems. 

To know more about the different Microsoft 365 Copilot extensibility options, please have a look here: https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/decision-guide

So in this post, let's have a look at how to build a plugin which talks to an external API and then infuses the real time knowledge into Copilot's AI. At the time of this writing, there is nothing more volatile than Cryptocurrency prices! So, I will be using a cryptocurrency price API and enhance Microsoft 365 Copilot's knowledge with real time Bitcoin and Ethereum rates!

(click to zoom)

So let's see the different moving parts of the plugin. We will be using a Microsoft Teams message extension built on the Bot Framework as a base for our plugin:  

1) App manifest

This is by far the most important part of the plugin. The name and description (both short and long) are what tell Copilot about the nature of the plugin and when to invoke it to get external data. We have to be very descriptive and clear about the features of the plugin here as this is what the Copilot will use to determine whether the plugin is invoked. The parameter descriptions are used to tell Copilot how to create the parameters required by the plugin based on the conversation.

{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"version": "1.0.0",
"id": "${{TEAMS_APP_ID}}",
"packageName": "com.microsoft.teams.extension",
"name": {
"short": "Cryptocurrency",
"full": "Cryptocurrency prices"
},
"description": {
"short": "Get the latest cryptocurrency prices",
"full": "Get the latest cryptocurrency prices and share them in a conversation. Search cryptocurrencies by name."
},
"composeExtensions": [
{
"botId": "${{BOT_ID}}",
"commands": [
{
"id": "getPrice",
"description": "Get cryptocurrency price by name",
"title": "Get Cryptocurrentcy Price",
"type": "query",
"initialRun": false,
"fetchTask": false,
"context": [
"commandBox",
"compose",
"message"
],
"parameters": [
{
"name": "cryptocurrencyName",
"title": "Cryptocurrency Name",
"description": "The name of the cryptocurrency. Value should either be bitcoin or ethereum. Output: bitcoin or ethereum",
"inputType": "text"
}
]
}
]
}
]
//Other properties removed for brevity
}
view raw teamsapp.json hosted with ❤ by GitHub

2) Teams messaging extension code

This function does the heavy lifting in our code. It is called with the parameters specified in the app manifest by Copilot. Based on the parameters we can fetch external data and return it as adaptive cards. 

protected override async Task<MessagingExtensionResponse> OnTeamsMessagingExtensionQueryAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery query, CancellationToken cancellationToken)
{
var templateJson = await System.IO.File.ReadAllTextAsync(_adaptiveCardFilePath, cancellationToken);
var template = new AdaptiveCards.Templating.AdaptiveCardTemplate(templateJson);
var text = query?.Parameters?[0]?.Value as string ?? string.Empty;
var rate = await FindRate(text);
var attachments = new List<MessagingExtensionAttachment>();
string resultCard = template.Expand(rate);
var previewCard = new HeroCard
{
Title = rate.id,
Subtitle = rate.rateUsd
}.ToAttachment();
var attachment = new MessagingExtensionAttachment
{
Content = JsonConvert.DeserializeObject(resultCard),
ContentType = "application/vnd.microsoft.card.adaptive",
Preview = previewCard,
};
attachments.Add(attachment);
return new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "result",
AttachmentLayout = "list",
Attachments = attachments
}
};
}

3) Talk to the external system (Cryptocurrency API)

This is helper function which is used to actually talk to the crypto api and return rates. 

private async Task<Rate> FindRate(string currency)
{
var httpClient = new HttpClient();
var response = await httpClient.GetStringAsync($"https://api.coincap.io/v2/rates/{currency}");
var obj = JObject.Parse(response);
var rateData = JsonConvert.DeserializeObject<Rate>(obj["data"].ToString());
rateData.rateUsd = decimal.Parse(rateData.rateUsd).ToString("C", CultureInfo.GetCultureInfo(1033));
return rateData;
}
view raw FindRate.cs hosted with ❤ by GitHub

Hope you found this post useful! 

The code for this solution is available on GitHub: https://github.com/vman/M365CopilotPlugin