KeyError when parsing "./config/agents.yaml" file in a trivial crew configuration

Dear Friends:

I created the following trivial setup to ask this question:

  • Directory structure
user$ tree .
.
├── crew.py
├── config
│   ├── agents.yaml
│   └── tasks.yaml # Intentionally empty for simplicity.
└── tools
    └── my_tools.py

  • ./crew.py
from crewai.project import (CrewBase, agent, task, llm, crew, before_kickoff, after_kickoff,)
from tools.my_tools import some_fantastic_tool

@CrewBase
class FantasticCrew():
    """ Some Fantastic Crew """

    print(f"+{'*'* 42}\n{some_fantastic_tool}\n+{'*'* 42}")
    agents_config = "config/agents.yaml"

FantasticCrew()
  • ./config/agents.yaml
ome_fantastic_agent:
  role: >
    Some fantastic role.

  goal: >
    Some fantastic goal.

  backstory: >
    Some fantastic backstory.

  verbose: True
  allow_delegation: False
  llm: ollama_gemma3_27b_llm
  tools: ['some_fantastic_tool',] # CrewAI @tool decorated functions inside: ./tools/sqlite_tools.py
  • ./tools/my_tools.py
from crewai.tools import tool

@tool("some_fantastic_tool")
def some_fantastic_tool() -> str:
    """
    Performs some fantastic function.
    Returns:
        str: A string containing some fantastic text.
    """
    return "Some fantastic text."

When I run this trivial Crew as shown below, I receive the indicated key_error:

user$ python ./crew.py
+******************************************
name='some_fantastic_tool'
description='Tool Name: some_fantastic_tool
Tool Arguments: {}
Tool Description: Performs some fantastic function.
Returns:
   str: A string containing some fantastic text.
'args_schema=<class 'abc.Some_Fantastic_Tool'>
 description_updated=False
 cache_function=<function BaseTool.<lambda> at 
 0x7f6346fc8b80>
 result_as_answer=False func=<function some_fantastic_tool
 at 0x7f639e749bc0>
