تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Recap: Assistants Workflow0:00

Alright, so have a look at what we worked on in the previous episode. And yeah, if you don't mind, I'm going to do a quick 30-second recap just to make sure we're all on the same page. Okay. So we begin by uploading one of our fictional documentation files to OpenAI. Next, we create a brand new assistant. And again, keep in mind, right now, we are creating an assistant every time we visit the home page, which, of course, you don't want to do. In real life, you only have to do it a single time. And from then on, you can reference the ID of the assistant, and we'll talk about that.

In real life, you only have to do it a single time. And from then on, you can reference the ID of the assistant, and we'll talk about that a little later. Alright, next, we create a brand new Thread, and we run it as part of the same API call. Alright, finally, we run it, but of course, it doesn't happen instantly. It sort of gets thrown onto a queue. So we will, every second, and we could probably speed that up even, but right now, every second we check on the status of the run. And once it has completed, we grab all of the messages and we dump them to the screen. Alright, so yeah, a little bit of terminology there, but luckily, we already know it, right?

Key Terminology Overview1:01

And once it has completed, we grab all of the messages and we dump them to the screen. Alright, so yeah, a little bit of terminology there, but luckily, we already know it, right? A thread, you've participated in a forum, we all know what a thread is. A thread can have many messages. A message belongs to a thread. So when I write something to OpenAI, that's a message. When OpenAI responds, that also is a message. And those messages are contained within a private thread. Okay, so the only confusing one, though, would be a run. Think of a run as a task that you want OpenAI to perform.

Okay, so the only confusing one, though, would be a run. Think of a run as a task that you want OpenAI to perform. And maybe that task is just answering the question, right? So if it helps, think of a run as, I don't know, the enter key. That's how I think of it. Enter key, do the thing that I asked you to do. It could be some kind of a mathematical calculation. I could try to do that. It could be some kind of some coding thing that you wanted to do, or it could be compiling the top 10 restaurants that are five miles from where you currently are.

Planning Code Organization1:58

It could be some kind of some coding thing that you wanted to do, or it could be compiling the top 10 restaurants that are five miles from where you currently are. All of those are commands that you want OpenAI to perform or to run. Okay. So now, yeah, all of this works, but again, it doesn't necessarily reflect the code you would actually write for your projects. So before we move on to something new, yeah, why don't we just discuss how you might go about organizing this and some considerations along the way. Okay, so we're imagining that we've built this tool called LaraParse, and we want to offer a tutor of sorts.

Okay, so we're imagining that we've built this tool called LaraParse, and we want to offer a tutor of sorts. Maybe this application or this project houses the documentation, sort of like the Laravel website. Okay, so with that in mind, why don't we create a custom assistant? And I'm going to call it LaraParseAssistant. So think about it. What are some behaviors or abilities that this class will need to have? One of them, at least to start, is the ability to create the assistant. And I'm imagining this could be a static constructor of sorts.

One of them, at least to start, is the ability to create the assistant. And I'm imagining this could be a static constructor of sorts. So when you call this method, it will actually create the assistant. That thing that I said only needs to happen once. All right, what else? Well, let's do this. Let's open up a split. And yeah, we're just going to go back and forth. So yeah, this section that I have selected right here, it uploads a file. And yeah, if you want, you could say upload, but you know, it's your project.

So yeah, this section that I have selected right here, it uploads a file. And yeah, if you want, you could say upload, but you know, it's your project. Why don't you have a little fun with it? What this is really doing is we are attaching a documentation file to our assistant. We're sort of educating it. So with that in mind, why don't we call it Educate? And then we'll give it a path to the file that we want to provide. And maybe in the future, this could be an array of files. All right. So yeah, that kind of takes care of this and this.

All right. So yeah, that kind of takes care of this and this. Next, create and run. That would be fine. But probably, maybe we should split those up into two threads. So maybe I have a method called createThread, and then we'll have another one called run. Or again, if you don't like that terminology run, you're in charge. This is almost like an adapter of sorts. You could call it execute or command or go. It doesn't really matter.

You could call it execute or command or go. It doesn't really matter. You can be in charge of that. Okay. So when we run it, yeah, you know what I'm imagining is maybe the run method contains all of this stuff. And in response, it returns all of the messages. Okay. So you get the basic idea here. We're figuring out what the behavior should be.

Building Educate Method4:42

So you get the basic idea here. We're figuring out what the behavior should be. All right. So we're going to take this in a couple steps. First, I'm going to grab this code and move it to educate. I will import this. And then right here, I'm going to replace this with our file. All right. And maybe, yeah, I don't know what this is going to return. Are we going to wrap up the response from OpenAI inside of our own class?

