crewAI
crewAI copied to clipboard
Not able to use tool with multiple input
I am playing around with CrewAI with the goal of using it in our production app. I have done a basic setup where I am trying to setup an agent with multi-input tool.
from crewai import Agent, Task, Crew, Process
from langchain.tools import tool
# Define a structured tool with multiple parameters
@tool
def data_analysis_tool(data_source, analysis_type, output_format):
"""
Perform data analysis on the specified data source.
Args:
data_source: The source of the data to analyze.
analysis_type: The type of analysis to perform.
output_format: The format of the analysis output.
Returns:
A string describing the result of the analysis.
"""
# Tool logic here
return f"Analysis of {data_source} using {analysis_type} in {output_format} format"
# Create an agent and assign the tool
analyst_agent = Agent(
role='Data Analyst',
goal='Perform data analysis based on given parameters',
backstory='An experienced data analyst capable of handling various types of data analysis tasks.',
tools=[data_analysis_tool],
llm=ChatOpenAI(model_name="gpt-4-1106-preview", temperature=0.1)
)
# Define a task with parameters for the tool
task = Task(
description='Analyze the sales data',
context={
'data_source': 'sales_database',
'analysis_type': 'trend_analysis',
'output_format': 'PDF'
},
agent=analyst_agent
)
# Create a crew and assign the task
crew = Crew(agents=[analyst_agent], tasks=[task])
# Execute the crew
result = crew.kickoff()
print(result)
When I am trying to run I am always getting following error:
---------------------------------------------------------------------------
ValidationError Traceback (most recent call last)
[<ipython-input-31-c57ec2325d57>](https://localhost:8080/#) in <cell line: 43>()
41
42 # Execute the crew
---> 43 result = crew.kickoff()
44 print(result)
13 frames
[/usr/local/lib/python3.10/dist-packages/pydantic/v1/main.py](https://localhost:8080/#) in __init__(__pydantic_self__, **data)
339 values, fields_set, validation_error = validate_model(__pydantic_self__.__class__, data)
340 if validation_error:
--> 341 raise validation_error
342 try:
343 object_setattr(__pydantic_self__, '__dict__', values)
ValidationError: 2 validation errors for data_analysis_toolSchemaSchema
analysis_type
field required (type=value_error.missing)
output_format
field required (type=value_error.missing)
Does CrewAI works well with Structured tools with multiple input parameter? What am I doing wrong?
I have opened the same issue in #110 and this issue is from pydantic version that crewai is using. The issue is also ongoing in langchain.
@Haripritamreddy you are right. Have you found any temporary solution for this?
Unlike langchain which can work with pydantic v1 and v2 crewai does not have this yet. I tried to install the oldest version of crewai 0.1.0 with pydantic 1.10.10 I get the error
The conflict is caused by:
The user requested pydantic==1.10.10
crewai 0.1.0 depends on pydantic<3.0.0 and >=2.4.2
So there is currently no way unless the next release of crewai adds pydantic v1 support or langchain should solve the issue.
@tkmerkel found a solution for it in the issue i opened. It has worked for me and it will work for you .
class SearchTools():
@tool("Search the internet")
def search_internet(payload):
"""Useful to search the internet about a given topic and return relevant results
:param payload: str, a string representation of dictionary containing the following keys:
query: str, the query to search for
image_count: int, the number of top results to return
example payload:
{
"query": "cat",
"result_count": 4
}
"""
url = "https://google.serper.dev/search"
query = json.loads(payload)['query']
top_result_to_return = json.loads(payload)['result_count']
search_payload = json.dumps({"q": query})
headers = {
'X-API-KEY': os.environ['SERPER_API_KEY'],
'content-type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=search_payload)
# check if there is an organic key
if 'organic' not in response.json():
return "Sorry, I couldn't find anything about that, there could be an error with you serper api key."
else:
results = response.json()['organic']
stirng = []
for result in results[:top_result_to_return]:
try:
stirng.append('\n'.join([
f"Title: {result['title']}", f"Link: {result['link']}",
f"Snippet: {result['snippet']}", "\n-----------------"
]))
except KeyError:
next
return '\n'.join(stirng)
The explaination they gave is
For what its worth, I've been getting around it by having all my functions have a single parameter payload and that payload being a dictionary you can pass multiple arguments to.
@joaomdmoura what do you think of this? I can revert to pydantic.v1
, but I don't think that is wise because the upgrade is imminent and the performance increase from v2 is notable. If the dict
approach for multiple inputs is working and testable, I think it should be noted in the docs and to move from there
i too have been futzing with a solution based on the @tkmerkel code, works but finicky at times - specially with arrays as you can see:
def convert_tool_for_crewai(tool: BaseTool):
if tool.metadata and "crewai" in tool.metadata.keys() and tool.metadata["crewai"] == True:
return tool
elif tool.args_schema and len(tool.args) <= 1:
return tool
elif not tool.args_schema:
return tool
def parse_input_and_delegate(tool_input: Optional[str] = None) -> Any:
"""Parse the input and delegate to the function."""
# parse starting with first { and last }
tool_input = tool_input or "{}"
tool_input_input: Dict = {}
try:
tool_input_input = json.loads(tool_input[tool_input.find("{"):tool_input.rfind("}")+1])
#handle for arrays here :/
for k,v in tool_input_input.items():
if tool.args[k]["type"] == "array" and not isinstance(v,list):
tool_input_input[k] = [v]
except:
# trust the input, it might be positional...
tool_input_input = tool_input # type: ignore
#parse it!
parsed_input = tool._parse_input(tool_input_input)
tool_args, tool_kwargs = tool._to_args_and_kwargs(parsed_input)
observation = (
tool._run(*tool_args, run_manager=None, **tool_kwargs)
if signature(tool._run).parameters.get("run_manager")
else tool._run(*tool_args, **tool_kwargs)
)
return observation
# gen a prompt describing the fields expected
tool_args_desc = ""
for k,v in tool.args.items():
tool_args_desc += f"field: {k}"
if "type" in v.keys():
if v['type'] == "array":
tool_args_desc += f"\ntype: {v['type']} of {v['items']['type']}"
else:
tool_args_desc += f"\ntype: {v['type']}"
tool_args_desc += "\n\n"
# create the tool
crewai_tool = Tool.from_function(
func=parse_input_and_delegate,
name=f"{tool.name}_crewai",
description=tool.description,
args_schema=create_model(
f"{tool.name}_crewai_input",
tool_input=Field(
default="{}",
description=f"This MUST be a string representation of a dictionary containing the following fields:\n\n{tool_args_desc.strip()}"
)
),
return_direct=tool.return_direct,
metadata={
"crewai": True,
"original_tool": tool
}
)
return crewai_tool
I found some tools (e.g. MS365, Gmail) would cause the large tool description to go beyond the 1000-ish char limit. Here im just splitting the tool description from the single args description.
I too have been tormented by the multi-parameter input of tools. I used to always concatenate multiple inputs with a special character, such as '#'. But now I have found a very effective method, and I hope it can help everyone.
@tool("Save the given code to a given path")
def save_code(path, code_content):
"""Useful to write a file to a given path with a given content.
:param path: string, the full path of the file(instead of the file name).
:param code_content: string, the code content you want to save.
example:
{
"path": "",
"code_content": "using UnityEngine;..."
}
"""
I too have been tormented by the multi-parameter input of tools. I used to always concatenate multiple inputs with a special character, such as '#'. But now I have found a very effective method, and I hope it can help everyone.
@tool("Save the given code to a given path") def save_code(path, code_content): """Useful to write a file to a given path with a given content. :param path: string, the full path of the file(instead of the file name). :param code_content: string, the code content you want to save. example: { "path": "", "code_content": "using UnityEngine;..." } """
MVP