Hi , This is Jim from Conveyor .
In this video , I'm going to create a web application that connects the twill s MS service to chat GP t s API , which will allow anyone with the phone number to text with chat GP t .
I'm imagining this being useful for a business where the customer doesn't want to have to go to a website or install an app .
Text messaging has the benefit of being a familiar interface and low friction .
This project uses Visual studio 2022 a s p dot net seven semantic kernel chat , GP , TT and conveyor .
This is the result of the project .
I'm texting here with the Laundromat assistant I made , but there are probably better business use cases out there .
You can even write plugins for chat GP t to enable business specific knowledge and functions .
Making it work is pretty awesome and surprisingly easy , actually , especially with our free tool conveyor to receive s MS Web calls during development .
Although I mentioned to service a lot , there isn't any special reason other than it being probably the most well known .
There are other services such as message bird D seven s MS Intel , Tech s MS s MS , Gateway and Tel .
I'm not endorsing any of them .
Please pick the one that fits you best .
I will also mention chat GP t a lot and things are changing rapidly in the chat bot market .
So I'm sorry if the code in this video becomes outdated and remember to consider other Chatbot API providers .
OK , I'm going to set up chat GP t as a laundromat assistant who can help with washing and drying questions .
I'm going to go through every step from building up the application to connecting to chat GP t and T .
So it is going to be really detailed , but please use the chapter markers to skip anything as desired .
I'll create a new Web APP project .
It needs to be dot net six or newer in order to use semantic kernel .
The idea for this project is to first have a simple chat Web page where I can test sending messages to chat GP T and then create an API controller that can receive messages from Twi OS MS service .
This is called a Web hook and works by giving the sending Web application to , in this case a URL to call when an event happens , such as a text message is received .
To start , I'll make the Web based chat that sends messages to chat GP t using the semantic kernel package .
So in develop a powershell , install the package by CD into the project folder dot net a package , and it's pre release mode at the moment .
And , of course , you can use the U I .
If you prefer .
Let's have a look at the chat GP t API in Semantic Colonel , how do you send a message ?
Well , here's the basic structure .
So to begin with , we create the kernel and then we configure it with the model that we want to use and our open API key and our organisation ID .
Next we set up chat GP t .
So , first of all , we give it its instructions on how to act .
And then here we create the service and send the set up instructions .
And then down here , we have to recreate the conversation history because chat GP t isn't state .
So you can see here The user , uh , asked for something .
And then , um , we had previously received a message back from chat GP t .
Why not try soccer ?
And then we can add the next message there , and then we can send that conversation to chat GP t and get the reply .
The main takeaways are that I need to provide credentials and have the conversation resent with every request , so it will need to be persisted somehow between calls .
This is also a good point regarding cost chat .
GP T pricing is based on the number of tokens sent and received .
The number of tokens is similar to the number of characters , but not quite .
There is a calculator on the open a I website , but I would just say that you can have hundreds of short messages for a few US cents .
However , because chat GP t isn't state and the entire conversation is resent with every message , the number of tokens increases with the length of the conversation and therefore becomes more expensive , just something to bear in mind .
Uh , there is also a usage limit page where you can set spend limits .
I would recommend that in case something goes wrong , like an infinite loop , it could get expensive .
OK , back to the application .
I'll create a basic data type , uh , for the messages being sent back and forth .
So I think we're going to need to know who the sender is .
And of course , we're gonna need the message , which I'm gonna default to now .
And we need to define message sender .
That's gonna be an ENUM .
And it can be the user or chat g BT , which is system .
There's already an index dot CS HTML .
I'll open the model for it and add properties for a list of conversation messages and the new message that's just been entered .
The list can function as a way to persist the conversation and show the user what has already been said each time the page loads .
This is our new message that the user has just entered .
And we want razor to bind to these properties in the markup side of things .
I just need to have a basic form to show the past messages and allow the entry of a new message .
So I'll get rid of the boiler plate .
We got to begin a form , and we want to display the conversation messages So that property we just added , and we're gonna need an editor for the new message and a button just to send a new message back to the server .
The application needs to know how to display for and also edit for the type conversation message because we just created it .
Although the call is to display for a the list of conversation message objects , Razor will automatically iterate the list and call display for on each conversation message .
So to do that , make a display templates and edit templates folder under shared and add a razor page to each called conversation message for the display template version .
I just want to output the fields for the model conversation message , so I'm gonna change the model to the type conversation message which I created .
Don't eat that , and what I need to do is at hidden fields for December and the message so that they are persisted and sent back to the server when the form is submitted .
But I also want to show the user the previous messages as well , so I will actually , uh , Albert those and I need to do the same for the message .
Just had a couple of plank lines there for the editor template .
I'm gonna change the model again .
We want it to be the data type that we created .
And we actually just need a text box for the message .
Because this is the new message field .
We don't actually want to , uh , allow the user to specify the sender .
That field will be added by us later .
All right , let's try it out .
That looks about right .
When the send button is clicked , the page model code is called , and it's gonna use the post HTTP method .
So in the page model , I'm going to change on , get to on post .
And I'm also gonna make this handler asynchronous because I know that , uh , chat G BT s API is asynchronous .
And I'm just gonna add the new message .
I I'm just gonna add the new message to my list of messages .
There we go and run it .
Just type in anything .
We can see that it works .
Basically , let's have a think how to send the new message to chat GP t and get the response .
I think I will create a class to handle the logic of sending the message .
This will definitely be handy when I add the API controller later .
that handles the s MS message since that will use this new class too .
So I'll create a new class , and I'm gonna call it Chatbot session and I can keep the conversation history in here too .
So I'm actually just gonna copy this and put it here , and I'm gonna initialise it as well because this copy of it isn't bound by razor .
And I'm gonna change that property back here in the page model as well .
Uh , what I want to do is actually just map it to the property in Chatbot session .
So what I need first is an instance of chatbot session .
I'll I'll do and initialise it , and then I can change this property here .
I don't need the setter anymore .
I'm just going to return what chat that session has .
There we go right close up and I can go back to my on post and we're not gonna do this here now .
Instead , let's create a method in here .
Call it forward Message from the user , a sync .
And yes , I do want that to be asynchronous .
So I'm going to That's why I'm going to await it .
Let's create this and then F 12 to get to it .
And we need to make this asynchronous as well .
And then in here , Well , I am going to want to add the message to the list .
Um , and I also do need to set the sender field .
If you remember , that's not coming from the user interface .
So by definition , it's going to be the user who sent that message , and I need to fix the capitalization here now and then .
We want to actually send the messages to chat GP t .
So let's call that in the whole history .
So now we actually need to generate this method again .
We have to change this and specify that it's a sync .
I'm just going to copy the first part from the boiler plate .
But there is actually a change already made here .
So if you remember , this argument was for the open A I key .
And this one here was for the organisation , ID .
So you can see that , uh , I'm actually gonna read it from my environment variables .
I'll talk about that in a moment , but for now , I just assume that those environment variables are set correctly .
I'm just gonna change this so it's more visible .
OK , so when you work with chat G BT , you need to sort of give it a mission statement .
I guess you could say , um we need to tell it what to do .
Otherwise it will just sort of do the usual , uh , chatbot that it does .
So you've probably seen on the Internet .
So I'm going to tell it that it's a laundromat attendant who gives laundry advice in 20 words or less .
So I don't want the messages to be too lengthy .
So I'm gonna specify 20 words or less , and I can give it more guidelines as need be .
So if I find that it , uh , gives advice , I don't want it to , or if I need it to have any , uh , specific advice I want to give .
I can specify that here .
And just remember again that this message does count , uh , to your token count .
So the longer this message is , the more it's gonna cost you , OK ?
And then the next part was to get the service , and then we're gonna create a new chat .
No typo there .
And if you remember , we need to recreate the entire history and send all that again , so we already have obviously the list of messages already sent .
So let's iterate those and we're going to switch on the type of sender because we call a different method in the API , depending on whether it's a message from the user or a message from the track .
GP t So in the case , it's the user we're gonna use add user message .
And of course , it's the message that we just received .
That's fine .
And if it's from chat G BT before that will work .
OK , that looks right .
So that just loads up the API with all of the messages , including the new one , because we already added that to the conversation .
So let's get the reply now from chat GP t you send the chat and we're gonna add the reply to It's a new conversation message .
The sender is going to be not the user , but the system and the message is , the sister replied , That was very good , and we're just gonna return it in case that becomes useful .
So this would be a strength .
So back to those two environment variables , open I a colon key and open .
I a colon or ID .
These are stored in the system environment variables rather than in a convict file in the spirit of keeping secrets out of source code so they don't accidentally slip into source control or get shared , as has happened in KUB , for example , these are the credentials you use to authenticate with Open A I to get the org ID , go to platform dot open a i dot com slash account slash org settings , and you can see it here , and I'm gonna copy it , then open the system environment variables and add a variable named open A I colon .
OK , go on .
Org ID with the value from the website and then go back to the website and down here in API Keys , I can create a new secret key .
Let's give it a name and I'm going to copy that as it tells you , it's never going to show you that again .
If you did forget the key , it's OK .
You can just delete it here from the list and create a new one and then update your code .
So I need a new environment variable for that key , which is going to be open a I colon , OK ?
And then I'll past the value here .
Here's the important thing to remember and save yourself some time .
If you set environment variables , you have to restart visual studio and maybe you just have to restart .
I s express .
I don't actually know , but you do that to get the new values picked up .
Now run it and see if it works .
So this is gonna actually send a message to Jack G .
BT .
There we go .
So we have a basic web based chat interface .
It's time to bolt on the twill part so that the application can handle s MS calls .
How does An s MS API provider like to work at present ?
When you sign up with them , you get some free credit .
It was $15 when I did it , which you use for a phone number for your region .
And any s MS you send and receive is paid for with that credit .
Remember , of course , that you are using a metre service and will get stuck with the bill if you send or receive a lot of messages , or if you call your emergency number like 911 .
But are not set up properly in North America .
You're charged $75 .
If you don't have an address in the system for the phone number , you'll see I haven't set it up in my account .
And that's because they expect a US address .
But I'm in Canada anyway .
I just won't call 911 using it from the coding side , you need a public URL .
Most likely a Web API that the S MS provider will call when a text message comes in to your number .
I'll show you how to create the Web API and expose it to the Internet using conveyor during development .
So you don't have to keep publishing your code to a server , which you would have to otherwise .
So first thing to do is add required packages .
So I like doing it and develop a powershell dot net a package and also s p net core .
You can probably get away without easing these packages if you want , but they help with formatting s MS replies in this case .
Next , I will add a web API controller to the project .
I'm gonna cool .
It s MS receiver controller .
I'm going to change it to a twill controller and decorate the class as API controller and the root I want to be API slash controller and I'll make the default method something I can test out because I know I won't get it right first time .
So there's no view .
It's an API so content Hello .
I need to add controllers to the services in programme .
So I'm gonna go there services at control This and I need two map controllers and run and test in the browser .
That's the main page .
But we want to call that controller .
So it's s MS receiver .
So there we go .
Hello , world .
Great .
I just need to create an action method to receive the S MS calls now .
And I got this method signature from Let me just go here .
I got this method signature from to documentation where from will be the sender's phone number and of course , body is the text message .
It's decorated HTTP post because by default , twill sends the messages a post request .
The from form attribute is because the post requests coming in will have the data in form of coding for other s MS providers .
The request could be totally different .
It could be json formatted , for example .
And so this code won't work at this point , I have everything I need to send the message on to chat g BT like I was before .
And then whatever I set in the return value , the TT m L result is sent as a response text to the user .
So let's write some code to actually use the chatbot session .
Uh , not quite .
We wanna make sure from is not now .
And we're just gonna create a chatbot session like that and the we're gonna create a new conversation message based on the arguments that came in .
So that's an empty message .
We're gonna fill it with the body , and the sender will be .
Obviously , it's not from it's actually the user .
The user wrote the message on the phone and sent it to us .
And now we're sending it to Jack G .
BT so we can get the reply and call that method that we wrote before forward message from user as sync with that message we just created .
And then we want to create an object that twill is expecting for the response .
So we send the response back to to and then it sends it as a text message back to the user .
So we just need to load it with the reply we got from chat GP t and then return it formatted the correct way , OK ?
And what is wrong here ?
Can I assign void to an implicitly typed variable ?
Ok , did I not specify ?
Yeah , I have to specify the return type over in this method that we wrote earlier and actually return it .
There we go .
And this is not supposed to be message response .
Supposed to be messaging response .
I think , um , when you also need to pull in the name space and we better just finish this off .
So if from is now , uh well , for now , we'll just return .
I OK now , to test this , I can either send a post request from a programme like Postman .
I'm just gonna run it , which allows me to create the request manually .
Or I can set up twill with this Web hook and hope it works .
I can't give twilio my local host for the webhook address because to twill , local host is their local machine .
Um , so that's why we're going to use the conveyor extension to , uh , create a public URL via tunnelling that we can give to twill .
So because I need to set up conveyor and have it tunnelling as well as setting up twill to point to the correct URL .
And because this introduces more points of failure when I haven't even tried out my method , I'm gonna send a test request from postman first and make sure it works .
Otherwise , I would be banking on twilio being configured properly , conveyor running properly and my API action working properly to do anything , so I'll test it in Postman Postman is a nice tool for testing Web API s and here it is .
So I'm going to create a new request .
The URL is this one , and I'm going to set the type to post , and the request that we want to send is formatted like this x w w format R l encoded .
This is according to to Leo's specifications in their documentation .
And there's two fields that we want to send one is from .
So that's gonna be the phone number of the person who sent us a message .
So we'll have to make up something really doesn't matter .
And we need to send the message body .
OK , my application is running , so I'm just gonna try it out .
And there we go .
So we can see that it sent my hello from postman message to the , uh , controller just fine .
The controller sent it on to chat G BT and chat GP t replied with its standard ish reply .
Great .
If it didn't work , I would put a break point in the method and try again and see if the request is being mapped at all .
If it isn't even hitting the break point , then I know something is wrong with either my test request .
Perhaps the wrong type of method or form encoding is wrong , or there's a mistake capitalization or spelling .
You can take a bit of trial and error to resolve issues like that without seeing how the A s p dot net middleware is actually handling your request .
So now I see it working .
I feel more confident to try it with twilio .
But I know I will need to give twilio a public URL , but without deploying my code , I don't have one .
The easiest way to get a public URL is to use a tunneler like conveyor .
There's a version of conveyor for the command line , which you can get from conveyor dot cloud website .
But in this video , I will set up the V s extension In visual studio , I go to extensions , manage extensions and in the online tab , I can search for conveyor download and close visual studio in order to install it now , I followed the installation process and restarted visual studio .
When I run the application , now I can find the conveyor window for me .
It's down here and I can see it's giving me the local URL , a remote URL , which is good for land connections .
And we want the Internet URL , which we can get by clicking access over Internet .
The first time you click access over Internet , you'll see a request to log in and also a register button .
You can click the register button to go to the website and sign up for free .
Once connected , conveyor will set up a tunnel , and I can see here the Internet URL .
This is the free level of service , which gives you a URL that changes every time you connect to tunnelling and only works for four hour stints .
After that , you have to restart V s and get a new URL .
Pay plans are pretty cheap and more convenient .
So now I have the public URL .
I can go to the twill website and log in .
They do have a great onboarding experience , which I have already done and received an s MS number .
Hopefully you can do that easily enough then to configure a message webhook I go to phone numbers , manage active numbers and I click the number I want to work with .
There's a warning I was talking about earlier for the 911 .
I'm just gonna ignore that , go down to messaging configurations .
And then here's what What ?
URL will be called when um an s MS comes in so you can see here .
This was the last time I was running it .
So I'm gonna update the URL to the new public URL and save it and remember that with the free conveyor plan , the URL is subject to change .
So keep an eye on that .
And now the moment of truth , I will send an S MS .
And it worked .
If it didn't , I could put a breakpoint in my method and check if the request came in properly .
If it didn't , there is a message log in to that can help .
There is one issue that may not have been obvious , and that is that this code treats every incoming message regardless of the sending phone as part of the same conversation , because it only has one instance of chatbot session .
It's pretty easy to go back to the controller and change this line here to get or create chat bot session and pass in the number of the cooler , and we're going to return the chatbot session , and what I need is a field to store my sessions in .
So let's make it a dictionary of strings for the from number and chatbot sessions for the corresponding session .
Now , if the if there isn't already a session for the from number , we create one and then we return , so it will either return an existing one or create a new one .
So now at least you can have separate chats for separate users to take it to production .
Of course , you would need better error handling logging session , time out , validation to prevent abuse , et cetera .
Well , I hope you found this video helpful and feel free to like and subscribe , Of course .
And thank you for watching Bye for now .