+******************************************
Traceback (most recent call last):
  File "/home/nmvega/WORKSPACES.d/AGENTIC.GEN.AI.d/CREWAI.d/ttt.d/./crew.py", line 11, in <module>
    FantasticCrew()
  File "/home/nmvega/WORKSPACES.d/AGENTIC.GEN.AI.d/CREWAI.d/.venv/lib64/python3.12/site-packages/crewai/project/crew_base.py", line 35, in __init__
    self.map_all_agent_variables()
  File "/home/nmvega/WORKSPACES.d/AGENTIC.GEN.AI.d/CREWAI.d/.venv/lib64/python3.12/site-packages/crewai/project/crew_base.py", line 143, in map_all_agent_variables
    self._map_agent_variables(
  File "/home/nmvega/WORKSPACES.d/AGENTIC.GEN.AI.d/CREWAI.d/.venv/lib64/python3.12/site-packages/crewai/project/crew_base.py", line 171, in _map_agent_variables
    tool_functions[tool]() for tool in tools
    ~~~~~~~~~~~~~~^^^^^^
KeyError: 'some_fantastic_tool'

What am I missing? Thank you. :blush:

Your problem is in how you have named your tool in agents.yaml. Reformat the agent.yaml file to be something like

some_fantastic_agent:
  role: >
    Some fantastic role.

  goal: >
    Some fantastic goal.

  backstory: >
    Some fantastic backstory.

  verbose: True
  allow_delegation: False
  llm: ollama_gemma3_27b_llm
  tools:
    - some_fantastic_tool

But you need to instantiate the tool in crew.py first eg

@tool
def some_fantastic_tool(self):
    return some_fantastic_tool()

This should work

1 Like

Hi again, and thank you for reading and replying.

  • Yes, I had already tried all four of these variants, which I forgot to mention. And, by the way, the exception raised when I specify tools in ./config/agents.yaml and/or ./config/tasks.yaml:
tools: [] # The only this that works.
tools:
  - 'some_fantastic_tool' # Excepts
  - some_fantastic_tool   # Excepts

tools: ['some_fantastic_tool',]  # Excepts
tools: [some_fantastic_tool,]    # Excepts
  • Separately, instantiation of the tool within the Crew class (i.e., adding what you mentioned) emits the following error:
 raise ValueError("Function must have a docstring")

That error makes sense, so I provided a docstring as well to satisfy the exception. However, fixing that only gets me back to square one: key_error. Also, supplying a docstring there begins to defeat the purpose of my having tools neatly factored-out and defined in ./tools/my_tools.py; so I prefer not to do that.

Honestly, I’ve never been able to overcome this key_error once I populate tools: in either ./config/agents.yaml or ./config/tasks.yaml as part of a Crew configuration. That’s what this experiment and this post is for: To figure out why this keeps happening.

I’ll now adapt my initial trivial configuration to show what does work. I annotated the entries below with running comments:

  • Directory structure:
user$ tree .
.
├── crew.py
├── config
│   ├── agents.yaml
│   └── tasks.yaml # Added so CrewAI doesn't complain about it missing, but otherwise unused.
└── tools
    └── my_tools.py
  • ./crew.py (tools are specified in Agents(...), not in YAML)
from crewai import Agent, Crew, Process, Task
from crewai.project import (CrewBase, agent, task, llm, crew, before_kickoff, after_kickoff,)
from crewai.tools import tool
from tools.my_tools import some_fantastic_tool

@CrewBase
class FantasticCrew():
    """ Some Fantastic Crew """

    agents_config = "config/agents.yaml"
   #tasks_config = "config/tasks.yaml" # Unused in this skeleton configuration.

    @agent
    def some_fantastic_agent(self) -> Agent:
        ollama_llm_config = {"model": "ollama/phi4:latest", "api_key": 'ollama'}
        agent_obj = Agent(config=self.agents_config['some_fantastic_agent'], 
                          tools=[some_fantastic_tool,],)
  • ./config/agents.yaml (tools left unspecified b/c Excepts)
some_fantastic_agent:
  role: >
    Some fantastic role

  goal: >
    Some fantastic goal

  backstory: >
    Some fantastic backstory

  verbose: True
  allow_delegation: False
  llm: ollama/phi4:latest

  tools: []
 #tools: # These variations result in a "key_error".
 #  -  some_fantastic_tool
 #  - 'some_fantastic_tool'
 #tools: [ some_fantastic_tool, ]
 #tools: ['some_fantastic_tool',]
  • ./config/tasks.yaml (tools left unspecified b/c Excepts)
    This file is added to my original trivial configuration only so CrewAI doesn’t complain about one not being supplied. It is otherwise unused and commented out in ./crew.py above. If uncommented, it too raises a key_error exception:
some_fantastic_task:
  description: >
    Adding this tasks.yaml file so that instantiation of FantasticCrew()
    doesn't complain about the absense of this file. This file is otherwise
    unused in this skeleton configuration.

  expected_output: >
    Some fantastic output.

  agent: some_fantastic_agent

  tools: []
 #tools: # These variations result in a "key_error".
 #  -  some_fantastic_tool
 #  - 'some_fantastic_tool'
 #tools: [ some_fantastic_tool, ]
 #tools: ['some_fantastic_tool',]
  • ./tools/my_tools.py (same as before)
from crewai.tools import tool

@tool("some_fantastic_tool")
def some_fantastic_tool() -> str:
    """
    Performs some fantastic function.
    Returns:
        str: A string containing some fantastic text.
    """
    return "Some fantastic text."
  • When I run this slightly modified trivial / skeletal Crew, no key_error is emitted (but sadly, also not relying on the YAML files for tool specifications):
user$ python crew.py
user$ echo $?
0

Ultimately, my goal is determining why this keeps happening so I that can get closer to the configuration in the OP, and not specify anything to the Agent(…) except the agents.yaml config.

Thank you.

To try and help clear up your doubts, I thought it’d be best to put together a small, working example. This way, you get a chance to test it, get familiar with it, and gradually adapt it to your own needs. Sound good?

The example below implements two tools, and the agent and task functionalities are defined solely in their corresponding YAML files, allowing you to set up the Agent and Task using just the config attribute. To test it, I used Qwen2.5 3B running via Ollama. It’s funny to see it use the tools without seeming to care at all about the answers it gets. Hope you have better luck with your model! :sweat_smile:

Directory structure:

platypus_crew/
├── config/
│   ├── agents.yaml
│   └── tasks.yaml
├── tools/
│   └── my_tools.py
└── crew.py

agents.yaml file:

platypus_poet_agent:
  role: >
    Concise Nature Poet, skilled in brief verse about unique fauna.
  goal: >
    Write a short, evocative poem about a platypus, strictly adhering to
    specified length constraints (250-300 chars, 45-70 words).
  backstory: >
    An expert poet specializing in capturing the essence of unique animals
    within tight word and character limits. Fascinated by the paradox of the
    platypus, you craft verses that are both descriptive and brief, always
    mindful of the requested form.
  verbose: True
  llm: ollama_llm
  tools:
    - word_counter_tool
    - character_counter_tool

tasks.yaml file:

write_platypus_poem_task:
  description: >
    Compose a poem celebrating the unique nature of the platypus. The final
    poem *must* be between 250 and 300 characters long (inclusive). It
    *must* also contain between 45 and 70 words (inclusive). Focus on its
    distinctive features within the constraints.
  expected_output: >
    A single, well-formatted poem about a platypus. The poem's text must
    strictly satisfy the length requirements: Character count: 250-300
    characters. Word count: 45-70 words. No extra text.
  agent: platypus_poet_agent

my_tools.py file:

from crewai.tools import tool

@tool("Word Counter Tool")
def word_counter(text: str) -> str:
    """
    Counts the number of words in the provided text string.
    Use this tool when you need to know how many words are in a piece of text.
    Input must be a single string.
    """

    words = text.split()
    word_count = len(words)
    return f"Your text is {word_count} words long."

@tool("Character Counter Tool")
def character_counter(text: str) -> str:
    """
    Counts the total number of characters (including spaces and punctuation)
    in the provided text string. Use this tool when you need to know the
    exact length of a string. Input must be a single string.
    """

    char_count = len(text)
    return f"Your text is {char_count} characters long."

crew.py file:

from crewai import Agent, Crew, Task, Process, LLM
from crewai.project import CrewBase, agent, task, crew, tool, llm
from crewai.tools import BaseTool
from tools.my_tools import word_counter, character_counter

@CrewBase
class PlatypusCrew:
    agents_config = "config/agents.yaml"
    tasks_config = "config/tasks.yaml"
    
    @agent
    def platypus_poet_agent(self) -> Agent:
        return Agent(
            config=self.agents_config["platypus_poet_agent"],
        )
    
    @task
    def write_platypus_poem_task(self) -> Task:
        return Task(
            config=self.tasks_config["write_platypus_poem_task"],
        )
    
    @tool
    def word_counter_tool(self) -> BaseTool:
        return word_counter
    
    @tool
    def character_counter_tool(self) -> BaseTool:
        return character_counter
    
    @llm
    def ollama_llm(self) -> LLM:
        return LLM(
            model="ollama/qwen2.5:3b",
            base_url="http://localhost:11434"
        )
    
    @crew
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,
            tasks=self.tasks,
            process=Process.sequential,
            verbose=True,
        )

