Lesson Description

The "List & Delete" Lesson is part of the full, AI Agents Fundamentals, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Scott demonstrates listing files and directories with optional default paths, formatting results for clarity, and safely deleting files using methods like fs.unlink.

Preview

Transcript from the "List & Delete" Lesson

[00:00:00]
>> Scott Moss:Cool, let's do list files. So description of list files, you'll list all the files and directories in the specified directory path. Right? Specifically, you gotta give me a directory path, not a file path. Input schema. We're here. We will do an object, not a boolean. An object, there we go. And they'll just be a directory. Like so, it'll be a string. Like so. We'll give it a description, and we'll just say the directory path to list the contents of.

[00:01:05]
And then just to be super nice, we could put a default here of current directory if it's not there. That would let the agent know that if a user asked you to list a directory explicitly but didn't tell you a directory, you don't have to ask them to follow up, you can just, the default will take over. It's letting the agent know that, and depending on the quality of the model, it may or may not respect that, but it's just letting you know that you don't need to ask, right?

[00:01:38]
I could also put that in a prompt. Make sure to ask the user, they always give you this, right? You can also do what's called a strict mode in tool calling, where, and by default I think it's on with the AI SDK, where it always has the AI, the LLM always has to fulfill the input schema, and the only way to get something to be optional is to make a union type and then union type is the type you want it to be or null.

[00:02:14]
And then the LLM can just pass in null if it's optional. Otherwise it will pass in a value. Right now, it's assuming that all of these are required, so it's always gonna pass something in. If you don't want that to be the case, you do that. And then because it is on strict mode, it won't pass in any additional properties that you didn't ask for. If you took off strict mode, it will fulfill your schema and it might also pass in some additional properties that you didn't even ask for, which, how would you know that they were there and how would you use them?

[00:02:40]
So you probably don't want that either. So strict mode is usually on by default because of that. But yeah, if you're thinking about how do we make something optional, you would have to do a union. And Zod, it's like there's a union, there's a union that takes an array of different types, like so. And then it's like z.literal null, right, you do something like that to make it a union.

[00:03:24]
Cool. OK, and execute. This one is not too crazy. I mean, none of these really are, to be honest, so, you know, it's probably better off like npm installing something that just does all this on a higher level than fs, but you know, why not? So here we got the entries. We'll do fs.readdir, which stands for read directory, we pass in the directory, which I did not reference up here, directory.

[00:03:57]
And then we say, with file types, yes, I want to see the file types, that's helpful information for the LLM to see those file types. Imagine if you got two files, one called index.py, index.js. If I didn't see the file types, it'd be very confusing. Items. The entries dot map over each entry. Get the entry. And then we want to get the type, so we're just formatting it so it looks better for the AI.

[00:04:31]
We could just return back the raw results, but you know, let's not do that, so we'll say, hey, is this a directory? IsDirectory, and then we can say, hey, if this is a directory, you know, we use some syntax like this to let you know that this is a directory. We can use some syntax like this to let you know that it's a file. All right, so we'll do that. I forgot to put await there, there we go, and then we can return just a string that says, hey, type, which will be directory or file, you know, a little space here and then we just put the name of it.

[00:05:10]
So it just knows what these are. We're just giving it hints, right? This would make sense to pass to the result of some function that's gonna consume, right? If the results of this were gonna be consumed by another function, we would just return entries and we'd be done cause we need to operate on that structured data. But the LLM needs language. We have to return some language.

[00:05:29]
Just returning some structured data is not that helpful. Returning language though, it is, it understands language, right? So that's what we want to do. So we have that array, and then we just want to return, hey, if there are some things in here, then what we want to do is say items that join into one string, so we're not returning an array. So just put them on one string with a new line in between each one of them.

[00:06:14]
Otherwise, we can just say, you know, directory the name you gave us is empty. Just giving you some hints. Could you interpret that if I just gave you this raw JSON? Probably, but think about all the wasted tokens in that. What if you had 100 files and 100 folders that had hundreds of files and hundreds of folders in them, and we just return that as JSON. Think about all the brackets and colons that are gonna be in there.

[00:06:50]
For what? We can just format this beautifully, just give it this, right? So much better. It's easy language, it's easier to see. And this return could not list the contents in this directory. Here is the Node.js error. You figure it out. Cool. And last one I believe we have is delete file. The most scary one. Well maybe write file is kind of scary too, since it blows out a file, so I guess that was terrifying as well.

