Skip to main content

Notifications

Dynamics 365 Community / Blogs / Learn with Subs / Automate preparing minutes ...

Automate preparing minutes of Teams meetiing using Azure AI Language services

Subhad365 Profile Picture Subhad365 7 User Group Leader
 
Wassup guys?!!!
Here comes yet another interesting way to leverage your Azure AI Language services, to get the gist of a Teams meeting, once it ends and send it to all the meeting invitees.
Yeah, I understand, you might wonder, Azure AI recap is already there -- why then reinventing the wheel? The reason is simple: you can customize your messsages, you can play with the depth of sumarization needed, and a lot of other tricks, which, I think, could certainly of immense help -- maybe this much of depth won't be covered by the OOB process.
So, here goes the trick: step-by-step:
Step 1
You need to create a Azure Language services, first of all. You can go to https://portal.azure.com and launch a new reuqest to create an Azure Language Service:

Make sure you select the above option, which consequently would enable you to extract summary and text classification.
Select all what you need: I had to choose the highlighted Pricing tier -- as the only option left, from the given Region. But feel free to choose the appropriate pricing tier, as per your need.


Choose proper storage account as per your need/create one if required:


Click Next >> Review + Create >> Create to submit the request for creation.
I no time, the resource would be created for you, for your usage:



Click on 'Manage Keys' to view primary and secondary keys. You need to copy the primary key along with endpoint somewhere, for our later reference:


Step 2: 
Hover to Visual Studio/VS Code >> Create an HTTP request based function app, with references to the following Nuggest packages:
using Newtonsoft.Json;
using Azure.AI.TextAnalytics;
using System.Threading.Tasks;
using System.Collections.Generic;
Once done you can define your copied Key details and endpoints as variables in the json.settings file:

You can write an async function like this, that taps down any speech body and gets the summary:
public static async Task<string> getSummary(string document)
{
    string languageKey = Environment.GetEnvironmentVariable("languageKey");
    string languageEndpoint = Environment.GetEnvironmentVariable("languageApi");


    AzureKeyCredential credentials = new AzureKeyCredential(languageKey);
    Uri endpoint = new Uri(languageEndpoint);
    var client = new TextAnalyticsClient(endpoint, credentials);


    var batchInput = new List<string>
    {
        document
    };


    TextAnalyticsActions actions = new TextAnalyticsActions()
    {
        ExtractiveSummarizeActions = new List<ExtractiveSummarizeAction>() { new ExtractiveSummarizeAction() }
    };


    // Start analysis process.
    AnalyzeActionsOperation operation = await client.StartAnalyzeActionsAsync(batchInput, actions);
    await operation.WaitForCompletionAsync();
    StringBuilder sb = new StringBuilder();
    sb.Append($"AnalyzeActions operation has completed");
    
    sb.Append($"Created On   : {operation.CreatedOn}");
    sb.Append($"Expires On   : {operation.ExpiresOn}");
    sb.Append($"Id           : {operation.Id}");
    sb.Append($"Status       : {operation.Status}");


   
    // View operation results.
    await foreach (AnalyzeActionsResult documentsInPage in operation.Value)
    {
        IReadOnlyCollection<ExtractiveSummarizeActionResult> summaryResults = documentsInPage.ExtractiveSummarizeResults;


        foreach (ExtractiveSummarizeActionResult summaryActionResults in summaryResults)
        {
            if (summaryActionResults.HasError)
            {
                sb.Append($"  Error!");
                sb.Append($"  Action error code: {summaryActionResults.Error.ErrorCode}.");
                sb.Append($"  Message: {summaryActionResults.Error.Message}");
                continue;
            }


            foreach (ExtractiveSumm
arizeResult documentResults in summaryActionResults.DocumentsResults)
            {
                if (documentResults.HasError)
                {
                    sb.Append($"  Error!");
                    sb.Append($"  Document error code: {documentResults.Error.ErrorCode}.");
                    sb.Append($"  Message: {documentResults.Error.Message}");
                    continue;
                }


                sb.Append($"  Extracted the following {documentResults.Sentences.Count} sentence(s):");
   


                foreach (ExtractiveSummarySentence sentence in documentResults.Sentences)
                {
                    sb.Append($"  Sentence: {sentence.Text}");
                    sb.Append("\n");
                }
            }
        }
    }
    return sb.ToString();
}
This function essentially gets out the summary of any input document text, by parsing it into paragraphs and loading that into a StringBuilder class, and finally calling it back.
Look at how it's calling Language key and endpoints. However I feel, these could better be stored in Azure Key Vaults, and accessed from there. Keeping in JSON settings would simply expose them as environment variables, which could be a breach of a potential security issue. 
And then finally call this method from the Azure function like this:
[FunctionName("AzFunctionGetSummary")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    lstring requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    requestBody = requestBody.Replace("\r\n", " ");
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    string speech =  data?.speech;


    string responseMessage = await getSummary(speech);


    return new OkObjectResult(responseMessage);
}
Okay, all set. Now let us talk to Teams.


Step 3
The following Logic app looks for the trigger, whenever a Team meeting ends, it will get started. Now the problem is: there is no direct way to understand if a meeting has ended. So instead, we can use this hack:




Log in to Teams, and set the keyword: ''", as is evident when a meeting ends, it ends with the key word:

Do login to teams with one admin account, that would be sending meeting invites -- not with some employee email Id (who if resigns, the entire structure would need to be dismantled).
Yeah -- the rest part is very simple:
Get the list of all the members of the meeting like this:


And then you need to access 'Recap' and recordings by using Graph API:

As to how to grant Graph API to Microsoft Teams, please follow the Microsodft Guidelines: https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/meeting-transcripts/overview-transcripts
And then you make a HTTP post request to mount the body of the meeting to call your Azure function:


And then you are calling the outlook sender mail by looping it through the list of members obtained from the earlier step:



Yeah....that's it guyz!!!
It will mount one email at the end of the meeting automatically. You can replace the mail subject, replace certain output words with something else, using azure AI language service, translate it to necessary target language -- and what not.
Cool....that's all for the day. 
Hope to see you soon with another such coll hack on Azure AI. Much love and namaste 💓💓💓 

Comments

*This post is locked for comments