crew = PlatypusCrew().crew()
crew_output = crew.kickoff()

print(f"\n[ 🧠 POEM ]\n\n{crew_output.raw}\n")

Run the Crew, and if you still have any questions, bring them here. That way, it’s easier for everyone to understand the starting point.

1 Like

Thank you. I’m looking at it right now (and visually performing diffs) to see where I went astray. BRB. :blush:

I create a repo that shows you how to use tools in yaml files

It shows both for subclassing BaseTool and using the tool Decorator.

It works for me using crewai run, try it out.

1 Like

Thank you. Looking at that shortly also. :blush:

Hi @zinyando @Max_Moura

Again, thank you for your participation and the valuable code examples you provided.

I tried @Max_Moura’s example and it worked as expected. I would have integrated @zinyando’s example also, but I’ve never had the same problem when subclassing BaseTool.

I think this issue is something @zinyando mentioned earlier and @Max_Moura expanded on - which is that I’ve been missing this final step in all of my ./crew.py files (probably because most examples author tools right inside ./crew.py itself):

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

:partying_face: Tada! :partying_face:

(@zinyando, I removed your trailing method parenthesis () and added the BaseTool hint, as in @Max_Moura’s example):

I my previous reply, I mentioned adding the above snippet to my OP ./crew.py file, so why did I still receive the key_error? Well, to be sure, this is a very in-progress, incomplete example, and I suspect there’s probably syntactic sugar (and/or meta-programming) that simply expects things to be present, and not absent. If you ran my OP example, even after adding the above missing @tool snippet, I think you’d experience the key_error also.

Finally, you might wonder how I got to this in-progress, incomplete example in the first place. Well, the scenario arises because, during development - which occurs inside nvim(1) tabs (and less frequently in vsCode or cursor) - I regularly shell-out to perform quick checkpoint runs to validate that things are working to-date. So for instance, I may check that Python imports work correctly; confirm class syntax; ensure correct YAML file structure, and so on. One such checkpoint led me to test the incomplete in-progress implementation I’ve shown. I’ll be sure to add the above snippet now. :grinning_face_with_smiling_eyes:

One quick question:

  • When supplying a parameter to the tool decorator - for example @tool("Some Fantastic Tool") - what purpose does it serve? For example, is it added to the docstring? I’ll also check the source on github.

Thank you!`

PS @Max_Moura: To answer you, I used ollama/gemma3:27b :blush: I also continue to use the llm code you provided in our other thread, which leverages a ./config/llms.yaml setup, which I like.