Tool Key error in YAML

I have defined and used the following tool

class JokerToolInput(BaseModel):
    """Topic to get jokes about."""
    argument: str = Field(..., description="Topic to get jokes about, e.g. 'dogs'")
    count: int = Field(
        1, description="Number of jokes to return, default is 1", ge=2, le=10
    )
    
class JokerResponse(BaseModel):
    """Response from the Joker API."""
    response: list[str] = Field(..., description="JSON string with the joke data")

class JokerTool(BaseTool):
    name: str = "JokerTool"
    description: str = (
        "Use this tool to get a list of jokes from the Joker API given a topic."
    )
    args_schema: Type[BaseModel] = JokerToolInput

    def _run(self, argument: str, count: int = 1) -> JokerResponse:
        try:
            # JokesAPI endpoint - filters out explicit content
            url = "https://v2.jokeapi.dev/joke/Any"
            params = {
                "blacklistFlags": ("nsfw,religious,political,racist,"
                                "sexist,explicit"),
                "type": "single",  # Get single-part jokes for simplicity
                "amount": min(count, 10),  # API limits to 10 jokes max
                "contains": argument  # Search for jokes containing the topic
            }
            
            response = requests.get(url, params=params, timeout=10)
            response.raise_for_status()
            
            joke_data = response.json()
            joke_list = [joke['joke'] for joke in joke_data['jokes']]
            
        except Exception as e:
            return f"Error fetching joke: {e}"
        
        #return json.dumps({"response": json.dumps(joke_data)})
        return joke_list

This is already working when passed as a tool to the agent in Python code. However I want to reference it in the task definition YAML and no matter what I do it does not find the key.

Here is the YAML. I have tried JokerTool and “JokerTool”

joke_task:
  description: >
    Fetch a list of jokes related to the topic from the previous task using the Joker API.
    Make sure the jokes are appropriate and relevant to the topic.
  expected_output: JokerResponse
  tools:
   - "JokerTool"
  agent: clown

I even tried adding a @tool definition function in python but that did not solve the problem so I removed it.

The consistent error I get it given below.

So far in general anything defined in Python, I cannot access in the YAML

