Agent Loop and Tools0:00
All right, so if you ask me for the recipe for an agent, I would tell you basically two things. First up, we need that loop. We need to give the agent or the AI a way for it to do this, then that, then look into this, then get a response, then do that, then provide a response, right? It's a loop. Now, the second ingredient is, of course, tools . Now, what are tools? Tools are things that we provide the AI in order to enhance its capabilities,
are tools? Tools are things that we provide the AI in order to enhance its capabilities, right? Think about it. Right now, actually, let's just do this. Let's run the dialog command that we built in the last episode, and we'll say, "How much revenue did we earn last quarter?" Now, you already know what the answer is going to be, right ? It's going to say, "Hey, I'm not a magician. I can't really help you with any of that. If you give
to say, "Hey, I'm not a magician. I can't really help you with any of that. If you give me that data, I can help you parse it, but I don't magically know how much your company earned. I don't even know what your company is." So this is what tooling can solve. It's a way for us to interact with AI while also giving it enhancements or functions , basically, that it can run to gather the information it needs. And function is the keyword there.
that it can run to gather the information it needs. And function is the keyword there. It's not like a skill where we're teaching the AI how to do something. Instead, we're saying, "Here's this function you can run if and when you need to gather certain information." And then you can use the response of that function to decide what you want to do next. So, for example, maybe we offered some kind of tool to the agent like revenue, right?
So, for example, maybe we offered some kind of tool to the agent like revenue, right? And it will run that tool, and it will gather maybe some JSON that shows the revenue from yesterday, the revenue from last month, and the revenue from last year. Well, it can then gather that data and then parse it to figure out, alright, well, in the last three months, this is how much you earned, right? It's an ability. It's a way to enhance the capabilities
this is how much you earned, right? It's an ability. It's a way to enhance the capabilities of the agent. That's it. Okay, so let's try this out. Once again, I'm going to duplicate dialogue commands, and we're going to graduate this to agent command. Alright, so I'm going to give it its first signature agent. Alright, so we have our loop here, and what's on your mind, when we are recording the history, this is all in the last episode. So if any of this is blurry, go back, don't skip
history, this is all in the last episode. So if any of this is blurry, go back, don't skip over that. It's important that we go step by step. We get our response, we update the history, and then we provide that response to the user. Now we got to change things up because we're introducing tools. And I'll say, yeah, I'm going to make this as bare bones as possible because this is how I like to learn. So without a mind, let me switch layouts.
Adding First Tool2:39
possible because this is how I like to learn. So without a mind, let me switch layouts. When we make our post requests, we send through the input, but now we're also going to send through tools. So we can see with the request, remember, this is all effectively stateless. So with each request, we're going to tell AI, here are some tools that are available to you if you need them. Okay, so for our first tool, I'm going to paste this
available to you if you need them. Okay, so for our first tool, I'm going to paste this in. We're going to get it very, very simple. It is a function with a name of get current time, and we'll get the current server time as an ISO string. I could not make this more simple if I tried. Notice in this case, we effectively have a function that receives no parameters . Alright, but nonetheless, the AI now has the ability to capture the current server time
Handling Tool Calls3:20
. Alright, but nonetheless, the AI now has the ability to capture the current server time on my machine. Cool. So is that it? Let's check. PHP artisan agent. What time is it? Give it a run. No, it's not going to work. So let's see what the issue is undefined array content. Okay, so the issue here is now, yeah, we talked about this in episode one, right? We cannot just assume that the first item in the response output is going to be our text
right? We cannot just assume that the first item in the response output is going to be our text content. It could be a tool call. So let's scroll up. Where are we? Let's do it right here. Let's dump the response. Alright, one more time. What time is it? And okay. So if we scroll up here is the initial response. If we come down to output, now notice that it 's not responding with a message, because think about it, it doesn't know what the time is yet,
's not responding with a message, because think about it, it doesn't know what the time is yet, but it does know it has a tool that it can that it can use or run to check the current time. So it's going to respond and say, okay, I want to provide an answer to you, but I need to run this tool first. Now, it's not going to do it directly. It's going to tell me basically, Hey, can you run this tool for me? And then I run the tool, I return the
tell me basically, Hey, can you run this tool for me? And then I run the tool, I return the response from that tool and I send it back along with the new request. So now that new request, stay with me here. The new request will be what time is it? It will be, well, let me call this function. It will be the response from that function. And then AI can say, okay, it looks like it's 355. I can gather that. And now I will officially provide a
okay, it looks like it's 355. I can gather that. And now I will officially provide a text response to the user. Notice the loop there. It's so cool when you think about it. Okay. So let's see how we can handle this. The output is now a single item. It wants to call a function, think of it like a tool, it's a function call. And the name of the tool it wants to call is get current time. Alright, so now we know at this point for the current
wants to call is get current time. Alright, so now we know at this point for the current chats, AI is saying, Hey, can I run the get current time tool? But here we just depend on history and then try to spit out text content that isn't even there. So like, we're missing something, right? Let's fix that now and I'll switch to full screen. Now a very primitive and not advised way you could do this is you could say, well, let's look at the
and not advised way you could do this is you could say, well, let's look at the response output. And if that first item is a function call, and we could just say, well, is the type function call? Then all right, let's call the function and then proceed. Now, I'm going to show you in a couple of minutes why I would not recommend doing it this way, because it can actually call multiple functions and a single response. But yeah, you could start with
can actually call multiple functions and a single response. But yeah, you could start with this. And then we might say, well, let's figure out what the name of the tool is that we want to call. So that would be response output once again. And let's see, get the name. And that is the tool that we're going to run. So now again, this is so procedural, it's okay, though, we're going to make it really nice in a minute. We'll say, well, if the tool
though, we're going to make it really nice in a minute. We'll say, well, if the tool equals get current time, and notice in this case, it is, then let's just do it in line. The time is what is it now to? What do we want here to ISO? Is that the one we want? I think so. And now let's append to the history. So we'll say, all right, this history. And this is going to represent our response. So the AI wants to run a tool. It includes that in
going to represent our response. So the AI wants to run a tool. It includes that in the history. We run that tool and we provide the result of that tool again, as a new item in the history. So in this case, it's not me responding, right? It's the tool that's responding . So we handle that again, using open AI, using type here. So the type will be function call output. And again, just check the documentation or interact with your real
function call output. And again, just check the documentation or interact with your real agent to learn what the correct order is here. All right, next, it's really important in this case that we don't just include the tool response, but we associate it with the specific request. And again, that's because multiple functions could be called to a tool. So we need a way to say, all right, the results from this particular request is that value. And
need a way to say, all right, the results from this particular request is that value. And this other function of the same tool or the function called to the same tool is associated with that ID. So we use call ID there. All right. Call ID is going to be our tool. And you know what, actually, what I really want to do is have access to this itself, like a call variable, and then I can grab the call ID from that. So with that in mind, why don't we
call variable, and then I can grab the call ID from that. So with that in mind, why don't we rename this to call that will give us this little guy right here. And then I can say, if call name, the tool name is get current time, and then append the history with the result of the current time. Okay. Finally, I need to give it the output, right? And that will be the time itself. So why don't we just inline that entirely? All right, a little
will be the time itself. So why don't we just inline that entirely? All right, a little messy. That's okay. We're doing it this way. There's a method to the madness. All right. So let's do this. Let's let's go through it. We're going to say, you know, what is the current time where I live? We're going to record that in the history, and we will run the model. The model is going to return a response. And now we know it's going to say, all
the model. The model is going to return a response. And now we know it's going to say, all right, I want to run this tool you have, and what tool are the tool that you provided right here called get current time, run that for me, give me the response. So here's what we'll do. I'm going to immediately update the history like so. So now the history will contain my prompt as well as the output, which includes the tool call here very naively.
will contain my prompt as well as the output, which includes the tool call here very naively. We're just checking like, all right, do we have a function call? Is that tool call get current time? Then here's how we calculate that. Let's append to the history. And then when we run the next loop, it's going to receive that response, use that tool result and then give me the actual time. Little confusing to understand, stick with it until it
then give me the actual time. Little confusing to understand, stick with it until it makes sense. So all of that begs the question, how do we know if we can provide a text response to the user? Because if we think about it, with this initial one, there is no, there is no response because it hasn't been prepared yet. So it's almost like we need a way to check, well, do we have any function calls at all? So yeah, once again, we could say, well, if the
do we have any function calls at all? So yeah, once again, we could say, well, if the first item is a function call, we're going to treat that as, no, we're not ready to provide a response. Otherwise, then we are. This is super naive, but it's okay until we loop over everything to find all function calls. I just want to take it this way to make sure nobody gets left behind. Okay, but now I bet you're thinking, I still don't get it. If you
Implementing Inner Loop10:26
gets left behind. Okay, but now I bet you're thinking, I still don't get it. If you 're following closely, it doesn't make any sense. Think about it. We send our initial prompts and our response is AI saying, hey, I'd love to call this tool. So we've ended the history. We run the tool. We update or push to the history array. But when does that go back to AI, right? Right now it doesn't. So here's the solution. We want a loop
back to AI, right? Right now it doesn't. So here's the solution. We want a loop within a loop. It's crazy, but that's what we want. And here's how I want you to think of it. The outer loop is going to be for our general dialogue with the agent, our back and forth. The inner loop is going to be the inner dialogue of the agent. This is where the agent goes, okay, I want to call this function. All right, and I have the response. Now I want to call
okay, I want to call this function. All right, and I have the response. Now I want to call that function or that tool. Now I have a response. Okay. Now I want to provide a text response to the user, right? There's a little inner loop that represents sort of the internal brain for our agent. So let's do that now. Where does the inner loop go? It's going to go . Let me think about it. I got to think about it myself. It's going to go right here. So
. Let me think about it. I got to think about it myself. It's going to go right here. So it's going to wrap our spinner where we run the model, where we append to the history and where we handle this. I think that's right. Yeah. All right. Let's wrap this within another inner loop. So fun. Once you understand what's going on here. All right. Now let's go through it again. So I said what time is it? That's all we have. And then we have a
through it again. So I said what time is it? That's all we have. And then we have a loop. All right. Spin it up. Ask the AI. What time is it? The AI responds with, I don't know what time it is for you, but I can run a tool to figure that out. So we update the history and we run that tool, we fetch the output, we append to the history, and then this doesn't run. So we continue the loop and we run the model again. Now when we run the model,
doesn't run. So we continue the loop and we run the model again. Now when we run the model, don't forget, we're sitting through the history, but this time the history contains the initial prompts, the response requesting a tool call, the response from the tool call. And now the AI has everything it needs to provide a text response. So let's run PHP artisan agents. What time is it? Give that a run. Oh, it feels invalid type for input to this
agents. What time is it? Give that a run. Oh, it feels invalid type for input to this history. Oh, I'm sorry. I have a nested array where I shouldn't. Of course, we're just adding a single item. One more time. What time is it? And give that a minute. It's running tools. Remember, there could be multiple requests taking place. We have one request to AI. It returns with a call for a tool. We run the tool. We send a request back to
AI. It returns with a call for a tool. We run the tool. We send a request back to AI. Maybe AI then returns another request to run a tool. And then at the very end, it will return a response. So notice it worked, but we never broke out of the loop. So it keeps running the same answer. So that's interesting. We need a way to check. All right, are we done here effectively? Is it time for us to break out of the loop and provide the
we done here effectively? Is it time for us to break out of the loop and provide the response to the user? How do we do that? Well, if you think about it, if we have no request to run a tool, then we're effectively done, right? If we have this content, we're good to go. So here's what we might do. Right here, we check if we're calling a function, but if not, we echo the response, which is what we were doing here, but then the loop
but if not, we echo the response, which is what we were doing here, but then the loop continues and we do it all over again. So why don't we break out of the loop after we provide the response? Okay, one more time. What time is it? We get the response and it breaks out of the loop. What time will it be in two hours? 1720. It's working. Cool. But it's a little bit messy, right? So I know we have agents and AI these days, but we
it's a little bit messy, right? So I know we have agents and AI these days, but we still want clean code. If an agent has or AI has access to clean code, it's going to produce cleaner code because it doesn't have to sift through a bunch of junk to figure out the solution for everything. So with that in mind, how can we refactor this a little bit? Well, the first thing to understand is that a response can run multiple functions, not
Supporting Multiple Tools14:45
Well, the first thing to understand is that a response can run multiple functions, not just one. And here's how I'm going to illustrate that. Let's imagine you introduce a new tool. I'm going to paste this one in. Here we go. Again, don't stress over the parameter list. You grab it from the documentation and you're good to go. So we now have another tool that we're offering the agent the ability to read a file. So in this case, we need
tool that we're offering the agent the ability to read a file. So in this case, we need to accept parameters, right? We need to give the agent away to read the file and then the path to the file like package.json, right? It needs a way to send through the name of the file that it wants us to read. So we're going to accept that as a parameter. Here's a property. Path is the path to the file. These are the required fields, which means you
a property. Path is the path to the file. These are the required fields, which means you can't run this function if you don't give me a path. Additional property specifies, like, is it okay if the agent passes properties that we didn't declare here or don't require? No, you can't give us additional properties from what we've declared. And also strict is going to turn on structured output. It has to conform to what we've specified here.
strict is going to turn on structured output. It has to conform to what we've specified here. Cool. So now we are giving the agent access to two tools, one to fetch the current time, and then one that can read a file matching the given path. So check this out. If we scroll back up, now yeah, you see where it gets messy. We have to do this is stuff you do in your early days of programming where you don't know how to refactor. You just add another one
early days of programming where you don't know how to refactor. You just add another one here. All right. Well, if the call is read file. Okay, so this is going to be super primitive. Also, I'm not going to do error handling. It doesn't really matter here. In terms of grabbing the argument, let me show you this real quick. Let's dump our call. And give this run, P2B artisan agent, what I'm sorry, read the contents of package.json. I'm going to
run, P2B artisan agent, what I'm sorry, read the contents of package.json. I'm going to copy that because we will run this a few times. And we get a failure. That's okay, though. I want you to notice that now we have arguments, which is an encoded list of the arguments that the AI wants to run for the tool. So it's basically saying, hey, can you run this function, run this tool? The tool I care about is read file. And I want to send through the
function, run this tool? The tool I care about is read file. And I want to send through the path of package.json. So let me show you how to grab this real quick. We could say call arguments. Now we will have yet this right here, we need to decode it. So I could say Jason decode, that will now give us an object. And then on that object, we want to grab the path. So let's see if that's right. Read the contents. And again, it's going to fail. But now we have package.json.
if that's right. Read the contents. And again, it's going to fail. But now we have package.json. So here's what we could do. Let's run this through base path, because it is relative to our project root. And now we're saying base path package.json. It will read that into a string, get the output and append it. All right, and then it continues the loop. Read the contents of package.json. And there we go. This is it. It works. So cool, right? But now here's what I
package.json. And there we go. This is it. It works. So cool, right? But now here's what I want you to pay attention to. So right up here, let's once again dump the response. What if we instead said read the contents of package.json and composer.json. How is it going to do that? Well, let's see what the initial response is. It's going to fail, and we would expect that. So the initial response is not just a single item. It now is two items. And
expect that. So the initial response is not just a single item. It now is two items. And notice it doesn't contain a text response for me yet, because it doesn't have a text response. Instead, it wants to run two tools. And that makes sense, right? It's going to say, all right, well, I need to read package.json. So I'm going to ask the application to run that tool. And then I'm also going to ask the application to run this tool. So now this is
that tool. And then I'm also going to ask the application to run this tool. So now this is super, super important. We know now that the AI can respond with a request to run multiple tools. We can't just assume that it's one at a time, run this tool, then provide a response, run that tool. It doesn't do that. It can do it all in a single request, which means like we 've been saying this whole time, this is incredibly naive. And we instead need to use a
Refactoring for Multiple Calls19:06
've been saying this whole time, this is incredibly naive. And we instead need to use a loop. Let's do that now. All right, so I'm going to switch layouts. And why don't we start right here? And the first thing I want to do now is say, do we have any function calls at all? Because really, that's kind of our metric. If there's no request to run tools, then we can assume, yep, we have some text content that we can echo to the user
tools, then we can assume, yep, we have some text content that we can echo to the user . So why don't we say, hmm, we need to review the output and gather all output items where the type is function call. Okay, let's use layer field collections for this, I think. Collect response output. This sounds like a job for filter. Give me it where the items together, we're doing this together where the items type is function call. All right, so now this is
we're doing this together where the items type is function call. All right, so now this is going to give us our function calls. Now what's cool about using a collection is we could say now, well, if we don't have any, so if function calls is empty, in that case, yeah, it's done, it's ready to provide a response. So now we can effectively do this, bring this all the way to the top. And again, I will remind you, even this is naive, because there
the way to the top. And again, I will remind you, even this is naive, because there could be more here. So we would need to extract all of the text, but it's okay for now. Next, we're going to loop over the function calls. So I can do it like this. Like so, each call is going to be an array. Okay, so now I can get rid of this. Let's see what we got here. Yeah, let's get rid of that wrap. That move this up. Like so, this line is no longer
Yeah, let's get rid of that wrap. That move this up. Like so, this line is no longer needed because it receives the call. And yeah, I think that looks good. All right, so what have we done here? Well, we fetched all function calls or two calls, really. If we don't have any, then we can break. Otherwise, we need to loop over all of the function calls and run them. So for each one, yeah, we just check, are we doing current time, then
calls and run them. So for each one, yeah, we just check, are we doing current time, then do it like this? Are we reading a file, then do it like this? PHP artisan agent, read the contents of those two files and... Oh, it fails. Attempting to read property, type on array. Let's see, line 47. Sorry. It's not a property. We'll do it as a key. All right, cross our fingers. Let's see if it works. Yes, it does. Okay, cool. So it has the contents of
fingers. Let's see if it works. Yes, it does. Okay, cool. So it has the contents of composer.json as well as package.json. So kind of cool, right? Think about it. We now have given our agent a superpower. We've given it the ability to read a file. And we've also given it an inner dialogue, an inner loop. So this is going to be confusing for you to remember. Don't forget the outer loop is your conversation with the AI. The inner loop is the inner dialogue. That's how I
loop is your conversation with the AI. The inner loop is the inner dialogue. That's how I think of it for the AI. And this is where it can run tools sequentially. It can do potentially 100 things one after the other. That's the point of an agent. What do I do next? Read this file. All right, I read the file. What do I do next? Update the file. What do I do next? Tell the user that I updated the file. All right, that's the loop there. And that's the inner dialogue. Next, we
updated the file. All right, that's the loop there. And that's the inner dialogue. Next, we updated our code to allow for running any number of functions. But if there are no remaining functions, that effectively means we're all set to go here. So the last thing I'm going to show you before we wrap up here, don't forget, we don't need to make this a little smarter. But the last thing I want to show you is, well, for each function call, it might be useful to
the last thing I want to show you is, well, for each function call, it might be useful to provide some feedback on the console that a certain tool is being run. And because we're in a loop here, maybe we could just say, how about this? Let's add a note. All right, let's just do simple info. Running tool. And we'll paste in the name of the tool, call name. And then actually, why don't we do this? Well, first at the very top, let's make sure that we import info for some
why don't we do this? Well, first at the very top, let's make sure that we import info for some reason, PHP storm doesn't want to import that for me. All right, and then I want to make it look like we're calling a function here. So something like that, is that right? We'll find out in a second. And then I want to JSON encode the calls arguments. I think that's right. Pretty messy. We should probably use print R or sprint F, but let's see if it works. PHP artisan agent.
Pretty messy. We should probably use print R or sprint F, but let's see if it works. PHP artisan agent. And let's extend it, actually. Hey, there. How can I help? Can you help me with some file reading chores? Yeah, tell me which ones I want to read package dot JSON and composer dot JSON. What do they contain? All right. So it's going to run this tool. I didn't get it quite right . But sure enough, we can see it's going to read package dot JSON. And it's going to read composer
. But sure enough, we can see it's going to read package dot JSON. And it's going to read composer dot JSON. It has that. So now it provided the response. And let's see at the top. Yeah, sure. Here's what each file contains. So cool, right? So yeah, I mean, you have to admit, right? This is fun. Or am I just weird? I think this is fun once you figure out the flow. So here's what I want to do in the next episode. Right now, we have these tool handlers effectively one
what I want to do in the next episode. Right now, we have these tool handlers effectively one after the other. That's not that's not very extendable. It's not very scalable. And then we pass them through here. It would be nice if we could extract them into their own classes of sorts. That way we have a tool class that contains its details as well as the corresponding handler. We 'll do that in the next episode.
'll do that in the next episode.