And maybe, yeah, I don't know what this is going to return. Are we going to wrap up the response from OpenAI inside of our own class? We could do that. Right now, I'm not going to worry about it, though. Okay. Let's switch back. Now, this code can be replaced with a new instance of our LaraParse assistant. So we'll call it assistant. And then I can say assistant educate, and we'll provide a path that I'll paste in here to that markdown file.

And then I can say assistant educate, and we'll provide a path that I'll paste in here to that markdown file. Cool. So that should get rid of this. Next, what about this section, creating the assistant to begin with? All right. Once again, I will copy all of that. Switch back. And yeah, like I said, this will be a static constructor. So let's see.

Creating Assistant Factory5:39

And yeah, like I said, this will be a static constructor. So let's see. Our defaults are the model, a name. I'm going to flesh out the instructions a little bit. And then the fileIds, we're not going to do that initially. We'll have to figure out a different way to associate a file with an assistant. But one thing we should probably do is allow for overrides. So why don't we say array config. It'll default to an empty array. And then what I can do is say, how about array_merge_recursive, and we'll take our

It'll default to an empty array. And then what I can do is say, how about array_merge_recursive, and we'll take our defaults and we will merge them with any potential overrides that you pass in. And I think that should do it. All right. And yeah, once again, we're just going to return whatever the API returns. But it's a very common practice to wrap up that response inside of something that you can own. All right. Now I can get rid of that.

All right. Now I can get rid of that. And yeah, you wouldn't need to contain that call within your homepage callback. Instead, you would just do it a single time, something like this, layer parse assistant create. And that will take care of physically creating the assistant on OpenAI's end. But now we do have one issue. If I go to the educate method, yeah, you'll see it's uploading a file. But because we got rid of that code that associates the file with the assistant, right now, it's sort of like they're not linked up.

Linking Files to Assistant7:01

But because we got rid of that code that associates the file with the assistant, right now, it's sort of like they're not linked up. We have a file here, and we have an assistant here, but they don't know about each other. All right. So here's what I'm thinking. Hmm. This is going to return a file, all right? Well, maybe we could optionally pass a second argument for the assistant. And that would be our trigger that if one is provided, we need to associate the file with the assistant.

And that would be our trigger that if one is provided, we need to associate the file with the assistant. And we can do that like this. OpenAI, assistant, where are you, there we go, files, create. So we would need to give the assistant ID. And then just like before, that code that we removed from up here, we're just going to store it here. fileID equals fileID. Okay. So now we need to figure out how do we pass the assistant, because right now we have this

Okay. So now we need to figure out how do we pass the assistant, because right now we have this call here, and that is going to create an assistant. So actually, let me show you this real quick. Let's go to our routes/web.php file. And what I'll do is comment out all of this. Okay. So all we have, and we can get rid of this as well. All we have to start is laravelParse, assistant, create, and we're going to die and dump the response.

All we have to start is laravel parse, assistant, create, and we're going to dd the response. All right. There we go. So notice we have an assistant response, and here is the ID. Okay. So when we create an assistant, it returns an object that includes the ID. But once that assistant already exists, there's no need to create it again. In those situations, you can simply retrieve it by passing through the ID. So why don't we do this?

In those situations, you can simply retrieve it by passing through the id. So why don't we do this? Let's create a constructor. And we'll assume whenever you instantiate an object, you will need to pass through an assistantId. Then what I could do, and we'll probably tweak this, keep in mind, we're taking this in a number of steps. But yeah, I could say openAI assistants, and there's a retrieve method where we pass through the id of the assistant. Okay.

through the ID of the assistant. Okay. So now I'm thinking we could store that on the object like so. And let's go ahead and set the type, right? And clean this up. All right. So now maybe our static constructor will have our assistant, and then it's going to instantiate itself. So it's mostly just a factory here. And we will pass through, hmm, let's see.

So it's mostly just a factory here. And we will pass through, hmm, let's see. Okay. So one issue with this approach is we're sort of making two API calls, and one of them isn't overly necessary. So I guess one thing we could do is our constructor could accept the ID of an Assistant or an AssistantResponse instance, and then it'll just figure out what it has. And if it has an Assistant, it will use that. If it just has a string, it will try to retrieve that. But you know what?

If it just has a string, it will try to retrieve that. But you know what? For now, let's just keep it simple. It's not a big deal either way. And we will perform that second API call. So now let's come back to our educate method. And yeah, this is what we were toying around with, right? If an assistant is provided, we should associate it. But you know what? Now that I think about it, when would you not want to provide an assistant?

But you know what? Now that I think about it, when would you not want to provide an assistant? So with that in mind, let's just assume that's always going to be the case. So instead, I'm going to say, do this no matter what. And I'll say, this assistantId. All right, makes sense. Next, we might want to continue chaining here. I could imagine doing that. So let's return the instance like that. And I think this is good.

So let's return the instance like that. And I think this is good. All right. What else? All right. Let's go back to our routes file. All right. So now we've created the assistant, which is good. But yeah, what about the next page request? It's almost like we need a way to store that ID of the assistant.

