I’ve been trying to build an agent that has access to multiple tools. and I want agent to call these tools based on exact requirement in prompt.
I am passing a list in the inputs while kickoff, and then also mentioning this list in the agent goal and task description. I am also explicitly mentioning if a value is present, then it should call relevant tool. I am not sure this is a correct way to do it. But via this way, agent still only calls one tool in a run.
To explicitly call these tools, ChatGPT CrewAI assistant is telling me to override Task, and loop through tools to execute manually. But I don’t see this as a good way, because it is very rigid way of doing it. I want the agent to follow the config and call tools accordingly
Can anyone help me with this? Does CrewAI support this kind of functionality?
The agent should be able to call all the tools it deems relevant to perform the task it’s been given. If it’s not using all the tasks then it means it thinks some of them are not needed.
You are on the right track, being explicit in the tools available to the agent and maybe adding the steps in how to perform the task might help. There is no need to create a loop like you mentioned.
Are you using custom tools? Make sure the tool names, descriptions and inputs are clearly defined so that the agent knows how and when to use them.
In my usecase, I have an agent, and that agent has been explicitly told that it should gather as much as information as it can. from source 1, source 2, source 3.
These sources I don’t know from start, so I am passing them as input, and in that I am telling agent that if source 1 is found, call tool for Source 1, if source 2 then Source 2 tool. Likewise there can be n number of tools. and Agent should gather the data to be processed further.
and yes, I am creating custom tool and with proper description. but still agent is only calling 1 tool in an execution. and the verbose output too is not helping me much.
@jaimish00, I’m afraid it’s unlikely anyone can help you with such a vague description. I’d suggest you provide a clearer example of your use case, either in plain English or (preferably) in a simplified version of your code with a concrete (though perhaps synthetic) example.
Sure, let me share some insights on what I am trying to achieve.
Agent :
news_agent:
role: News Fetcher
goal: >
Retrieve information from ALL sources based
on the ID {id} and list of sources {sources_list}.
backstory: >
An experienced Journalist who always retrieves news from all available sources.
Your critical responsibility is to:
1. Check for all available sources_list and gather news information
2. For EACH source found, call the corresponding tool:
- For EACH "SOURCE_1" entry, call Source1Tool with the id
- For EACH "SOURCE_2" entry, call Source2Tool with the id
3. NEVER skip any source - you must call EVERY tool that corresponds to an entry in sources_list
4. Combine all news from all tools into a comprehensive collection
Task :
news_scouring_task:
description: >
IMPORTANT: Collect news from ALL sources listed in sources_list.
You MUST use EVERY tool corresponding to the source in sources.
Step 1: Check if "SOURCE_1" is in sources_list.
- If yes, you MUST call Source1Tool with the id.
Step 2: Check if "SOURCE_2" is in sources_list.
- If yes, you MUST call Source2Tool with the id.
Step 3: Repeat for any other sources.
Step 4: Combine ALL results from ALL used tools and return them.
VERIFICATION: Before completing, verify that you have called EVERY tool that corresponds to an entry in sources_list.
expected_output: >
Combined news from ALL sources listed in the input
agent: news_agent
Here I am expected to gather news from all sources and pass it to next agent. Every tool here is a custom tool created to fetch from different sources.
For example this is one of the tool,
class Source1Tool(BaseTool):
name: str = "Source 1 Tool"
description: str = (
"Query news source from Source 1 if it is one of the available source"
)
args_schema: Type[BaseModel] = Source1ToolInput
Another detail I wanted to add is that I am overriding crewai’s Task in order to push audit details to my db, before starting and ending each tasks.
def execute_sync(
self,
agent: Optional[BaseAgent] = None,
context: Optional[str] = None,
tools: Optional[List[BaseTool]] = None,
) -> TaskOutput:
# Push updates to DB
output = super().execute_sync(agent, context, tools=tools)
# Push updates to DB
return output
Even if I provide multiple sources via list ["SOURCE_1, “SOURCE_2”], agent only calls one tool. and I think only first tool which is defined in order.
Let me know if any more details are needed,
From what I understand, you’re looking to generate a news report from multiple sources about someone or something identified by an id. There could be several sources, and this information (about which sources are available) is dynamic. So, you intend to pass an id and a sources_list with the sources that should be checked to ultimately generate a report about that id. Did I get that right?
If that’s what you’re trying to do, it’s a standard implementation. Oh, and as for doing something before or after each execution, you can use the @before_kickoff and @after_kickoff decorators. Here’s a simplified and functional implementation of what I understood:
from crewai import Agent, Crew, Task, Process, LLM
from crewai.project import CrewBase, agent, task, crew, before_kickoff, after_kickoff
from crewai.tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field
class SourceToolsInput(BaseModel):
"""Input schema for SourceATool."""
id: str = Field(..., description="Id to fetch news for.")
class SourceATool(BaseTool):
name: str = "Source A Tool"
description: str = "Fetch news from Source A for the given Id"
args_schema: Type[BaseModel] = SourceToolsInput
def _run(self, id: str) -> str:
return f"{id} is a very funny guy"
class SourceBTool(BaseTool):
name: str = "Source B Tool"
description: str = "Fetch news from Source B for the given Id"
args_schema: Type[BaseModel] = SourceToolsInput
def _run(self, id: str) -> str:
return f"{id} is a hard-working guy"
@CrewBase
class NewsCrew:
"""Retrieves News from many sources"""
@before_kickoff
def push_audit_details_before(self, inputs):
"""This function will run before the crew starts."""
print("\npush_audit_details_before\n")
return inputs
@after_kickoff
def push_audit_details_after(self, output):
"""This function will run after the crew finishes."""
print("\npush_audit_details_after\n")
return output
@agent
def news_agent(self) -> Agent:
"""Defines the news agent."""
return Agent(
role="News Fetcher",
goal="Gather lots of information",
backstory="An experienced Journalist who retrieves news from available sources",
verbose=True,
tools=[SourceATool(), SourceBTool()]
)
@task
def news_scouring_task(self) -> Task:
"""Defines the task to collect news from multiple sources."""
return Task(
description=(
"Collect news from all sources listed below based on the given id.\n"
"Each listed source has an associated tool and you must call each tool "
"listed for the given id.\n"
"- id: {id}\n"
"- Sources: {sources_list}\n"
"Then combine all outputs from all used tools."
),
expected_output="A 200-character report summarizing all news about {id}.",
agent=self.news_agent()
)
@crew
def crew(self) -> Crew:
"""Defines and configures the crew."""
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
)
crew = NewsCrew().crew()
result = crew.kickoff(
inputs={
"id": "Max Moura",
"sources_list": ["source_a", "source_b"]
}
)
print(f"\nFinal Output: {result}\n")
About the multiple sources - yes, you got that right. I am trying to fetch data from multiple sources, which will eventually be passed as a dynamic value from input. But the issue is that the Agent is only calling one tool, and I am unable to get it working. It is not calling all the tools. That is why I was confused about this behavior, and that is why I have explicitly defined everything in my goal and description.
Additionally, before_kickoff and after_kickoff are not helpful for me because I am trying to capture the state of each task. CrewAI does support task_callback, but it only gets called after a task is completed. A couple of requests have been filed on GitHub requesting the same feature—having granular callback controls over each task—but there has been no progress. That is why the only option I could think of was to override the base Task.
I am still facing the issue, and in this case, the GPT assistant eventually tells me to call the tools manually within the overridden task.
Regarding your agent calling multiple tools based on the parameters the task receives, I hope the working example I posted helped you adapt it to your use case. In the example I gave, you create as many new tools as needed, then pass all the available tools to the tools parameter of your agent. Then you just need to state, clearly and simply, in the description of your task, which tools should be used for that id.
As for fine-grained control of your tasks, if you don’t need to stop the task execution, I believe an event listener is more suitable, as it’s less intrusive than overriding the base Task class. There can be many event listeners on crewAI’s event bus. For example, AgentOps implements its own event listener on the same bus.
So if you only need to be notified every time a task starts or completes, I propose the following functional code that listens to TaskStartedEvent, TaskCompletedEvent and TaskFailedEvent events. I have tested it together with the previous code, and everything ran smoothly. I hope it helps.
from crewai.utilities.events.base_event_listener import BaseEventListener
from crewai.utilities.events.task_events import (
TaskCompletedEvent,
TaskFailedEvent,
TaskStartedEvent,
)
class MyOwnEventListener(BaseEventListener):
def __init__(self):
super().__init__()
def setup_listeners(self, crewai_event_bus):
@crewai_event_bus.on(TaskStartedEvent)
def on_task_started(source, event: TaskStartedEvent):
print(f"\nReceived 'TaskStartedEvent' with 'source': {source}\n")
@crewai_event_bus.on(TaskCompletedEvent)
def on_task_completed(source, event: TaskCompletedEvent):
print(f"\nReceived 'TaskCompletedEvent' with 'source': {source}\n")
@crewai_event_bus.on(TaskFailedEvent)
def on_task_failed(source, event: TaskFailedEvent):
print(f"\nReceived 'TaskFailedEvent' with 'source': {source}\n")
my_event_listener = MyOwnEventListener()
I tried with exact prompt you provided for the agent and task, and it is still not working for me. It is still calling only one tool in a run. My original task description was also clear and I stated the agent to call multiple tools
description: >
IMPORTANT: Collect news from ALL sources listed in sources_list.
You MUST use EVERY tool corresponding to the source in sources.
Step 1: Check if "SOURCE_1" is in sources_list.
- If yes, you MUST call Source1Tool with the id.
Step 2: Check if "SOURCE_2" is in sources_list.
- If yes, you MUST call Source2Tool with the id.
Step 3: Repeat for any other sources.
Step 4: Combine ALL results from ALL used tools and return them.
VERIFICATION: Before completing, verify that you have called EVERY tool that corresponds to an entry in sources_list.
For better planning, I enabled planning too, and in the verbose output when the task is getting started is like this
[2025-03-26 11:03:07][📋 TASK STARTED: COLLECT NEWS FROM ALL SOURCES LISTED BELOW BASED ON THE GIVEN ID. EACH LISTED SOURCE HAS AN ASSOCIATED TOOL AND YOU MUST CALL EACH TOOL LISTED FOR THE GIVEN ID. RETURN NEWS, AND IF ANY CONFIGURATION IS NOT DONE CORRECTLY OR ANY ERROR OCCURS, RAISE PROPER EXCEPTION. - COMPANY_ID: D17C6D0F-61C8-483D-9D0B-50002B28556D - NEWS SOURCES: ['SOURCE_1', 'SOURCE_2'] THEN COMBINE ALL OUTPUTS FROM ALL USED TOOLS.
2025-03-26T11:03:07.150891839Z 1. BEGIN WITH INITIALIZING THE NEWS AGENT.
2025-03-26T11:03:07.150892756Z 2. USE THE FOLLOWING TOOL, SOURCE 1 TOOL:
2025-03-26T11:03:07.150893672Z A. CONFIGURE THE TOOL BY SETTING THE 'ID' AS 'D17C6D0F-61C8-483D-9D0B-50002B28556D'.
2025-03-26T11:03:07.150894464Z B. CALL THE SOURCE 1 TOOL TO FETCH NEWS FOR THE PROVIDED ID.
2025-03-26T11:03:07.150895214Z C. CHECK THE RESPONSE FOR SUCCESSFUL NEWS RETRIEVAL. IF THERE IS ANY ERROR IN CONFIGURATION OR RETRIEVAL, RAISE A PROPER EXCEPTION AND LOG THE ERROR DETAILS.
2025-03-26T11:03:07.150896172Z
2025-03-26T11:03:07.150896797Z 3. PROCEED TO THE NEXT TOOL, SOURCE 2 TOOL:
2025-03-26T11:03:07.150897422Z A. SIMILARLY, SET THE 'ID' TO 'D17C6D0F-61C8-483D-9D0B-50002B28556D' FOR THE SOURCE 2 TOOL.
2025-03-26T11:03:07.150898172Z B. CALL THE SOURCE 2 TOOL TO FETCH LOGS CORRESPONDING TO THE SPECIFIED COMPANY_ID.
2025-03-26T11:03:07.150898881Z C. VERIFY THE RESPONSE; IF ANY ISSUES ARISE DURING THE TOOL'S EXECUTION, RAISE AN EXCEPTION NOTING THE ERROR TYPE.
2025-03-26T11:03:07.150899631Z
2025-03-26T11:03:07.150900214Z 4. AFTER COLLECTING NEWS FROM BOTH TOOLS, COMBINE ALL OUTPUTS OBTAINED FROM SOURCE 1 AND SOURCE 2 INTO A UNIFIED NEWS STRUCTURE.
2025-03-26T11:03:07.150901256Z 5. ENSURE THAT THE COMBINED NEWS ARE RETURNED AS THE OUTPUT OF TASK NUMBER 1.]: 2025-03-26 11:03:07.150079
2025-03-26T11:03:07.159016672Z
Can you help with this problem? As I am stuck with this from past weeks. I am not able to get it working with multiple tools.
Moreover, In future I might have 100s of tools to find news from, and I don’t know whether the crew approach will be feasible or not, as it does not call the tools reliably.
It looks like the issue you’re running into boils down to prompt engineering, which covers both how you define your tools and how you describe your task. I can assure you, no matter how many tools you give your agent, it will use the right ones, as long as:
Your tools have crystal clear name, description, and args_schema, including solid descriptions for the fields within the args_schema.
Your task’s description is clear enough for the LLM to figure out when to use which tool (CrewAI actually handles a lot of this heavy lifting internally based on your tool definitions). So, in the task description, you can focus on handling edge cases or giving examples of specific situations where a tool should kick in, always aiming for simplicity and clarity.
All this careful setup can go down the drain if your LLM isn’t robust enough to handle the back-and-forth about tools. Smaller LLMs often stumble and give frustrating results when they need to use tools, and it gets worse the more complex the tools are. So definitely keep that in mind when designing your agentic system.
Now, about getting your agent to log tool errors: you need to consider that how an LLM uses tools is basically like this plain text conversation: “Hey, you’ve got tool X available, it does Y, and takes parameter Z, which is an integer.” Then, at some point, the LLM replies: “Execute tool X with parameter Z=7.” Finally, the LLM gets back plain text like: “The result is 777.” Error handling, logging, automatic retries – all that logic should ideally be handled by you within your tool’s definition code. Of course, the tool can just respond with something like “- The tool execution resulted in error XYZ…”, but the key is always clear communication back to the LLM.
Lastly, don’t underestimate the importance of properly structuring your Agents and Tasks in your design. Getting this right can seriously boost how effectively your tools are used. Check out some good examples of CrewAI orchestration in code from community members, like the repos by Lennex Zinyando, Lorenze Jay, Tony Kipkemboi, Brandon Hancock, among others.
I definitely recommend starting over with a much simpler version of your use case – maybe just 2 or 3 tools. Focus on improving the tool descriptions, the input schema descriptions for each tool, and your task description. Once you start getting consistent, predictable results, then you can gradually ramp up the complexity of your agentic system.