In this video, I explore the differences between two powerful features: Plugin Actions and Dynamic Chaining Topics. I explain why you may use one over the other. 🚀 Also, learn how to leverage Adaptive Cards within Copilot Studio. I demonstrate how to choose records and execute plugin actions on selected items. I also delve into three strategies for handling translations for multi-language copilots.
#powerapps #powerplatform #copilotstudio #tutorial #ai #multilingual
📺Watch part 1: https://youtu.be/OQsvSmXI2As?si=mqS0DFdCDENBcjn4
🎫Win a 2-Day Ticket to the European Power Platform Conference! 🎉
I have the privilege to give away a 2-day ticket to the European Power Platform Conference in Brussels. Details on how to participate are in the video – don’t miss out! https://bit.ly/win-eppc-ticket-with-scott
🌐 European Power Platform Conference 2024: Join us in Brussels from June 11-13, 2024, at the Square. Connect with fellow enthusiasts, learn from global experts, and immerse yourself in the Power Platform community. 🚀
-----------------------------
00:00 Introduction
02:05 Plugin Action Recap
02:50 Validating Inputs using Topics
05:13 Adaptive card to show results and to perform an action
07:19 Multi-language copilots with validation of inputs
08:58 Translating list and dynamic text
11:31 Translating adaptive cards and static text
13:12 News and competition time!
-----------------------------
Code snips:
Translations:
{
OrderStatus:[
{
key: "Pending",
Name: "Pending",
Language:"English"
},
// ...
// Norwegian Translations
{
key: "Pending",
Name: "Avventer",
Language:"Norwegian"
},
// ...
],
Date:[
{
Name:"Date",
Language:"English"
},
{
Name:"Daddel",
Language:"Norwegian"
}
]
// ...
}
Adaptive card:
{
'$schema': "http://adaptivecards.io/schemas/adaptive-card.json",
type: "AdaptiveCard",
version: "1.5",
body: [
{
type: "Container",
items: [
{
type: "TextBlock",
text: "Order List",
weight: "Bolder",
size: "Large"
}
]
},
{
type: "Container",
items: ForAll(Topic.OrderListByStatus,
{
type: "Container",
items: [
{
type: "FactSet",
facts: [
{
title: "ID",
value: Text(ThisRecord.id)
},
{
title: LookUp(Global.Translations.Date,Language=Text(System.User.Language)).Name,
value: Text(ThisRecord.orderDate,"dd MMM yyyy")
},
{
title: LookUp(Global.Translations.Amount,Language=Text(System.User.Language)).Name,
value: Text(ThisRecord.orderTotal,"$#,####")
},
{
title: LookUp(Global.Translations.Status,Language=Text(System.User.Language)).Name,
value: LookUp(Global.Translations.OrderStatus,Language=Text(System.User.Language) && key = status).Name
}
]
},
{
type: "ActionSet",
actions: [
{
type: "Action.Submit",
title: $"{LookUp(Global.Translations.MarkAs,Language=Text(System.User.Language)).Name} {LookUp(Global.Translations.OrderStatus,Language=Text(System.User.Language) && key = "Shipped").Name}",
data: {
orderId: Text(ThisRecord.id),
action: "Shipped"
}
},
{
type: "Action.Submit",
title: $"{LookUp(Global.Translations.MarkAs,Language=Text(System.User.Language)).Name} {LookUp(Global.Translations.OrderStatus,Language=Text(System.User.Language) && key = "Delivered").Name}",
data: {
orderId: Text(ThisRecord.id),
action: "Delivered"
}
}
]
}
]
}
)
}
]
}
Hi, Hi friends. Well, in part one of this video, I
showed you how to use plugin actions in Copilot Studio to call an API
and then use the magic of AI to pass the parameters and then show the
response in a human readable form. If you've not seen that video,
go and check it out here. And it does feel like magic, right? But we do need to use AI
responsibly and appropriately. So if you want an example of this here
inside Copilot Studio, I can create a prompt plugin, ask a very simple question. How m
any letter Ns are
there in the word enter? I'll actually create a little
parameter here to put the word in and then test it out. Oddly enough, it gives
us the wrong answer, two. And for another example, we ask how
long does it take to dry four towels if it takes two hours to dry two towels? And what happens is it thinks
it's a proportional maths problem and it says four hours. When it's very obvious to us it's
only going to take two hours. Well, unless you take air
humidity and surface area into
account, maybe it's got a point. The plug in actions allow us to interact
with external data and use the AI to interpret what we're asking it to
do and then interpret the response. And it's great for doing that because it
doesn't really require any significant understanding of the context. So in this video, I'm going
to compare plugin actions with another feature of Copilot Studio,
which is dynamic chaining topics. And I'm also going to show how to
use adaptive cards to choose records. And then
some of the nuances of
creating multi language copilots. The key to understanding the difference
between plugin actions and topics is that topics have more control. You get to wrap up your logic
in a more orchestrated way using Power Fx and branching logic. Whilst plugin actions represent a single
call to a custom connector API all wrapped up in that AI powered parsing
of parameters and showing the response. You do get a little bit of control
in plugin actions by being able to specify the descr
iption which acts
essentially as a system prompt and you can also supply a custom message as
a response or even an adaptive card but it's still a single operation. Topics can be a lot more orchestrated
and have many many different steps. So how do you get those topics to
act in the same way as those plugin actions and automatically extract the
parameters rather than the old fashioned bot approach of having this step based
question and answer conversation? And stick around to the end
of the video
where you can win a two day ticket to EPPC. So here's this little app that we
created last time where it lists the orders and this is all using a custom
connector and it's calling Visual Studio. So if I go over here and
I put a little breakpoint. Get Orders. And then go back to my app here,
I can ask a question, list all orders that have the status shipped. So you can see here it's calling my
custom connector, which is calling my Web API in Visual Studio, via a Dev Tunnel. And check out my last
video
to see how I did that! So what it's doing here is it's
formatting this in a Human Readable Form even though it's coming back
from that Web API in a JSON object. If we go down to our Plugin Action here,
we've got Inputs and we've got outputs and we're giving a little bit of information
to the AI, how to interpret the input. So it's great because very little effort. You can then call your own custom
connector and actually interact with it. What happens if I want to validate
perhaps the inpu
t, more control over it to check that the status is one
of the statuses that is available. Now entities gives us the ability
to create lists of things. So you can see here, I've got a
list of all the different statuses and I've called it order status. It's like a choice. Now if we go into plugins and topics
and I deactivate my get orders by status plugin, I go and create another topic. Create it from blank here. Give it a description. Allows the user to list orders
and it also gives an example.
And next we can add in a question here. And this is where we can
now select order status. And then we can select which
ones we want to display. And I'm going to set. Store it in a order status choice. So saving that, we can then test that out. And so we can see here that it is asking
us for these various different options. So I can select processing
and then it set that variable. But what we want to do is make it
the same as the plugin action, where processing is automatically or In
this case, s
hipped is automatically picked up from that initial message. So the way we can do that is if we go
into settings and if we come down to generative AI, we need to make sure
that the dynamic chaining is turned on. And then if we go back into
our topic, select the variable, and there's this option here to
receive values from other topics. That's very important that we check that. So if we save that, now if we do the
same thing again, now what we, what we're seeing here is this error message. And th
at's because closed list option
sets, entity lists in this case, are not supported at the moment for
that dynamic chaining extraction. So what we can do is we just
need to change this identify to be user's entire response. So it's only gonna, it's
gonna pick up a string. And of course we now need to change
that variable to be a, a string type. And we'll select that receive
values from other topics again. And then let's output the value
from that we've actually got. So send a message here. Let's
try again. And there we go. We've actually now extracted
shipped from that initial message. So it's acting the same
as our plugin action. So now we've actually got that. We can go and make a call to our
connector, list orders by status, and we can specify the variable to use
as the status that we want to list. And then it's going to
give us this output. So of course we needed
to show this output. Now before with our plugin action,
That was just automatically working for us because it was using t
he
AI to just create an answer. But in this case, what we're
going to do is we're going to have to use an adaptive card. We can use the adaptive card to
perform actions on the results. Well, I can come into the adaptive
card designer, which is pretty good. You can go and drag and drop items. And since we're talking about the magic
of AI, let's use Copilot inside VS Code to generate our adaptive cards. I've got a little prompt here to
ask it to create an adaptive card. So you can see here it's do
ne
it and it's also creating an action set to mark as shipped or
delivered for each of these items. So I'm gonna go and copy that. I'm gonna paste this into here,
and then we can change this into a formula which allows us to use
Power Fx, which is really cool. Inside our adaptive card, the first
thing we need to do is need to make each of the items in here its own little
container that can be re repeated. Now we can wrap that up in a for all. so that it gets repeated for each
of the individual o
rder statuses. And so we got here,
we've now got the action. And we've got the Order ID that's
coming out of that Adaptive Card. And so each of these
correspond to the Action Items. Order ID and Action. So the next thing is we can
now, the great thing is we can now call our Plugin Action. Which is Update Order. And then we can set a value which is the
ID of the order which has come from that submit button, and we have another one,
which again comes from the Submit button. So let's test that. Oh,
we've got one error, and that's
because it's getting a string when it's actually expecting a number. Let's put, change that to be a expression. We can just simply say value. But we also need to, of course,
make the adaptive card dynamic. So let's go down here. At the moment this is all hard
coded, so let's try this out then. And actually the order ID here
needs to go to be the same thing. The order ID, like that. Let's save that and let's give it a go. So, listing orders that
are status shipped
, so we should see all of the orders. in that status, looking promising. There we go, we've got two
orders and status shipped. Now if we mark as delivered, now
that should call our plug in action. And you can see here we've got
an order, and it's got delivered. So if we carry on, and we get
order updated, because we've just called that plug in action,
which we've already implemented. So what about creating a
multi language copilot? If I come over into languages here, I
can very easily add anothe
r language. I'm going to add in Norwegian. And that means our plugin
actions now automatically speak Norwegian, which is pretty cool. So I'm going to look at this plugin
action, which is get orders by ID. And one of the inputs
is just simply order ID. So let's switch over to Norwegian. Details for 10001. And you can see it's translated
the response and it's translated shipped into sent. Let's try a different order. The problem now is it hasn't
translated processing, so you can see it's a little
bit unpredictable. But what we can do is we can add
in a little prompt here to say always translate the status into
the current user's language. But I do need to switch that over
to English to do that, to update, because you always need to update
your copilot in the primary language. So I'll save that and we'll
go back into Norwegian. And now let's see what it comes up with. So we're asking for the same details. And you can see now that prompt that
we've said translate into the user's language i
s actually meant that it
responds with the correct language. So that's great. I mean, you can see that is kind of magic. Well, what about updating with statuses
or retrieving with statuses where I actually need to provide that status? So you can see here, I'm actually
asking it to retrieve all the orders in the status shipped. So what you can see here is
that the copilot's translated what the user provided, but it's
translated into the wrong value. Translated it to sent, not shipped. So we need
a mechanism to make sure
that the copilot is sending the right values to our custom connector. Now there is a very easy
way of downloading a file of translation strings that we can
then re upload for static text. But in this case we have dynamic
text the user is entering. So if we go into the Conversation Start,
we can create a variable that can be used throughout our custom copilot, which is
going to hold all of our translations. So we'll set a variable called
translations, and we're going to m
ake this variable, a global variable, so that
it can be used in any different topics. And we're going to set it
to an expression value. And this expression contains
translations for order status. So there's the English versions. And then we've got the Norwegian versions. And of course they could also include
different synonyms, but we've also got translations for date, amount,
status, other things that we're going to use in our adaptive card. And this is different to downloading
the resource fil
e, the JSON or ResX, because this is going to be dynamic. We'll save that. And then if we go into our
GetOrderByStatus, now we can add in a validation step. The users should have provided
an order status, but we can now check that that matches one of
the values in our translations. So we'll select the value to say it's
going to set the order status string that's already been identified, but
we're going to use a expression to look up against our translation variable,
where the OrderStatusString e
quals the name in that OrderStatus collection. And then we're going to pick up the key
and the key is the system value, not the name that the user has provided. So we'll insert that. What happens if they haven't
entered a value that's recognized? Well, here, what we need
to do is provide a branch. So we'll add a condition to check
if the value that's being looked up, it's been found or not. Check the OrderStatusString if it. Now what we need to do is ask a question
to identify from a list of val
ues. Options from a list variable, and the
values that we're going to provide are going to come from that translation. So let's put in a formula. So we're filtering, only giving us
the order status names where the language equals the user's language. So we'll insert that. Now I've just typed in English there,
but what I'm going to show in a minute is how you then translate that into the
user's language using the resource file. We'll change this variable
name to be status choice. That's going to
give us the value,
which is the translated value, but we also need to, if we copy this. translate that back
into the system version. So we'll add in another step
here and change this expression to use the order status choice. So we'll save that and
let's test our copilot. So list all orders in
the status back order. So this now should give us a list
of order statuses to select from because back order doesn't exist. And there we go. We've got the list of order statuses. So I select one and there
we go. It gives us the list. Now let's try this in Norwegian. List all orders in the status
shipped, which is sent. So it's correctly listed them, but you
can see here that there are still bits of text here, which are in English. The first one to tackle is
the adaptive card, and then we'll go and tackle the text. If we go back into English and open
up this adaptive card here, at the moment these are all hard coded, but
what we can do is replace all the bits of text with a lookup to the
translati
on object that we created. So you can see here we're looking up date,
and then the amount, and the status, and we're also looking up the actual status. Value in the local language. And we're doing the same for the actions,
providing the button label with a mark as and for ship to mark as for delivered. And the last piece of the
jigsaw is we go into languages. We can now download a file which
contains a row for all the pieces of text inside our copilot. And interestingly, we do have a row
for all
the different values in our closed list items or order items. We could translate that
if we were using it. But in this case, we want to translate
the values for the get order status. And I'm gonna use copilot to do that. We'll accept that. And we're also going to translate
the welcome message as well. And this is amazing, this is
using GitHub Copilot, which really speeds up your day to day work. And we'll accept and then save. And back inside Copilot we can go
and select that file that we've ju
st saved and updated with the Norwegian
translations and upload those. Let's switch back to
Norwegian and give it a go. So you can immediately see that the
welcome message is in Norwegian. We've got a message there that we're
listing them in the status shipped. And we've got all the
different translations for the different labels here. The last piece we do need to provide
a lookup to shipped to show it in Norwegian, but I can show you,
you can work out how to do that. And the other thing is, is
that
you should probably want to do some lookups to make sure it's not case
sensitive as well, but you can get the idea of how this all works. So we've seen. Three ways to create your
custom multi language copilots. And of course that bonus content
of creating a multi language adaptive card that allows you
to select from multiple records. So a little bit of news then. There is the third European Power
Platform Conference, EPPC, that's happening in Brussels on June the
11th to the 13th in my poss
ession I have a two day conference ticket to
give away free to a lucky winner. It's a fantastic event. There are a hundred plus sessions on all
range of Power Platform topics, including Copilot Studio and some amazing speakers. Check out the link below
for more information. So are you ready for the question to
win a two day ticket to this event? What did I ask you to forget about
for a moment in my most recent blog post on the Microsoft Power Apps blog? Is it A. No Code, B. Low Code, C. Pro Code
, or D. All of the above? If you know the answer and you fancy
a chance at winning a two day ticket to EPVC, you can click on the link
below and submit your answer and a win will be picked at random. And I'll also be at the MVP Summit in
Seattle and then the Canadian Power Platform Summit in Vancouver the next
few weeks, so look out for social media posts from me on news from those events. And that's all for today, so until
next time, cheers, thanks for watching.
Comments
Finally we get some pro videos on this topic 😊
Thanks for sharing Scott, I watch your videos as a source of inspiration to create cool stuff like the drag and drop PCF in canvas app 👏
And still waiting for pcf course to drop!😅😅😇
Thanks for sharing this information ! Is very usefull!!
Thank you and I love it!!!
Thank you for the information. It helped me a lot with Copilot. I also want to learn how to customize Dynamics Copilot with custom entities(Context based).