Persisting Assistant ID10:50

But yeah, what about the next page request? It's almost like we need a way to store that id of the assistant. And again, keep in mind, this doesn't have to be that dynamic. We're assuming we're building a project for this LaraParse package that we built. So it can be as simple as just storing the id of the assistant within your .env file or something like that. Why don't we do that now? All right. So let's go back to Chrome. And I'm going to copy the id of this assistant.

So let's go back to Chrome. And I'm going to copy the ID of this assistant. And yeah, we could hard code it here. Or again, like I said, you could put it within your .env file. And we could say, LARAPARSE_ASSISTANT_ID. And I'll paste that in. All right. Next, why don't we go into our config? We could put it in OpenAI if we want. Right here, assistants.

We could put it in OpenAI if we want. Right here, assistants. And we'll say id. Why don't we just do assistantId? And then this will be LaraParse assistantId. Cool. So now let's go back to our routes file. Yeah, like I said, we can just instantiate the class and then pass through the id like so. LaraParse.

so. Lara parse. Where are you? Hmm. OpenAI. Oh, yeah, assistant.ID. So we instantiate that class. We create an object. We pass through the ID of the assistant. We fetch that assistant.

We pass through the ID of the assistant. We fetch that assistant. Now we should keep in mind that could potentially throw an exception if an assistant with that ID doesn't exist. So ideally, we should catch that and then respond however you need to. But for now, we're going to skip over it. All right. Next, once again, you only have to educate your assistant once. And we've already done that. So I can get rid of that.

Thread, Write, Send API12:33

And we've already done that. So I can get rid of that. And let's move on to this section right here. All right. And this won't take long. OK, so we want to create a thread. So let's go into our assistant. And we have a method, I believe, called createThread. And the manual call for that would be OpenAI threads create. All right, so that will give us our thread.

And the manual call for that would be OpenAI threads create. All right, so that will give us our thread. And yeah, maybe we should cache the ID of the thread on the object. So we'd have threadId. All right. So let's go ahead and create that property, like so. All right, let's scroll back down. So here we have assistant create a new thread. And as before, I don't know if this should be fluent. But just for now, I'm going to assume that it is.

And as before, I don't know if this should be fluent. But just for now, I'm going to assume that it is. All right. What else? We should probably offer the ability to write a message. So here's what I'm thinking. Let's create a Thread and then write something like, hello. And we can call this method or chain it as many times as we want. So we could say, hello. And then maybe as a second call or something like that,

So we could say, hello. And then maybe as a second call or something like that, how do I grab the first paragraph using LaraParse? OK. And then finally, we've created the thread. We've written a message. We've written another message. Why don't we go ahead and send it? And that will give us the responses. And this is kind of the API that I'm imagining right here.

And that will give us the responses. And this is kind of the API that I'm imagining right here. OK. So let's go ahead and create our write method. And then also our send method. And it seems like we could chain send off of the write response, which, of course, means this is going to be fluent. OK, so how do we write a message? Well, you'll see right here, we included the messages.

OK, so how do we write a message? Well, you'll see right here, we included the messages and the thread creation and the run all in one API call, which is cool. But if we want a little more flexibility, we will have to split those up. So yeah, let's do this instead. We can say openAI->threads, and you'll see there is a messages method that we can chain on it. And we can create a new Message that is associated

see there is a messages method that we can chain on it. And we can create a new Message that is associated with that specific Thread. OK, so if we click through here, they want the ID of the specific Thread we care about, as well as the parameters. All right. Well, we've already cached it. threadId. And then we need to pass through the parameters.

Thread ID. And then we need to pass through the parameters. Now, the parameters, if I switch back here, will be this right here. So I can grab that and paste it in and reformat. All right. We are going to write a message. So I can substitute that here. And then down to the send method, yeah, we discussed the idea that if you

And then down to the send method, yeah, we discussed the idea that if you don't like that run terminology, you can replace it. And that's what we ended up doing. Cool. OK, so what is the code for running it? Well, once again, we're going to have to figure out what the manual API call is. But then I also want to grab this stuff or this junk right here and move it over.

But then I also want to grab this stuff or this junk right here and move it over. OK. All right, so it turns out I can say openAI threads runs create. And you know what? I, myself, always forget what the parameters are. Give it the threadId. So once again, this is the thread that I care about. And then I need to provide the assistant that I want to run.

So once again, this is the thread that I care about. And then I need to provide the assistant that I want to run. So that would be assistantId is this assistantId, like so. All right, so that is our run. And yeah, now I'm going to paste in all of that code from the routes file. And yeah, if this is confusing, just think of it as polling. We're going to keep making this API call to check what the status of the run is. And as long as it is not completed, it is still working.

