Afrikaans
Akan
Albanian
Amharic
Arabic
Armenian
Azerbaijani
Basque
Belarusian
Bemba
Bengali
Bihari
Bosnian
Breton
Bulgarian
Cambodian
Catalan
Cebuano
Cherokee
Chichewa
Chinese (Traditional)
Corsican
Croatian
Czech
Danish
Dutch
English
Esperanto
Estonian
Ewe
Faroese
Filipino
Finnish
French
Frisian
Ga
Galician
Georgian
German
Greek
Guarani
Gujarati
Haitian Creole
Hausa
Hawaiian
Hebrew
Hindi
Hmong
Hungarian
Icelandic
Igbo
Indonesian
Interlingua
Irish
Italian
Japanese
Javanese
Kannada
Kazakh
Kinyarwanda
Kirundi
Kongo
Korean
Krio (Sierra Leone)
Kurdish
Kurdish (Soranî)
Kyrgyz
Laothian
Latin
Latvian
Lingala
Lithuanian
Lozi
Luganda
Luo
Luxembourgish
Macedonian
Malagasy
Malay
Malayalam
Maltese
Maori
Marathi
Mauritian Creole
Moldavian
Mongolian
Myanmar (Burmese)
Montenegrin
Nepali
Nigerian Pidgin
Northern Sotho
Norwegian
Norwegian (Nynorsk)
Occitan
Oriya
Oromo
Pashto
Persian
Polish
Portuguese (Brazil)
Portuguese (Portugal)
Punjabi
Quechua
Romanian
Romansh
Runyakitara
Russian
Samoan
Scots Gaelic
Serbian
Serbo-Croatian
Sesotho
Setswana
Seychellois Creole
Shona
Sindhi
Sinhalese
Slovak
Slovenian
Somali
Spanish
Spanish (Latin American)
Sundanese
Swahili
Swedish
Tajik
Tamil
Tatar
Telugu
Thai
Tigrinya
Tonga
Tshiluba
Tumbuka
Turkish
Turkmen
Twi
Uighur
Ukrainian
Urdu
Uzbek
Vietnamese
Welsh
Wolof
Xhosa
Yiddish
Yoruba
Zulu
In this episode, we're going to have a look at a blog post system that can accept comments.
And the interesting thing about these comments is that if we enter in one, we can then
create the comment, and then you'll see that we have a button.
In the focus of this episode, it's going to be creating this button that could be hindered,
and also an API wrapper for the OpenAI API.
So when we click the button, it'll make a request, and then that request will be an
AI response.
And so there's a lot of different use cases that we can do with this with OpenAI, and they
have a pretty extensive AI.
So if you just want some kind of text generation, or if you want to have some kind of code
generation, which, GitHub's, Copilot actually uses, Codex under the hood, and there's
many more examples.
And so in this episode, we're going to be using the OpenAI and the Deventy model in order
to have this AI bot response.
And because we are using the API, it is a paid solution.
But if you sign up for a free account, you can get about $18 worth of credit that can
be used within the first three months.
And so the cost associated with this is going to vary depending on the amount of traffic
to your website, as well as how much the actual AI is going to be used.
But as we'll see, we do have some mitigating factors that we can do to limit the usage.
And then we're also going to be creating an API wrapper around OpenAI, because it's one
of those things where we are creating an API request for one external API.
But in our application, there's good chances that there's going to be many more APIs that
we're going to be interacting with, and without bringing in any external libraries, I would
like to just create a simple wrapper that we're then going to be able to reuse within
our application.
And so we are going to start out this application with the blog post already created.
And we're not doing anything fancy with that.
We have a very basic device set up.
A user has many posts and a user has many comments.
And as you would expect, a post has many comments and that belongs to the user.
The post has a rich text content, so we're using the action text.
And for the comments, that belongs to the user and it also belongs to the post.
And we also have the rich text content.
So again, we're using action text for the comments.
We have our controller for the post and the controller for the comments, neither of which
we're really going to have to touch in this episode.
For this episode, I was very intentional to keep it as simple as possible.
So I'm not introducing any kind of hot wire with the turbo frame tags or stimulus controllers
for this.
I wanted to just keep it pretty bare bones, so we can focus on building the AI integration.
And so the first thing that you need to do is go to openAI.com and sign up for a free account.
We then generate some kind of API key.
And you'll just want to copy this and keep it secret.
And so first, let's go into our routes file and we'll make a route for the open AI to comment.
So we'll just create a block under the resources for the comments.
And the reason for that is because when we make the call to open AI, it is going to generate
a response based on the prompt we give it and the prompt that we're going to give it is
the comment.
So then we could also have a resources for the comments.
And we would only need the create action.
But I don't like doing this because this is very confusing.
If I were to just look at this, I would think that this is so that people can make a comment
on a comment and it has nothing to do with the open AI.
So instead, what I would rather do is to just wrap this comment that we just created
in a name space.
And we'll just call this the open AI.
We'll create our block and they will close it out.
And before we move on from that, let's go ahead and run the Rails routes.
Dash G. So we can see what the routes are and we'll grab the open AI.
And so we can see our prefix.
This is going to have the URI for the posts.
So we'll pass in the post ID, the comments, the common ID.
And then it'll just go to the fourth slash open AI, fourth slash comments.
And then we have the open AI name space, a comments controller with the create action.
And so I'm happy enough with this.
We then generate our controller and we'll just call it the open AI, fourth slash comments.
And as we would expect, that would create the folder open AI and the comments controller.
And within here, we can make our create action.
Similar to what we are doing in other areas of the application, we have a before action
for the authenticate user.
And that simply a device helper that we want to make sure that on the open AI that we do
have a user authenticated before we make that API call to open AI.
And so I'm going to lead this alone for now because what we are displaying out the comments,
we can trace that back if we look at our posts.
When we are showing a post, you'll see that we are rendering out the post comments.
And so we need to look at the comments folder in the comment partial.
And then here we have where we are displaying the user, said on the date, and then the content.
So let's go ahead and create our link.
I'm not going to do a link too because we do have a post request that we are making for
that create action.
So instead, I'll do a button too.
We can then give it some text.
We'll just call it the ask AI to respond.
And then this needs to go to the post comment, open AI comments path.
We do need to pass in both our posts and the comment for this particular endpoint.
I'm also going to have a class because I am using bootstrap on this project.
I'll just make a button small and then a button outline primary.
And just so you know, this post and the comment, we do have access to both of these
because when we are displaying out in the post show page, we're rendering out the post comments.
So this will automatically create that local variable comment.
But then we are passing in the local variable post sending it to the instance variable.
Because when I'm working in partials, I don't like use it instance variables within
here.
For a few reasons, it may not be clear what that object should actually be.
And if that instance variable was even required by using local variables instead of the instance
variables, then we would actually receive an error that the variable wasn't defined.
Otherwise we would get a nil and then we could get into situations where we get a
undefined method created at four nil class and we don't want to deal with those kind
of issues.
And so surprisingly, this is really all we have to do on our front end.
We created the endpoint and we created our button.
Everything else is going to be done on the back end.
And we'll start with then the controllers, open AI and the comments controller.
Because there's a lot of different ways that we can architect this, but essentially what
I want to do is that we're going to create a new comment.
And that comment is going to be from our current user, which we do have access to, especially
because we are requiring that user to be authenticated and then we'll have a comment
start new.
We can set that new comment and we can set the post is equal to the post, which that
post we don't have access to.
So we're going to create a private method for our post. We'll send an instance variable post
double pipe equals just so we are memoizing it because we are going to refer to the post
potentially a few different times and then we can have a post stop find and then we need
to pass in the prams and then the post underscore ID and that post underscore ID.
If you remember, if we look at our routes, we have our post and then that post ID and then
for the comments, we have the comment ID and we're also going to need that comment because
we need to get the content of that comment.
And so we're not going to be using the comment multiple times, so there's no need to
memoize it, but we are going to call on our post.
Notice that I'm just using the local variable and not the instance variable that we
said because we don't know if that instance variable has been set yet.
We name call the comments.
Find and we can find the comment based on the prams comment underscore ID.
So now that we have created a new comment, we haven't saved the yet and we've also
associated the comment to a post.
We then set some kind of content so we have our new comment dot content and we want to
set this equal to and we'll propend some text on here, we'll just call the AI said and
then we can call some kind of external service.
So we wouldn't want to put all of that logic or call an open AI within our controller.
So I'm going to create a different kind of class.
We'll just call this the open AI commenter.
We'll have a class method called call and then we're just going to pass in the comment
dot content, but the problem with this is that we just need the plain text of the content.
And because this content is action text, we can call to underscore plain underscore text.
We then check if the new comment that save, we can then just redirect to the post with
the notice AI has responded and that's all we're going to do in here for now.
If you do have your system set up for hot wire where you're broadcasting the changes
that you would need to redirect to the post, you could just call the new comment dot save
then maybe a head okay.
So that way it's not expecting any kind of return value or input, but then turbo would
then broadcast the response stream to the browsers.
And I would definitely prefer doing that over just calling our class here because then we could
just call this from a background job.
So that way the end user isn't having to wait for open AI to generate a response.
But again, those kind of implementations are very simple and we've already covered them
in the past another episodes because we have done the turbo stream broadcasts and we've
done plenty on background jobs.
You would simply just move this into a background job passing in the comment and the broadcast
within the comments model would handle the rest.
So I'm going to undo these changes for now because we're not going to set that part up.
We're just going to redirect back to the post.
So we do need to create this open AI commenter and I'm just going to create a new file in the
models.
We'll call it the open AI commenter dot RB.
It'll be a class of open AI commenter will have a class method named the call method.
And we're going to be taking the comments content plain text.
So I'm just going to call it the prompt.
We'll create a new instance of this class passing in the prompt and then we'll have a call method.
We can initialize a class taking in our prompt.
Well, then set our instance variable prompt is equal to the prompt and then we'll have the call
method where we'll do all the business logic.
And so we do have a few different things going on here.
I'm first going to set a constant and that constant I'm just going to put the URL for the open
AI's completion API.
And while I could do all of the necessary bit of code to make the API call from here, I actually
don't want to do that.
I want this open AI commenter to be specifically on the formation of the request and getting the
response.
I want to create an API wrapper that's then going to make whatever kind of API requests we want
to make, whether it's a get request or a post or a putter patch.
And so this API let's just call it an API client.
And this API client, we're going to initialize it and we're going to take in a few different variables.
We're going to take in our URL.
We also are going to create some kind of headers and then we're also going to have
some kind of body that we're going to pass in.
We then have an action where do we want to do a get request, a put request, a delete, or in our case,
we want to make a post request to this endpoint.
We can set this to a local variable.
And for open AI specifically, it's going to give us a response back, which we'll look at.
And we can actually do that with a Rails.logger.
And unlike using emojis in my logs, just so I can see it more clearly.
And we'll just spit out that JSON because I've already done this in preparations.
I know that this is going to return some kind of choices.
And then we can get the first item and then the text response.
If the choices is nil, because it will some kind of issue, maybe we've hit some kind of limits,
or some other kind of issue, we can do a rescue or some other kind of handler.
Because remember, when we are calling this open AI commoner from our controller,
it is expecting some kind of text response.
So this is going to be visible to the end user.
So we can capture whatever kind of errors we want here.
And the nice part about this kind of thing, we can just respond with,
I'm sorry, but I cannot give a response based on the information provided,
or some other kind of meaningful error.
But if we go back and look at the comments controller for the open AI,
remember, we are creating this comment as that particular user.
So if the user has the ability to delete their own comments,
then they're going to be able to also delete the comment that was created by the AI.
So for that reason, I would actually be okay with having some kind of text like this,
because if the user who created or asked the bot to make a response,
doesn't like the response or it wasn't what they were looking for,
then they could delete it and move on.
But before we create this API client, we do need to go ahead and create
this headers and the body, because these both are going to be private methods
within this model.
So we have our headers and the body method.
And for the headers, we do need to set the content,
dash type, and this is going to be the application,
or slash JSON.
And then we also need to pass in our authorization.
The authorization is going to be bear with the space,
and then we need to interplay it in our API token
that we are getting from OpenAI.
And we can just make this an environment variable or something like that.
However, this is just a semantics thing.
I really don't like interplating in text like this.
I would rather do it as an array, or we have our bearer,
and then we also have our token,
and then I'm just going to join it with a space.
And so for me, I think that reads better,
and it's easier to just work around in my opinion,
but you can interplay it in if you want to.
And for the body, we're also just going to make a JSON.
We need to pass in some kind of model,
and their documentation is actually very good.
I like it.
It explains all the different models that you can use.
And in our case, I want to use the DaVinci model,
and its code is text-divinci-003.
We take in our prompt, and that prompt is going to be our instance
variable prompt.
And then the max tokens,
this is basically going to be an integer value,
and that's going to be the number of words in our response
that we're getting from OpenAI.
So if you set this to be too large,
it's going to get expensive very fast.
So I would set it to some kind of reasonable amount.
100 is probably going to be a bit too low,
but you can experiment with what's going to be the expected response.
And then we also are going to pass in a temperature.
And I think it's worth going into the docs for OpenAI,
just so we can see what the completions API is expecting,
because there's a lot more options here.
There's our max tokens,
which you can see it defaults to 16 if we did not include it,
but then the temperature.
It's going to take a number between 0 and 2.
A higher value will make the output more random,
while a lower value will make it more focus and deterministic.
And there's other ones that you can use as well to tweak,
basically how it's going to work.
One that you may want to look at or use is the presence penalty,
and the frequency penalty,
but ultimately it'll be up to you on how you wanted to tweak the settings
to get the desired responses.
And so that's basically all we have to do
to get the AI to generate the response.
Next we have to create that client API,
but that simply just making a post request
to open AI's endpoint with the headers and the body.
So I'm going to create another file
on our models called the API client.rb.
It'll be a class API client,
and this is actually going to be more code than the open AI
cometer, because we are going to take in
a few different things into account.
Well, first initializes,
remember we had our URL, the headers,
and the headers if we don't pass in anything,
just so we don't get any kind of no class errors.
I'm going to set it to an empty hash,
and we're also going to do the same thing for the body.
We'll set our instance variable URL,
is equal to the URL for our URL.
We have our headers is equal to the headers,
and we'll have our body is equal to the body,
but I'm also going to pass to JSON on here,
just so it's properly formatted.
And so we're going to handle a few different types of requests.
We're going to have a get request,
we're going to have a post, a put,
and let's also handle the delete.
So in each one of these cases,
they're going to be instance methods,
and whenever you call the get post, putter delete,
we're just going to make a request passing in,
the appropriate get post, putter delete.
And when I have a single line method like this,
we're as a very simple definition,
I like using the semicolon to just make a one line,
instead of having it multiple lines like this,
because I think that's just going to make it vertically longer,
and it's not necessary.
You can see just kind of add a glance what this is doing,
and what to expect.
We then have our private method,
and we'll first have the request.
We're going to take in our method for that request,
and then we need to build that request.
So I'm going to make a build request,
taking in the method again,
and that's just where we're going to do a case statement on the method.
Because depending on what we're going to use,
it is going to vary on how this is going to look.
So we can have when it's a get request,
when it is a post,
when it's a putt,
and also when it is a delete.
If it is a get request,
then we can respond with the net HTTP get,
and then we can create a new instance of that,
passing in our URL and the headers.
If it's a post request,
we actually want to make this a post.
It's still going to take in the URL and headers,
but we also need to set the body for this,
and so we can call the request.body,
and set that equal to our body.
If it's a putt,
then we need to do the same thing as the post,
except we'll just change this class to a putt,
and if it's a delete,
that's going to be very similar to where it get requests,
but we'll call the delete instead.
And so we could just return the request here,
and that's all we should have to do,
but I think this looks a little bit funny
having the request down here like this multiple times.
So instead, it's a little bit strange,
but I think I would just rather set the request
on each one of these,
and that should work,
or if we wanted to,
we could just set the request up here like this,
on the case,
but then we're going to have to remove this body
on both the putt and the post,
and then we can do a check if we can then check if it's
a putt, or a post,
we'll make it array and just check to see if it includes that method.
Then we return the request.
And so you can do this however you want,
it's really just a matter of styling,
as long as the application is consistent,
with whatever kind of styling you're using here,
then it would be fine by me with whatever direction you went.
So we're going to run the build request,
and we'll set our request as equal to that build request passing in the method.
We can then take our HTTP is equal to the net,
and then HTTP will create a new instance passing in our URL.host,
and also passing in the URL.port.
We can then set the use,
underscore SSL is equal to the URL.scheme,
and we just want to do a check if that is equal to HTTPS.
We can then set some kind of response is equal to
the HTTPS.request passing in our request.
And then we need to handle that response.
So this is going to be another private method that we create,
passing in the response.
And there is a possibility that the request that we make
to the open AI or some other API endpoint is going to timeout.
So what I like to do is to have some kind of rescue
and in our case with net HTTP,
we can rescue from the timeout.
We can then raise the error.
I'm going to create a standard error called API error,
and then we'll just call this the operation time down.
So we do need to create that API error,
and that's just going to be a class API error
inheriting from the standard error,
and we don't need to have anything in there.
So then we need to handle our response.
Again, we'll make that a private method,
and let's just return the JSON dot parse
response.body if it was a net HTTP success.
If it wasn't a success, then we're not going to return,
and then we could do something else here.
We could say some kind of error message,
and we can set this to go to the API request failed with code,
and then we can't interplay it in response.code,
and then we could also interplay it in the response.message.
We can raise the API error with that error message,
but then we also want to handle in case
if we are not getting the proper response.
So for not getting a good response,
meaning that the JSON parsing will fail,
we could also rescue from the JSON parser,
and then we can raise to the API error
that we were unable to parse the JSON response.
And so while this was a bit more,
then the actual API commenter,
I think we are doing two very different things,
and the reason why I like breaking it up like this
is because we have so much going on around that HTTP within here.
Sure, we could use something like HTTP party or another gym.
However, I really don't think that's necessary,
especially once we create some kind of API client like this.
I am going to require the net HTTP at the top here.
Just in case if we're not requiring that else we're in our application,
we don't want to get an error raised that the net HTTP doesn't exist.
It is part of the standard Ruby library,
so there is no external gym that we have to require.
And so once you say you're open a i access token,
don't forget to do that.
We can come in and test this out.
We do what is drifting Ruby.
We'll create our comment,
and then we can ask a i to respond.
It'll take a moment because it is making a API request
and doing some AI generation,
but then we get our response.
And then we could also just make some other kind of comment,
S the AI to respond,
it will respond to that particular comment,
and then we got the response.
And if we look at our logs,
we can see the response that we got back from open a i.
And just so you know how the open a i does handle its billing,
with the prompt tokens,
that was eight, and the completion token,
so the number of words it said in response,
was 20 for a total tokens of 28,
so you would actually be metered build on that 28 tokens.
And so if you're going to implement something like this,
if that is something that you would want to pass a charge
onto the end user,
whether just for the completion tokens or whatever the situation,
you do have access to the usage from that particular user,
but you do want to be careful because it is a metered billing,
and it could get out of hand pretty quick.
And if I were to refactor this,
if we had a particular need,
let's say for the API client,
we also had a situation where you wanted to make a get request,
you then wanted to make a post request or a put or a delete,
multiple times within one single request from the end user,
we are rebuilding a lot of the net HTTP internals here,
for each one of the things that we're doing.
So one option that you could do is instead of passing in that full URL,
we could just pass in the base URL,
and then on the get request, the post put in delete,
we could just take in the URL I.
We would need to update our request to handle that a bit,
but once we initialize the API client and the class that's consuming it,
we can then basically just have one instance of the net HTTP,
and then make the appropriate requests.
But in my case, that really hasn't come up very often,
because the APIs are often interface with our fairly simple,
you just make a request already providing in some kind of credentials.
If you were doing something where you first had to make an authentication request,
and some other kind of handshake,
then I could see that kind of refactor may make sense,
just so we're not having to do an SSL handshake multiple times,
but that net HTTP instance could be reused.
Well, that's all for this episode. Thanks for watching.
Can't find what you're looking for?
Get subtitles in any language from opensubtitles.com, and translate them here.