Sistava

CrewAI Tools: Adding Built In and Custom Agent Tools

How-to — — by Mahmoud Zalt

How to give a CrewAI agent tools: install the tools package, use built ins like SerperDevTool, and write your own with @tool or BaseTool.

An agent without tools can only rearrange what is already in its prompt. Tools are how it reaches your database, your files, a search engine or an internal API, and they are the difference between a crew that writes plausible text and a crew that does work.

This guide covers the built-in tool library, the two documented ways to write your own, and the part most tutorials skip, which is how to describe a tool so the model actually picks it at the right moment.

One version note first. The import path for the tool decorator is not the same everywhere you will read about it: the current documentation shows it under crewai.tools, while the tools repository README shows a shorter path. Confirm which one your installed version accepts before you assume a snippet is broken.

How do you attach a built-in tool to an agent?

Install the tools extra, import the tool class, instantiate it, and pass it in the agent's tools list. The documentation describes tools as attached to agents, with tasks getting access through whichever agent owns them.

The documented tool library includes a web search tool, a website scraper, a website search tool that works in a retrieval style, plus file and directory readers. The tools repository lists a good deal more than the documentation page covers, so browse it before you build something that already exists.

How do you write a quick custom tool?

Use the decorator. You give it a display name, and the function's docstring becomes the description the model reads when deciding whether to call it. That is the whole mechanism, which is why the docstring deserves more care than the code.

Notice three things in that docstring. It states the exact argument format, it says when to reach for the tool rather than only what the tool is, and it names what a miss looks like. An agent that knows what a miss returns will not invent an answer to cover one.

The truncation on the last line matters too. Tool output goes straight back into the prompt, so an unbounded return value is a context window problem waiting to happen. Cap it and say in the docstring that you capped it.

The decorator suits read-only helpers with one or two arguments. Once a tool takes structured input, or you want the framework to reject bad arguments before your code runs, move up to the class form.

When should you subclass BaseTool instead?

Subclass BaseTool when you want validated inputs, persistent state, or custom error handling. The documented shape is a Pydantic model as args_schema, a name, a description, and a _run method carrying the logic.

Swap the dictionary for a real query and this is a production tool. The important habit is the return type: a short, flat string the model can quote. Returning a large nested object forces the model to do parsing work that your Python could have done for free.

Decorated functions and BaseTool instances mix freely in the same list. Note the backstory doing real work there: it tells the agent what to do when a tool fails, which is the difference between a graceful stop and a confident fabrication.

Writing the tool is usually the easy half. The harder half is that a tool is only useful if the model chooses it at the right moment, and that choice is driven entirely by the text you wrote in the name and description. Treat those strings as the interface, not as comments.

What makes a tool description the agent actually uses?

A good description answers when to call it, what to pass, and what comes back, in that order. Independent tutorials on the framework make the same point repeatedly, and it matches how the docstring is consumed: it is the only thing the model knows about your function.

WeakStrong
Looks up invoices.Look up one invoice by reference and return status, amount and due date.
Takes an id.Pass the reference exactly as printed, for example INV-1042.
(nothing about failure)Returns a not-found message when the reference does not exist.
(nothing about when)Call this before answering any question about whether a customer has paid.

Two more habits pay off quickly. Give every tool a narrow job rather than one tool with a mode argument, because a model choosing between three clear options beats a model guessing a mode string. And name tools after the action, not the system, since the agent is picking a verb rather than a vendor.

The documentation also describes a cache_function attribute on a tool, letting you decide from the arguments and the result whether a call should be cached. It is worth setting on anything that hits a paid API with repeatable inputs.

It is worth stepping back at this point to see what the tool layer really is. It is an integration project: authentication, argument shapes, error handling, rate limits and cache policy, one connector at a time, all of it yours to maintain as the upstream APIs move.

If those connectors are the product you are building, that ownership is exactly what you want. If you only wanted the invoice answered, that is the case Sistava handles differently. You hire an AI Employee that arrives with its tools already connected, tell it what to handle in plain English, and it asks you when it is unsure rather than needing a schema for the question.

For anyone staying in the framework, the discipline above is what separates a demo from something you can leave running. Narrow tools, honest descriptions, validated arguments, bounded output and a clear failure string will carry you further than any amount of prompt tuning on the agent itself.

Frequently asked questions

FAQ

How do I create a custom tool in CrewAI?

Two documented ways. Decorate a plain function with @tool("Some Name") and let its docstring serve as the description, which suits small read-only helpers. Or subclass BaseTool with name, description and an args_schema Pydantic model, then put your logic in _run, which suits anything needing validated inputs, state or custom error handling. Both kinds can sit in the same agent's tools list.

What is the import path for CrewAI tools?

The current documentation shows BaseTool and the tool decorator under crewai.tools, while the tools repository README shows a shorter path for the decorator. This is exactly the kind of detail that has moved between versions, so check what your installed version accepts before concluding a snippet is wrong.

Do I attach tools to the agent or to the task?

The tools documentation describes tools as attached to the agent through its tools parameter, with tasks getting access through the agent assigned to them. If you need one task to have narrower access than another, the straightforward approach is a separate agent with a narrower tool list.

Why does my agent ignore the tool I gave it?

Nearly always the description. The name and description are the only information the model has about your function, so a vague description means the tool never looks like the right move. Rewrite it to say when to call the tool, what to pass, and what comes back including the failure case, then run again with verbose on and read which tool it reached for.

Do CrewAI tools need API keys?

The built-in ones that call a third-party service do. The documentation shows a Serper key in the environment for the web search tool, for example. Retrieval-style tools also embed text, and the crew embedder setting defaults to OpenAI, so a project avoiding that provider should configure the embedder as well as the chat model.

How do I stop a tool from being called repeatedly with the same arguments?

The documentation describes a cache_function you can set on a tool, which receives the arguments and the result and returns whether the call should be cached. A common policy is caching successful lookups but not failures, so a transient error does not get frozen in for the rest of the run.

Tools are where an agent framework stops being a text generator and starts touching your systems. Build them narrow, describe them honestly, and give every one of them a sensible answer for the case where the thing you asked for is not there.

Sources