Running the Crew
Traceback (most recent call last):
  File "/home/mlokhandwala/linkedin-poster/poster/src/poster/main.py", line 25, in run
    Poster().crew().kickoff(inputs=inputs)
    ^^^^^^^^
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 37, in __init__
    self.map_all_task_variables()
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 234, in map_all_task_variables
    self._map_task_variables(
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 262, in _map_task_variables
    self.tasks_config[task_name]["tools"] = [
                                            ^
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 263, in <listcomp>
    tool_functions[tool]() for tool in tools
    ~~~~~~~~~~~~~~^^^^^^
KeyError: 'JokerTool'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/bin/run_crew", line 10, in <module>
    sys.exit(run())
             ^^^^^
  File "/home/mlokhandwala/linkedin-poster/poster/src/poster/main.py", line 27, in run
    raise Exception(f"An error occurred while running the crew: {e}")
Exception: An error occurred while running the crew: 'JokerTool'
from crewai import Agent, Crew, Task, Process
from crewai.project import CrewBase, agent, task, crew, tool, llm
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.tools import BaseTool
from typing import List

@CrewBase
class YourCrewName:
    """Description of your crew"""

    agents: List[BaseAgent]
    tasks: List[Task]

    agents_config = 'config/agents.yaml'
    tasks_config = 'config/tasks.yaml'

    @agent
    def clown(self) -> Agent:
        return Agent(
            config=self.agents_config['clown'], # type: ignore[index]
            verbose=True
        )

    @task
    def joke_task(self) -> Task:
        return Task(
            config=self.tasks_config['joke_task'] # type: ignore[index]
        )

    #
    # 👇👇👇
    #
    @tool
    def JokerTool(self) -> BaseTool:
        return JokerTool()

    @crew
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,
            tasks=self.tasks,
            process=Process.sequential,
            verbose=True,
        )

See the technical discussion here.

No Luck, also I had tried this before, including returning JokerTool (not BaseTool)

    @tool
    def JokerTool(self) -> BaseTool:
        """JokerTool instance for fetching jokes."""
        return JokerTool()
    
    @task
    def joke_task(self) -> Task:
        return Task(
            config=self.tasks_config['joke_task'], #tools=[JokerTool()], # type: ignore[index]
        )
        
joke_task:
  description: >
    Fetch a list of jokes related to the topic from the previous task using the Joker API.
    Make sure the jokes are appropriate and relevant to the topic. You have three topics to try.
    Try the first topic, if no jokes are found, try the second topic, and if no jokes are found try the third topic.
  expected_output: JokerResponse
  tools:
    - JokerTool
  agent: clown
Running the Crew
Traceback (most recent call last):
  File "/home/mlokhandwala/linkedin-poster/poster/src/poster/main.py", line 25, in run
    Poster().crew().kickoff(inputs=inputs)
    ^^^^^^^^
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 37, in __init__
    self.map_all_task_variables()
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 234, in map_all_task_variables
    self._map_task_variables(
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 262, in _map_task_variables
    self.tasks_config[task_name]["tools"] = [
                                            ^
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 263, in <listcomp>
    tool_functions[tool]() for tool in tools
    ~~~~~~~~~~~~~~^^^^^^
KeyError: 'JokerTool'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/bin/run_crew", line 10, in <module>
    sys.exit(run())
             ^^^^^
  File "/home/mlokhandwala/linkedin-poster/poster/src/poster/main.py", line 27, in run
    raise Exception(f"An error occurred while running the crew: {e}")
Exception: An error occurred while running the crew: 'JokerTool'

Even this fails

    # @tool
    # def JokerTool(self) -> JokerTool:
    #     """JokerTool instance for fetching jokes."""
    #     return JokerTool()
    
    @task
    def joke_task(self) -> Task:
        return Task(
            config=self.tasks_config['joke_task'], tools=[JokerTool()], # type: ignore[index]
        )
        

Only the above code with the following YAML (JokerTool removed) runs

joke_task:
  description: >
    Fetch a list of jokes related to the topic from the previous task using the Joker API.
    Make sure the jokes are appropriate and relevant to the topic. You have three topics to try.
    Try the first topic, if no jokes are found, try the second topic, and if no jokes are found try the third topic.
  expected_output: JokerResponse
  agent: clown

The name of the function/method you wrap with the @tool decorator must match the string you declare in the YAML file. You either need to change the string in your YAML to joker_tool, or rename the function/method to JokerTool, as I did in the code from my response above.


 @tool
    def joker_tool(self) -> BaseTool:
        """JokerTool instance for fetching jokes."""
        return JokerTool()
    

Even tried the above with

joke_task:
  description: >
    Fetch a list of jokes related to the topic from the previous task using the Joker API.
    Make sure the jokes are appropriate and relevant to the topic. You have three topics to try.
    Try the first topic, if no jokes are found, try the second topic, and if no jokes are found try the third topic.
  expected_output: JokerResponse
  tools:
    - joker_tool
  agent: clown

Still failed

Running the Crew
Traceback (most recent call last):
  File "/home/mlokhandwala/linkedin-poster/poster/src/poster/main.py", line 25, in run
    Poster().crew().kickoff(inputs=inputs)
    ^^^^^^^^
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 37, in __init__
    self.map_all_task_variables()
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 234, in map_all_task_variables
    self._map_task_variables(
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 262, in _map_task_variables
    self.tasks_config[task_name]["tools"] = [
                                            ^
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/lib/python3.11/site-packages/crewai/project/crew_base.py", line 263, in <listcomp>
    tool_functions[tool]() for tool in tools
    ~~~~~~~~~~~~~~^^^^^^
KeyError: 'joker_tool'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/mlokhandwala/linkedin-poster/poster/.venv/bin/run_crew", line 10, in <module>
    sys.exit(run())
             ^^^^^
  File "/home/mlokhandwala/linkedin-poster/poster/src/poster/main.py", line 27, in run
    raise Exception(f"An error occurred while running the crew: {e}")
Exception: An error occurred while running the crew: 'joker_tool'

Alright, since this thread is getting huge, I’m going to try and reproduce your error. It’s possible something has changed under the hood in CrewAI in the two months since I last had to dig into this part of the code. I’ll get back to you with a definitive, working version.

1 Like

Directory structure:

crewai_joke/
├── config/
│   ├── agents.yaml
│   └── tasks.yaml
└── main.py

config/agents.yaml:

clown:
  role: >
    You're the former director of a circus that went bankrupt
    due to lack of audience
  goal: >
    Make at least one person laugh
  backstory: >
    Your circus went bankrupt because of unfunny jokes.
    It's time to get back on your feet!
  tools:
    - joker_tool
  llm: funny_llm
  verbose: true

config/tasks.yaml:

joke_task:
  description: >
    Fetch a list of jokes related to the topic: {topic}.
    Make sure the jokes are appropriate and relevant to the topic.
  expected_output: >
    The best joke EVER!
  agent: clown

main.py:

from crewai import Agent, Crew, Task, LLM, Process
from crewai.project import CrewBase, agent, task, crew, tool, llm
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.tools import BaseTool
from typing import Type, List
from pydantic import BaseModel, Field
import os

os.environ["GEMINI_API_KEY"] = "YOUR_KEY"

class JokerToolInput(BaseModel):
    """Topic to get jokes about."""
    argument: str = Field(..., description="Topic to get jokes about, e.g. 'dogs'")
    count: int = Field(
        1, description="Number of jokes to return, default is 1", ge=2, le=10
    )
    
class JokerResponse(BaseModel):
    """Response from the Joker API."""
    response: list[str] = Field(..., description="JSON string with the joke data")

class JokerTool(BaseTool):
    name: str = "JokerTool"
    description: str = (
        "Use this tool to get a list of jokes from the Joker API given a topic."
    )
    args_schema: Type[BaseModel] = JokerToolInput

    def _run(self, argument: str, count: int = 1) -> str:  # You're talking to an LLM. Return string.
        worst_joke_ever = "I know someone who talks like an owl."

        return worst_joke_ever

@CrewBase
class CircusCrew:
    """Get ready to laugh"""

    agents: List[BaseAgent]
    tasks: List[Task]

    agents_config = 'config/agents.yaml'
    tasks_config = 'config/tasks.yaml'

    @agent
    def clown(self) -> Agent:
        return Agent(
            config=self.agents_config['clown'], # type: ignore[index]
            verbose=True
        )

    @task
    def joke_task(self) -> Task:
        return Task(
            config=self.tasks_config['joke_task'] # type: ignore[index]
        )

    @tool
    def joker_tool(self) -> BaseTool:
        return JokerTool()

    @llm
    def funny_llm(self) -> LLM:
        return LLM(
            model='gemini/gemini-2.5-flash',
            temperature=1.0,
            max_tokens=512,
        )

    @crew
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,
            tasks=self.tasks,
            process=Process.sequential,
            verbose=True,
        )

print(
    CircusCrew().crew().kickoff(
        inputs={"topic": "Drunk dogs"}
    )
)

Ouput:

I know someone who talks like an owl.

Hi @maxmoura the issue for me was that there are TWO tool decorators!

  1. is in crewai.project
  2. is in crewai.tools

Both are decorators to mark a function as tool.

I was using the second one.

CrewAI is nice, but really need more thorough documentation.

:frowning:

1 Like

@maxmoura wanted to bring to your attention that the documentation refers to the crewai.tools

Please see if you can find or explain when either one has to be used.

First and foremost, your point is completely valid.

CrewAI defines two distinct decorators, both named @tool. This is a naming collision that violates the Principle of Least Astonishment (a name should not behave in a surprising way) and also PEP 20, aka The Zen of Python: “There should be one—and preferably only one—obvious way to do it.”

The @tool decorator you get when you import from crewai.project:

from crewai.project import CrewBase, tool

This decorator is only functional when used on a method within a class that has been decorated with @CrewBase. Its purpose is to mark or label a class method as a provider of a tool. It doesn’t create the tool itself. It simply tells the CrewBase system, “Hey, if you need a tool, call this method, and it will give you one.” This is how, when your agents.yaml file is parsed, it’s able to link a string identifier to the actual tool your method returns.

The @tool decorator you get when you import from crewai.tools:

from crewai.tools import tool

This one is a factory. It takes a regular Python function and, through inspection of its name, docstring, and type hints, constructs a full-fledged tool (which inherits from BaseTool). It’s used to quickly convert any standalone function into a usable tool for an Agent.tools parameter.

As a local workaround to manage this ambiguity, you can use more descriptive aliases (my naming suggestions aren’t the most creative, but I’m sure you can do better):

from crewai.project import tool as tool_provider
from crewai.tools import tool as function_tool

This way, you can use @tool_provider to decorate methods inside a CrewBase class and @function_tool to decorate functions that actually implement your tools.

And if you feel strongly that this is something the CrewAI developers should address, I’d encourage you to open an issue about it. Best of luck in getting a response.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.