what the status of the run is. And as long as it is not completed, it is still working. So sleep for some kind of variable amount of time and then do it again. That's all that's happening here. Finally, right here, we will grab all of the messages for the thread. So what I'm thinking we could do is make that its own method. So we have createThread. We have write.

So we have create thread. We have write. Why don't we also have a method called giveMeTheMessages for this assistant? So I'm just going to grab all of that and move it in. All right. Now you'll see here it's passing through the runThreadID, but we can just access the threadID off of the instance like so. OK, so now I can delegate like so.

like so. OK, so now I can delegate like so. Return this messages. Oh, and keep in mind, we still have a little bit of work to do. So I'm thinking in the next episode, we will talk about when it might be beneficial to take those API calls and extract them into their own class that we then inject into our assistant. And we'll talk about why to do that, when to do it, and if you should do that.

And we'll talk about why to do that, when to do it, and if you should do that. So that should be fun. All right, let's get back to work. So we have our assistant. We create it as part of the constructor. We also have a factory constructor. And I'll go ahead and add that return type. We have a method called educate where we can feed it a file. And again, a second pass would include accepting an array.

We have a method called educate where we can feed it a file. And again, a second pass would include accepting an array. And if an array is provided, we loop over it and make this call. Or you know what? I'm assuming there is a way to upload multiple files as part of a single API call. And you know what? I just don't know that off the top of my head, but maybe this could just be an array or something like that. And we'll look that up.

but maybe this could just be an array or something like that. And we'll look that up. All right, next, we associate the file with the assistant. We have a method to create a thread. And you'll see here, it's expecting parameters. And don't forget, you can review all of these things through the referenced links, like so. So to create a thread, we can pass through a list of messages to instantiate the thread with. So yeah, we could do that if we want.

to instantiate the thread with. So yeah, we could do that if we want. So if you want to allow for that kind of flexibility, yeah, you could do something like accept an array of parameters, but default that to an empty array, and then pass those through. That way, you do have the option of setting the messages when you perform this call. All right, next, we have effectively a getter. Let's add the return type and simplify that.

All right, next, we have effectively a getter. Let's add the return type and simplify that. Next, we have a method to write a new message and associate it with the thread. Cool. Next, we have a method to actually run the task. So we create a run, we reference the thread ID, and we also reference the corresponding assistant ID. And yet again, if you want to make your parameters here configurable, do the exact same thing as we did before.

And yet again, if you want to make your parameters here configurable, do the exact same thing as we did before. All right, next, we will sleep. And again, I'm probably going to shorten that. And we will check on the status and then return the messages once it has completed. Cool. So now we can go back to our routes file, and we can test all of this out and see how it ends up being quite a bit cleaner.

Testing the Refactor19:38

and we can test all of this out and see how it ends up being quite a bit cleaner. All right, so let's import this. We create a new LaraParse assistant, and then we create a new thread where we write, hello, how do I grab the first paragraph using LaraParse? We send it, that will run the task, and it will return the messages where I will once again die and dump them to the screen. All right, let's go back to Chrome.

where I will once again die and dump them to the screen. All right, let's go back to Chrome. Let's give it a refresh. Three, two, one. And here is our response, just like the previous episode. All right, let's have a look. Our content, and yeah, of course, when we return the messages, we could also pluck all of these out so that you're not digging through all of these confusing arrays. All right, but anyways, we have hello.

so that you're not digging through all of these confusing arrays. All right, but anyways, we have hello. The next one, of course, is going to be, how do I grab the second or the first paragraph? And the most recent one will be the response from OpenAI. So let's have a look. All right, to grab the first paragraph of a markdown file, blah, blah, blah, you can use the following code. It works. All right, very cool.

It works. All right, very cool. So yeah, notice it's just a bit of work, a little cleanup, and it really does improve the code a good bit, I think. So now, yeah, the huge benefit, when I come back to this six months from now, I don't have to remember or relearn what a run is or how to associate the thread ID or the assistant ID or what the parameter list should be. I don't care, and that's the entire point.

or what the parameter list should be. I don't care, and that's the entire point. Instead, I have a method called write, and it makes sense to me. I also have a method called send, and I don't need to know what send is comprised of or composed of, whatever the correct word is there. I just know that it works, and again, that's the entire point. Okay, but we're not yet done. In the next episode, I want to take five, six, seven minutes maybe to discuss why and when it might be beneficial

Next: Extract Client Class21:26

In the next episode, I want to take five, six, seven minutes maybe to discuss why and when it might be beneficial to take these open AI calls that we have in our assistant and extract them into their own object, and we might call that a Client. You have your own object where those calls can exclusively live, and then you take that class, and you inject it as an object into your assistant. So we're going to talk about all of that in the next episode. Please be excited.

So we're going to talk about all of that in the next episode. Please be excited. I'll see you next time. Bye-bye.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