From 6d04eb7614ef3256a7efc7e062fc43a7b40d380b Mon Sep 17 00:00:00 2001 From: Tanmay Gupta Date: Wed, 5 Jun 2024 10:33:56 +0530 Subject: [PATCH 1/2] minimal recreation of async tooling --- src/crewai/tools/tool_usage.py | 27 +++++++++++++++++++++++---- tests/crew_test.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index d4d128dbde..4adba9cd2e 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -2,6 +2,8 @@ from difflib import SequenceMatcher from textwrap import dedent from typing import Any, List, Union +import asyncio +import inspect from langchain_core.tools import BaseTool from langchain_openai import ChatOpenAI @@ -123,6 +125,11 @@ def _use( tool=calling.tool_name, input=calling.arguments ) + tool_method = tool.func + is_async_tool = asyncio.iscoroutinefunction( + tool_method + ) or inspect.iscoroutinefunction(tool_method) + if not result: try: if calling.tool_name in [ @@ -139,16 +146,28 @@ def _use( for k, v in calling.arguments.items() if k in acceptable_args } - result = tool._run(**arguments) + if is_async_tool: + result = asyncio.run(tool._run(**arguments)) + else: + result = tool._run(**arguments) except Exception: if tool.args_schema: arguments = calling.arguments - result = tool._run(**arguments) + if is_async_tool: + result = asyncio.run(tool._run(**arguments)) + else: + result = tool._run(**arguments) else: arguments = calling.arguments.values() # type: ignore # Incompatible types in assignment (expression has type "dict_values[str, Any]", variable has type "dict[str, Any]") - result = tool._run(*arguments) + if is_async_tool: + result = asyncio.run(tool._run(*arguments)) + else: + result = tool._run(*arguments) else: - result = tool._run() + if is_async_tool: + result = asyncio.run(tool._run()) + else: + result = tool._run() except Exception as e: self._run_attempts += 1 if self._run_attempts > self._max_parsing_attempts: diff --git a/tests/crew_test.py b/tests/crew_test.py index 75868ac9e3..9b13acde89 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -941,6 +941,40 @@ def test_manager_agent(): execute.assert_called() +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_async_tool_execution(): + from langchain.tools import tool + from langchain_openai import ChatOpenAI + import asyncio + + @tool + async def async_answer() -> str: + """Useful for when you need to multiply two numbers together.""" + await asyncio.sleep(1) + return "The capital of France is Paris." + + agent = Agent( + role="Research", + goal="Research the user query and provide a brief and concise response. If you need more information, ask the user for it.", + backstory=( + "You are a virtual concierge specialized in research. Respond to our first-class users with the latest information." + ), + tools=[async_answer], + llm=ChatOpenAI(temperature=0, model="gpt-4"), + ) + task_description = "Find the capital of France" + expected_output = "A response to the user query." + + async_task = Task( + description=task_description, expected_output=expected_output, agent=agent + ) + + crew = Crew(agents=[agent], tasks=[async_task]) + + result = crew.kickoff() + assert result == "The capital of France is Paris." + + def test_manager_agent_in_agents_raises_exception(): task = Task( description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", From e8a11dc26e762ee25ee503788594ad6f7ed22f32 Mon Sep 17 00:00:00 2001 From: Tanmay Gupta Date: Wed, 5 Jun 2024 11:36:19 +0530 Subject: [PATCH 2/2] supporting @tool and Basetool --- src/crewai/tools/tool_usage.py | 23 +++++++++------ tests/crew_test.py | 51 ++++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index 4adba9cd2e..0a60ff8dee 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -3,7 +3,6 @@ from textwrap import dedent from typing import Any, List, Union import asyncio -import inspect from langchain_core.tools import BaseTool from langchain_openai import ChatOpenAI @@ -125,10 +124,16 @@ def _use( tool=calling.tool_name, input=calling.arguments ) - tool_method = tool.func - is_async_tool = asyncio.iscoroutinefunction( - tool_method - ) or inspect.iscoroutinefunction(tool_method) + tool_method = tool.func or tool.coroutine + is_async_tool = asyncio.iscoroutinefunction(tool_method) + + if is_async_tool: + if tool.func: + # async tool defined using BaseTool class from crewai_tools + async_tool_run = tool._run + elif tool.coroutine: + # async tool defined using @tool decorator from langchain + async_tool_run = tool._arun if not result: try: @@ -147,25 +152,25 @@ def _use( if k in acceptable_args } if is_async_tool: - result = asyncio.run(tool._run(**arguments)) + result = asyncio.run(async_tool_run(**arguments)) else: result = tool._run(**arguments) except Exception: if tool.args_schema: arguments = calling.arguments if is_async_tool: - result = asyncio.run(tool._run(**arguments)) + result = asyncio.run(async_tool_run(**arguments)) else: result = tool._run(**arguments) else: arguments = calling.arguments.values() # type: ignore # Incompatible types in assignment (expression has type "dict_values[str, Any]", variable has type "dict[str, Any]") if is_async_tool: - result = asyncio.run(tool._run(*arguments)) + result = asyncio.run(async_tool_run(*arguments)) else: result = tool._run(*arguments) else: if is_async_tool: - result = asyncio.run(tool._run()) + result = asyncio.run(async_tool_run()) else: result = tool._run() except Exception as e: diff --git a/tests/crew_test.py b/tests/crew_test.py index 9b13acde89..f81afdc7fe 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -941,15 +941,14 @@ def test_manager_agent(): execute.assert_called() -@pytest.mark.vcr(filter_headers=["authorization"]) -def test_crew_async_tool_execution(): +def test_crew_async_tool_execution_langchain(): from langchain.tools import tool from langchain_openai import ChatOpenAI import asyncio @tool - async def async_answer() -> str: - """Useful for when you need to multiply two numbers together.""" + async def async_search(query: str) -> str: + """Useful to get the answer to a user query.""" await asyncio.sleep(1) return "The capital of France is Paris." @@ -959,7 +958,49 @@ async def async_answer() -> str: backstory=( "You are a virtual concierge specialized in research. Respond to our first-class users with the latest information." ), - tools=[async_answer], + tools=[async_search], + llm=ChatOpenAI(temperature=0, model="gpt-4"), + ) + task_description = "Find the capital of France" + expected_output = "A response to the user query." + + async_task = Task( + description=task_description, expected_output=expected_output, agent=agent + ) + + crew = Crew(agents=[agent], tasks=[async_task]) + + result = crew.kickoff() + assert result == "The capital of France is Paris." + + +def test_crew_async_tool_execution(): + from langchain.tools import tool + from langchain_openai import ChatOpenAI + import asyncio + import time + from crewai_tools import BaseTool + + class AsyncSearch(BaseTool): + def __init__(self): + super().__init__( + name="AsyncSearch", + description="Performs searches based on query", + ) + + async def _run(self, query: str): + await asyncio.sleep(1) + return "The capital of France is Paris." + + async_search = AsyncSearch() + + agent = Agent( + role="Research", + goal="Research the user query and provide a brief and concise response. If you need more information, ask the user for it.", + backstory=( + "You are a virtual concierge specialized in research. Respond to our first-class users with the latest information." + ), + tools=[async_search], llm=ChatOpenAI(temperature=0, model="gpt-4"), ) task_description = "Find the capital of France"