[00:07:37]
Description. Or I should call it delete file, there we go. Description would be, I don't know, you just say delete a file at a given path. Oh, yeah, use with caution. It already kind of knows this, the best ones do, but yeah, use with caution as this is irreversible. I spell that right? I'll just say as this is very destructive and cannot be recovered from. Cool. All right, got a description.

[00:08:42]
Need to make our input schema. It's an object, has a path, it's a string. That was a string. Boolean, string. Description, the path to the file you want to delete. And then execute. Got our path here. And yeah, pretty simple. All we have to do is try catch. Like so. And we can just say await fs.unlink, which I believe if path refers to a symbolic link, then the link is removed without affecting the file or directory to which that link refers.

[00:09:22]
If path refers to a file path that is not a symbolic link, the file is deleted. So it's just a safer delete because I believe the delete file or maybe remove, no I guess it is just unlink at this point, but it's just safer because some paths are virtual, as in they are symbolic links to other paths. And the traditional delete file remove file method would not account for that, and you have some, I guess in this case, floating symbolic links, memory leaks, I don't know how that would work on an OS level.

[00:10:04]
Unlink just figures that out for you, so you don't have to do a statement. So we're gonna unlink at the path and then we'll just return a message that says, if we get to this line, then there was no error. Successfully deleted the file at path. Cool. Else, return could not delete that file. Here is the Node.js error. Cool. Nothing crazy. Let's register these tools now. So if you head over to tools index, if you have your datetime thing, it'll still be in there.

[00:10:50]
I don't have it on this branch, so I'm just gonna remove it. And instead I'm just going to import all of our file tools from the files or from yeah, from file.ts, so we have our read, we have our write, we have our list, and we have our delete. I'm just gonna add those here. Read file, write file, list file, and delete file. And sometimes it's helpful just to group these into their own tool set just in case you just wanna bring them over as one pack, like just bring over all the file stuff, right, versus installing all the tools, so we'll do some different exports.

[00:11:26]
So we'll one, we'll export them all individually as well so you can just import them individually from this index without having to go into the file tools file. So that's one thing we'll do. And then like I said, it's kind of just make a tool set, right? So we'll say export const, these are the file tools. So if you just only want to bring in just these tools and no other tools, you could do that.

[00:11:53]
I think the AI SDK does have a concept of tool sets. We've used the type somewhere in here, I remember. I just never use it. I don't know what it does. I'm not sure I knew what to expect, but I kind of had to do a double take because I was just remembering the, it seemed like we've kind of been through all this like list files and things like that, and we did, we made mocks.

[00:12:15]
We made mocks in the single turn eval. We made mocks for all these tools. Gotcha. Yeah, of course, that makes sense. We mocked them out. They didn't do anything yet because I like to, I like to make the evals for things that we will do to get a baseline. And then I like to make the tools, so I know where to start cause I already had an eval. That makes sense. Yeah, it's like TDD.

[00:12:45]
Yeah, EDD. EDD driven. Like is that mean something else? That's a different thing. Yes. If you wanted user intervention, would you try to build it at the tool level or you do it between the steps when it might call the tool? We're gonna do that today, but you can do both, but you should only do it on the steps in between. I personally, you could do it at the tool level, it depends on the abstraction that you want, but I think you should do it at the control plane, which is the loop.

[00:13:11]
I think you want to do it at the loop, yeah. At least in our architecture because you wanna show the user a prompt and I would have to pass all that UI stuff down to a tool so the tool can, in a real, most frameworks or if you really got sophisticated with this, this execute function would also have a context object here and this context object would be like the user's ID, you know, methods that every tool can get access to like here's the database ORM, the stuff that you passed down and it might be some way to alert the UI.

[00:13:42]
Like for instance, what if your tool does a lot of async things, just one tool does a lot of async things and you want to show the UI searching Google, found results, 63 results found, right? All in one tool, so you might pass in something that allows you to push things to the UI so it could show that. But our architecture is not set up that way. So it depends.

Learn Straight from the Experts Who Shape the Modern Web

  • 250+
    In-depth Courses
  • Industry Leading Experts
  • 24
    Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now