diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9759036..f76865c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,4 +51,6 @@ jobs: run: poetry run pytest - name: Run integration tests + env: + OPENAI_API_KEY: fake_key_1337 run: poetry run python tests/run_integration_tests.py diff --git a/examples/delegation_crewai.py b/examples/delegation_crewai.py index 994ebdb5..38eb71cf 100644 --- a/examples/delegation_crewai.py +++ b/examples/delegation_crewai.py @@ -3,70 +3,81 @@ from motleycrew import MotleyCrew, Task from motleycrew.agent.crewai import CrewAIMotleyAgent +from motleycrew.common.utils import configure_logging -load_dotenv() -search_tool = DuckDuckGoSearchRun() +def main(): + search_tool = DuckDuckGoSearchRun() -researcher = CrewAIMotleyAgent( - role="Senior Research Analyst", - goal="Uncover cutting-edge developments in AI and data science", - backstory="""You work at a leading tech think tank. -Your expertise lies in identifying emerging trends. -You have a knack for dissecting complex data and presenting actionable insights.""", - verbose=True, - delegation=False, - tools=[search_tool], -) + researcher = CrewAIMotleyAgent( + role="Senior Research Analyst", + goal="Uncover cutting-edge developments in AI and data science", + backstory="""You work at a leading tech think tank. + Your expertise lies in identifying emerging trends. + You have a knack for dissecting complex data and presenting actionable insights.""", + verbose=True, + delegation=False, + tools=[search_tool], + ) -writer = CrewAIMotleyAgent( - role="Tech Content Strategist", - goal="Craft compelling content on tech advancements", - backstory="""You are a renowned Content Strategist, known for your insightful and engaging articles. -You transform complex concepts into compelling narratives.""", - verbose=True, - delegation=True, -) + writer = CrewAIMotleyAgent( + role="Tech Content Strategist", + goal="Craft compelling content on tech advancements", + backstory="""You are a renowned Content Strategist, known for your insightful and engaging articles. + You transform complex concepts into compelling narratives.""", + verbose=True, + delegation=True, + ) -# Create tasks for your agents -crew = MotleyCrew() -task1 = Task( - crew=crew, - name="produce comprehensive analysis report on AI advancements", - description="""Conduct a comprehensive analysis of the latest advancements in AI in 2024. -Identify key trends, breakthrough technologies, and potential industry impacts. -Your final answer MUST be a full analysis report""", - agent=researcher, - documents=["paper1.pdf", "paper2.pdf"], # will be ignored for now -) + # Create tasks for your agents + crew = MotleyCrew() + task1 = Task( + crew=crew, + name="produce comprehensive analysis report on AI advancements", + description="""Conduct a comprehensive analysis of the latest advancements in AI in 2024. + Identify key trends, breakthrough technologies, and potential industry impacts. + Your final answer MUST be a full analysis report""", + agent=researcher, + documents=["paper1.pdf", "paper2.pdf"], # will be ignored for now + ) -task2 = Task( - crew=crew, - name="provide a literature summary of recent papers on AI", - description="""Conduct a comprehensive literature review of the latest advancements in AI in 2024. -Identify key papers, researchers, and companies in the space. -Your final answer MUST be a full literature review with citations""", - agent=researcher, -) + task2 = Task( + crew=crew, + name="provide a literature summary of recent papers on AI", + description="""Conduct a comprehensive literature review of the latest advancements in AI in 2024. + Identify key papers, researchers, and companies in the space. + Your final answer MUST be a full literature review with citations""", + agent=researcher, + ) + task3 = Task( + crew=crew, + name="produce blog post on AI advancements", + description="""Using the insights provided by a thorough web search, develop an engaging blog + post that highlights the most significant AI advancements. + Your post should be informative yet accessible, catering to a tech-savvy audience. + Make it sound cool, avoid complex words so it doesn't sound like AI. + Create a blog post of at least 4 paragraphs.""", + agent=writer, + ) -task3 = Task( - crew=crew, - name="produce blog post on AI advancements", - description="""Using the insights provided by a thorough web search, develop an engaging blog -post that highlights the most significant AI advancements. -Your post should be informative yet accessible, catering to a tech-savvy audience. -Make it sound cool, avoid complex words so it doesn't sound like AI. -Create a blog post of at least 4 paragraphs.""", - agent=writer, -) + [task1, task2] >> task3 -[task1, task2] >> task3 + # Get your crew to work! + result = crew.run( + agents=[researcher, writer], + verbose=2, # You can set it to 1 or 2 to different logging levels + ) -# Get your crew to work! -result = crew.run( - agents=[researcher, writer], - verbose=2, # You can set it to 1 or 2 to different logging levels -) + # Get the outputs of the task + for output in task3.outputs: + print(output) -print(list(result._done)[0].outputs) + return task3.outputs + + +if __name__ == "__main__": + configure_logging(verbose=True) + + load_dotenv() + main() diff --git a/examples/single_llama_index.py b/examples/single_llama_index.py index 47f7f405..314f775c 100644 --- a/examples/single_llama_index.py +++ b/examples/single_llama_index.py @@ -4,7 +4,6 @@ from motleycrew import MotleyCrew, Task from motleycrew.agent.llama_index import ReActLlamaIndexMotleyAgent -from motleycrew.caсhing import enable_cache, disable_cache from motleycrew.common.utils import configure_logging @@ -19,7 +18,6 @@ def main(): verbose=True, ) - crew = MotleyCrew() # Create tasks for your agents @@ -33,24 +31,20 @@ def main(): documents=["paper1.pdf", "paper2.pdf"], ) - # Instantiate your crew with a sequential process + # Get your crew to work! result = crew.run( agents=[researcher], verbose=2, # You can set it to 1 or 2 to different logging levels ) - # Get your crew to work! - outputs = list(result._done)[0].outputs + outputs = task1.outputs print(outputs) - print("######################") - return outputs[0].response + return [output.response for output in outputs] -if __name__ == '__main__': +if __name__ == "__main__": configure_logging(verbose=True) load_dotenv() - enable_cache() main() - disable_cache() diff --git a/examples/single_openai_tools_react.py b/examples/single_openai_tools_react.py index 7be621b3..06115072 100644 --- a/examples/single_openai_tools_react.py +++ b/examples/single_openai_tools_react.py @@ -4,48 +4,48 @@ from motleycrew import MotleyCrew, Task from motleycrew.agent.langchain.openai_tools_react import ReactOpenAIToolsAgent from motleycrew.agent.langchain.react import ReactMotleyAgent +from motleycrew.common.utils import configure_logging -# # You can delete this block if you don't want to use Langsmith -# from langsmith import Client -# -# unique_id = uuid4().hex[0:8] -# os.environ["LANGCHAIN_TRACING_V2"] = "true" -# os.environ["LANGCHAIN_PROJECT"] = f"Tracing Walkthrough - {unique_id}" -# os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com" -# os.environ["LANGCHAIN_API_KEY"] = credentials["LANGCHAIN_API_KEY"] -# -# client = Client() -# # End of Langsmith block -load_dotenv() +def main(): + search_tool = DuckDuckGoSearchRun() -search_tool = DuckDuckGoSearchRun() + tools = [search_tool] + researcher = ReactOpenAIToolsAgent(tools=tools, verbose=True) + researcher2 = ReactMotleyAgent(tools=tools, verbose=True) -tools = [search_tool] + outputs = [] + for r in [researcher, researcher2]: + crew = MotleyCrew() + task = Task( + crew=crew, + name="produce comprehensive analysis report on AI advancements", + description="""Conduct a comprehensive analysis of the latest advancements in AI in 2024. + Identify key trends, breakthrough technologies, and potential industry impacts. + Your final answer MUST be a full analysis report""", + agent=r, + documents=["paper1.pdf", "paper2.pdf"], # will be ignored for now + ) + result = crew.run( + agents=[r], + verbose=2, # You can set it to 1 or 2 to different logging levels + ) -researcher = ReactOpenAIToolsAgent(tools=tools, verbose=True) -researcher2 = ReactMotleyAgent(tools=tools, verbose=True) + # Get your crew to work! + print(task.outputs) + outputs += task.outputs -for r in [researcher, researcher2]: + return outputs - crew = MotleyCrew() - task1 = Task( - crew=crew, - name="produce comprehensive analysis report on AI advancements", - description="""Conduct a comprehensive analysis of the latest advancements in AI in 2024. - Identify key trends, breakthrough technologies, and potential industry impacts. - Your final answer MUST be a full analysis report""", - agent=r, - documents=["paper1.pdf", "paper2.pdf"], # will be ignored for now - ) - result = crew.run( - agents=[r], - verbose=2, # You can set it to 1 or 2 to different logging levels - ) - # Get your crew to work! - print(list(result._done)[0].outputs) +if __name__ == "__main__": + from motleycrew.caching import enable_cache, set_cache_location, set_strong_cache -print("######################") + configure_logging(verbose=True) + + load_dotenv() + enable_cache() + set_strong_cache(False) + main() diff --git "a/motleycrew/ca\321\201hing/__init__.py" b/motleycrew/caching/__init__.py similarity index 100% rename from "motleycrew/ca\321\201hing/__init__.py" rename to motleycrew/caching/__init__.py diff --git "a/motleycrew/ca\321\201hing/caching.py" b/motleycrew/caching/caching.py similarity index 71% rename from "motleycrew/ca\321\201hing/caching.py" rename to motleycrew/caching/caching.py index dc36dd64..e779233b 100644 --- "a/motleycrew/ca\321\201hing/caching.py" +++ b/motleycrew/caching/caching.py @@ -1,6 +1,6 @@ import os -from motleycrew.caсhing.http_cache import ( +from motleycrew.caching.http_cache import ( BaseHttpCache, RequestsHttpCaching, HttpxHttpCaching, @@ -16,23 +16,23 @@ def set_strong_cache(val: bool): - """Enabling disabling the strictly caching option""" + """Enable or disable the strict-caching option""" BaseHttpCache.strong_cache = bool(val) def set_update_cache_if_exists(val: bool): - """Enabling disabling cache updates""" + """Enable or disable cache updates""" BaseHttpCache.update_cache_if_exists = bool(val) def set_cache_location(location: str) -> str: - """Sets the caching root directory, returns the absolute path of the derrictory""" + """Set the caching root directory, return the absolute path of the directory""" BaseHttpCache.root_cache_dir = location return os.path.abspath(BaseHttpCache.root_cache_dir) def enable_cache(): - """The function of enable the caching process""" + """Enable global caching""" global is_caching for http_cache in caching_http_library_list: http_cache.enable() @@ -40,7 +40,7 @@ def enable_cache(): def disable_cache(): - """The function of disable the caching process""" + """Disable global caching""" global is_caching for http_cache in caching_http_library_list: http_cache.disable() diff --git "a/motleycrew/ca\321\201hing/http_cache.py" b/motleycrew/caching/http_cache.py similarity index 96% rename from "motleycrew/ca\321\201hing/http_cache.py" rename to motleycrew/caching/http_cache.py index 0296a1fa..2f6d6ab4 100644 --- "a/motleycrew/ca\321\201hing/http_cache.py" +++ b/motleycrew/caching/http_cache.py @@ -32,9 +32,6 @@ class StrongCacheException(BaseException): """Exception use of cache only""" -load_dotenv() - - def file_cache(http_cache: "BaseHttpCache", updating_parameters: dict = {}): """Decorator to cache function output based on its inputs, ignoring specified parameters.""" @@ -90,18 +87,14 @@ def enable(self): """Enable caching""" self._enable() self.is_caching = True - library_log = ( - "for {} library.".format(self.library_name) if self.library_name else "." - ) + library_log = "for {} library.".format(self.library_name) if self.library_name else "." logging.info("Enable caching {} class {}".format(self.__class__, library_log)) def disable(self): """Disable caching""" self._disable() self.is_caching = False - library_log = ( - "for {} library.".format(self.library_name) if self.library_name else "." - ) + library_log = "for {} library.".format(self.library_name) if self.library_name else "." logging.info("Disable caching {} class {}".format(self.__class__, library_log)) def prepare_response(self, response: Any) -> Any: @@ -130,11 +123,7 @@ def get_cache_file(self, func: Callable, *args, **kwargs) -> Union[tuple, None]: # check or create cache dirs root_dir = Path(self.root_cache_dir) - cache_dir = ( - root_dir - / url_parsed.hostname - / url_parsed.path.strip("/").replace("/", "_") - ) + cache_dir = root_dir / url_parsed.hostname / url_parsed.path.strip("/").replace("/", "_") cache_dir.mkdir(parents=True, exist_ok=True) # Convert args to a dictionary based on the function's signature @@ -214,9 +203,7 @@ def read_from_cache(self, cache_file: Path, url: str = "") -> Union[Any, None]: except Exception as e: logging.warning("Unpickling failed for {}".format(cache_file)) if self.strong_cache: - msg = "Error reading cached file: {}\n{}".format( - str(e), str(cache_file) - ) + msg = "Error reading cached file: {}\n{}".format(str(e), str(cache_file)) raise StrongCacheException(msg) return None diff --git "a/motleycrew/ca\321\201hing/utils.py" b/motleycrew/caching/utils.py similarity index 100% rename from "motleycrew/ca\321\201hing/utils.py" rename to motleycrew/caching/utils.py diff --git a/motleycrew/common/exceptions.py b/motleycrew/common/exceptions.py index e132f4cd..6ead1bf5 100644 --- a/motleycrew/common/exceptions.py +++ b/motleycrew/common/exceptions.py @@ -31,3 +31,13 @@ def __str__(self) -> str: return "Cannot modify agent{} as it is already materialized".format( f" `{self.agent_name}`" if self.agent_name is not None else "" ) + + +class IntegrationTestException(Exception): + """Integration tests exception""" + + def __init__(self, test_names: list[str]): + self.test_names = test_names + + def __str__(self): + return "Some integration tests failed: {}".format(self.test_names) diff --git a/motleycrew/crew.py b/motleycrew/crew.py index c8207e97..11c4a181 100644 --- a/motleycrew/crew.py +++ b/motleycrew/crew.py @@ -113,17 +113,14 @@ def assign_agent(self, task: Task) -> MotleyAgentParent: agent = task.agent else: agent = spawn_agent(task) + tools = self.get_agent_tools(agent, task) + agent.add_tools(tools) logging.info("Assigning task '%s' to agent '%s'", task.name, agent.name) - tools = self.get_agent_tools(agent, task) - agent.add_tools(tools) - return agent - def get_agent_tools( - self, agent: MotleyAgentParent, task: Task - ) -> Sequence[BaseTool]: + def get_agent_tools(self, agent: MotleyAgentParent, task: Task) -> Sequence[BaseTool]: # Task is needed later when we do smart tool selection # TODO: Smart tool selection goes here # Add the agents as tools to each other for delegation diff --git a/motleycrew/tasks/task.py b/motleycrew/tasks/task.py index f2c3ef03..93d39e44 100644 --- a/motleycrew/tasks/task.py +++ b/motleycrew/tasks/task.py @@ -47,8 +47,8 @@ def __init__( self.done: bool = False - self.upstream_tasks: Set[Task] = set() - self.downstream_tasks: Set[Task] = set() + self.upstream_tasks: list[Task] = list() + self.downstream_tasks: list[Task] = list() self.crew.add_task(self) @@ -84,8 +84,10 @@ def set_upstream(self, task: Task) -> Task: if task is self: raise TaskDependencyCycleError(f"Task {task.name} can not depend on itself") - self.upstream_tasks.add(task) - task.downstream_tasks.add(self) + if task not in self.upstream_tasks: + self.upstream_tasks.append(task) + if self not in task.downstream_tasks: + task.downstream_tasks.append(self) return self diff --git a/motleycrew/tracking/utils.py b/motleycrew/tracking/utils.py index 0c85ded6..38e8ae85 100644 --- a/motleycrew/tracking/utils.py +++ b/motleycrew/tracking/utils.py @@ -12,11 +12,14 @@ def get_lunary_public_key(): """Return lunary public key or None""" - return ( + key = ( os.environ.get("LUNARY_PUBLIC_KEY") or os.getenv("LUNARY_APP_ID") or os.getenv("LLMONITOR_APP_ID") ) + if not key: + logging.warning("Lunary public key is not set, tracking will be disabled") + return key def create_lunary_callback() -> LunaryCallbackHandler: @@ -66,9 +69,7 @@ def get_default_callbacks_list( if callable(dc_factory): _default_callbacks = dc_factory() else: - msg = "Default callbacks are not implemented for {} framework".format( - framework_name - ) + msg = "Default callbacks are not implemented for {} framework".format(framework_name) logging.warning(msg) return _default_callbacks @@ -104,6 +105,6 @@ def add_default_callbacks_to_langchain_config( _default_callbacks = get_default_callbacks_list() if _default_callbacks: - config_callbacks = config.get("tracking") or [] - config["tracking"] = combine_callbacks(config_callbacks, _default_callbacks) + config_callbacks = config.get("callbacks") or [] + config["callbacks"] = combine_callbacks(config_callbacks, _default_callbacks) return config diff --git a/tests/cache/api.openai.com/v1_chat_completions/84c649846dd264e0b73fecfab8a4a9b5d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/cache/api.openai.com/v1_chat_completions/84c649846dd264e0b73fecfab8a4a9b5d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl deleted file mode 100644 index 3d86cdd8..00000000 Binary files a/tests/cache/api.openai.com/v1_chat_completions/84c649846dd264e0b73fecfab8a4a9b5d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl and /dev/null differ diff --git a/tests/cache/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb4c1c84a6f853639e9161893c511b0a31aaf8a195b8285230d592a899124ca1e.pkl b/tests/cache/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb4c1c84a6f853639e9161893c511b0a31aaf8a195b8285230d592a899124ca1e.pkl deleted file mode 100644 index 1d02b131..00000000 Binary files a/tests/cache/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb4c1c84a6f853639e9161893c511b0a31aaf8a195b8285230d592a899124ca1e.pkl and /dev/null differ diff --git a/tests/cache/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b86168fc063272c18992e4eb0016b519c7d1aaf8a195b8285230d592a899124ca1e.pkl b/tests/cache/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b86168fc063272c18992e4eb0016b519c7d1aaf8a195b8285230d592a899124ca1e.pkl deleted file mode 100644 index 2f512283..00000000 Binary files a/tests/cache/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b86168fc063272c18992e4eb0016b519c7d1aaf8a195b8285230d592a899124ca1e.pkl and /dev/null differ diff --git a/tests/data/single_llama_index_test.txt b/tests/data/single_llama_index_test.txt deleted file mode 100644 index 7940b69b..00000000 --- a/tests/data/single_llama_index_test.txt +++ /dev/null @@ -1,30 +0,0 @@ -**AI Advancements in 2024: Comprehensive Analysis Report** - -**1. Realistic Expectations:** - - The AI industry is moving towards setting more realistic expectations for technology deployment and capabilities, focusing on achievable goals and transparent communication. - -**2. Multimodal AI:** - - Advancements in multimodal AI, which integrates and processes multiple types of data (e.g., text, images, sound), are expected to enhance AI's understanding and interaction capabilities. - -**3. Smaller Language Models and Open Source Advancements:** - - There is a shift towards developing smaller, more efficient language models that are easier to deploy and maintain. Open source projects are playing a crucial role in democratizing AI technology, making it accessible to a broader audience. - -**4. GPU Shortages and Cloud Costs:** - - The AI industry may face challenges such as GPU shortages and rising cloud computing costs, which could affect the development and deployment of AI models. - -**5. Model Optimization:** - - Techniques for optimizing AI models are becoming more accessible, allowing for better performance with lower resource consumption. - -**6. Customized Local Models and Data Pipelines:** - - There is a growing trend towards developing customized AI models and data pipelines tailored to specific local needs, enhancing efficiency and relevance. - -**7. Generative AI:** - - Generative AI is expected to become increasingly useful to the general public, not just tech enthusiasts. This technology will enable users to create customized content and solutions, potentially transforming various industries. - -**8. Industry Impact:** - - The widespread adoption of generative AI is poised to trigger a significant transformation across the global economic landscape. Businesses are beginning to realize the broad potential of AI to innovate and streamline operations. - -**9. Key Technologies:** - - According to MIT Technology Review, generative AI is among the top technologies that have the potential to significantly change our lives in the coming years. - -This report highlights the key trends and breakthrough technologies in AI for 2024, underscoring the industry's evolution and its potential impacts on various sectors. As AI continues to advance, it is crucial for businesses and individuals to stay informed and adapt to these changes to leverage the opportunities they present. \ No newline at end of file diff --git a/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/4a264628c775d4db62ba8ce634ffd0a2d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/4a264628c775d4db62ba8ce634ffd0a2d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..ce071404 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/4a264628c775d4db62ba8ce634ffd0a2d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/78811c187a22b55fa5b7e522a71aa36bd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/78811c187a22b55fa5b7e522a71aa36bd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..f75a0f9b Binary files /dev/null and b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/78811c187a22b55fa5b7e522a71aa36bd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/b39ddaa72db8b64f5c85a09bb8a4e259d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/b39ddaa72db8b64f5c85a09bb8a4e259d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..1e731c9e Binary files /dev/null and b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/b39ddaa72db8b64f5c85a09bb8a4e259d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/b414050ebc88bff2c657da5a257ffe1dd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/b414050ebc88bff2c657da5a257ffe1dd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..809aa1e0 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/b414050ebc88bff2c657da5a257ffe1dd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/c4cc0cd6c3ec4ff77e1a234ae4c2fc07d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/c4cc0cd6c3ec4ff77e1a234ae4c2fc07d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..58dbe959 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/c4cc0cd6c3ec4ff77e1a234ae4c2fc07d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/cfae648e42e0a9e385fc6f0541c91d97d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/cfae648e42e0a9e385fc6f0541c91d97d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..1cf00bb5 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/cfae648e42e0a9e385fc6f0541c91d97d41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/d2b1db6240390c1234c4f08da46774fdd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/d2b1db6240390c1234c4f08da46774fdd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..c022d262 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/api.openai.com/v1_chat_completions/d2b1db6240390c1234c4f08da46774fdd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/delegation_crewai/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebec8358fbe3cb4f93d2ec0f9d703bc07d0a1aaf8a195b8285230d592a899124ca1e.pkl b/tests/itest_cache/delegation_crewai/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebec8358fbe3cb4f93d2ec0f9d703bc07d0a1aaf8a195b8285230d592a899124ca1e.pkl new file mode 100644 index 00000000..f7474f82 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebec8358fbe3cb4f93d2ec0f9d703bc07d0a1aaf8a195b8285230d592a899124ca1e.pkl differ diff --git a/tests/itest_cache/delegation_crewai/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb249a43ee5f64346d467b2ff847f29fb1aaf8a195b8285230d592a899124ca1e.pkl b/tests/itest_cache/delegation_crewai/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb249a43ee5f64346d467b2ff847f29fb1aaf8a195b8285230d592a899124ca1e.pkl new file mode 100644 index 00000000..a97a73a7 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb249a43ee5f64346d467b2ff847f29fb1aaf8a195b8285230d592a899124ca1e.pkl differ diff --git a/tests/itest_cache/delegation_crewai/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b869e74eb42d6d87692e2bee63ef6d6f4491aaf8a195b8285230d592a899124ca1e.pkl b/tests/itest_cache/delegation_crewai/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b869e74eb42d6d87692e2bee63ef6d6f4491aaf8a195b8285230d592a899124ca1e.pkl new file mode 100644 index 00000000..e27f239d Binary files /dev/null and b/tests/itest_cache/delegation_crewai/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b869e74eb42d6d87692e2bee63ef6d6f4491aaf8a195b8285230d592a899124ca1e.pkl differ diff --git a/tests/itest_cache/delegation_crewai/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b86b5b07045df3b4c5a78eb37ef17ac90ce1aaf8a195b8285230d592a899124ca1e.pkl b/tests/itest_cache/delegation_crewai/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b86b5b07045df3b4c5a78eb37ef17ac90ce1aaf8a195b8285230d592a899124ca1e.pkl new file mode 100644 index 00000000..914f0544 Binary files /dev/null and b/tests/itest_cache/delegation_crewai/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b86b5b07045df3b4c5a78eb37ef17ac90ce1aaf8a195b8285230d592a899124ca1e.pkl differ diff --git a/tests/itest_cache/single_llama_index/api.openai.com/v1_chat_completions/885bd7f13b815fd63043d9465b4288dbd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/single_llama_index/api.openai.com/v1_chat_completions/885bd7f13b815fd63043d9465b4288dbd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl new file mode 100644 index 00000000..c2443ea6 Binary files /dev/null and b/tests/itest_cache/single_llama_index/api.openai.com/v1_chat_completions/885bd7f13b815fd63043d9465b4288dbd41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/cache/api.openai.com/v1_chat_completions/9a9f10525ef474effb9fbd57e1af29eed41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl b/tests/itest_cache/single_llama_index/api.openai.com/v1_chat_completions/9a9f10525ef474effb9fbd57e1af29eed41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl similarity index 96% rename from tests/cache/api.openai.com/v1_chat_completions/9a9f10525ef474effb9fbd57e1af29eed41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl rename to tests/itest_cache/single_llama_index/api.openai.com/v1_chat_completions/9a9f10525ef474effb9fbd57e1af29eed41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl index 9917ca98..7572e630 100644 Binary files a/tests/cache/api.openai.com/v1_chat_completions/9a9f10525ef474effb9fbd57e1af29eed41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl and b/tests/itest_cache/single_llama_index/api.openai.com/v1_chat_completions/9a9f10525ef474effb9fbd57e1af29eed41d8cd98f00b204e9800998ecf8427ef6bfa7aa5512fbf52a0935a74f6edf99.pkl differ diff --git a/tests/itest_cache/single_llama_index/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb4c1c84a6f853639e9161893c511b0a31aaf8a195b8285230d592a899124ca1e.pkl b/tests/itest_cache/single_llama_index/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb4c1c84a6f853639e9161893c511b0a31aaf8a195b8285230d592a899124ca1e.pkl new file mode 100644 index 00000000..694f574d Binary files /dev/null and b/tests/itest_cache/single_llama_index/duckduckgo.com/25557515d9cb6a2ff7f04e0c9186ebecb4c1c84a6f853639e9161893c511b0a31aaf8a195b8285230d592a899124ca1e.pkl differ diff --git a/tests/itest_cache/single_llama_index/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b864c0e0b6cf95a194abbec95dc2a6409f71aaf8a195b8285230d592a899124ca1e.pkl b/tests/itest_cache/single_llama_index/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b864c0e0b6cf95a194abbec95dc2a6409f71aaf8a195b8285230d592a899124ca1e.pkl new file mode 100644 index 00000000..c998fc2a Binary files /dev/null and b/tests/itest_cache/single_llama_index/links.duckduckgo.com/d.js/0ec71190039321d8b6c7404990533b864c0e0b6cf95a194abbec95dc2a6409f71aaf8a195b8285230d592a899124ca1e.pkl differ diff --git a/tests/itest_golden_data/delegation_crewai.json b/tests/itest_golden_data/delegation_crewai.json new file mode 100644 index 00000000..8f210f3e --- /dev/null +++ b/tests/itest_golden_data/delegation_crewai.json @@ -0,0 +1 @@ +["Navigating the New Frontiers: AI Advancements in 2024\n\nAs we step into 2024, the realm of artificial intelligence (AI) continues to evolve at a breathtaking pace, reshaping how we interact with technology in our daily lives. From the way we work to how we entertain ourselves, AI's influence is becoming more tangible and beneficial. Let's dive into some of the most significant AI advancements this year and explore how they're making technology more accessible and impactful for everyone.\n\nGenerative AI: Your Personal Creative Assistant\n\nOne of the standout stars in this year's AI advancements is Generative AI. Imagine having a personal assistant that not only helps you with your daily tasks but also sparks your creativity. This technology has now matured to a point where it's not just for tech wizards; everyone can harness its power. Whether it's crafting unique art pieces, writing stories, or generating custom code, Generative AI is here to enhance your creative vision. The beauty of this technology lies in its ability to learn from diverse data inputs and produce content that is not only original but also incredibly personalized.\n\nMultimodal AI: A Symphony of Data\n\nMultimodal AI is another exciting frontier. This technology processes and integrates various types of data\u2014text, images, and sound\u2014to perform tasks that require a holistic understanding of the world. It's like having a system that can read a book, listen to music, and watch a movie, then give you insights that are as nuanced as what a human might understand. This advancement is particularly promising as it paves the way for more intuitive and interactive AI systems that can operate in complex, real-world environments with a deeper level of understanding.\n\nEfficiency and Accessibility: Smaller Models and Open Source Innovations\n\nAs AI technologies grow, so does the need for them to be more efficient and accessible. 2024 has seen a significant shift towards smaller, more manageable AI models. These models are not only easier to use but also less resource-intensive, making them ideal for businesses and developers who might not have access to state-of-the-art computing power. Moreover, the open-source community has been instrumental in democratizing AI, ensuring that these powerful tools are available to a wider audience. This collaborative approach helps in refining AI technologies and making them more robust and user-friendly.\n\nEthical AI: Navigating the Future with Responsibility\n\nWith great power comes great responsibility, and the AI community is taking this seriously. As AI becomes a more integral part of our lives, the focus on ethics, safety, and regulatory considerations has intensified. The goal is to ensure that AI systems are not only effective but also fair and safe for everyone. The discussions around ethical AI are shaping policies and practices that govern the deployment of AI technologies, ensuring they contribute positively to society without compromising on safety or privacy.\n\nIn conclusion, 2024 is shaping up to be a transformative year for AI. With advancements like Generative AI, Multimodal AI, and more efficient models, coupled with a strong emphasis on ethical practices, AI is set to enhance our capabilities and lead us towards a more interconnected and efficient future. As we continue to explore these new frontiers, the potential for AI to improve every aspect of our lives becomes more apparent, promising a future where technology and humanity progress hand in hand.\n```"] \ No newline at end of file diff --git a/tests/itest_golden_data/single_llama_index.json b/tests/itest_golden_data/single_llama_index.json new file mode 100644 index 00000000..b561db05 --- /dev/null +++ b/tests/itest_golden_data/single_llama_index.json @@ -0,0 +1 @@ +["**Comprehensive Analysis of AI Advancements in 2024**\n\n**1. Key Trends:**\n - **Reality Check:** There is a shift towards setting more realistic expectations for AI capabilities and applications.\n - **Multimodal AI:** Advancements in AI that can process and integrate multiple types of data (e.g., text, images, sound) are gaining traction.\n - **Smaller Language Models and Open Source Advancements:** There is a move towards developing smaller, more efficient language models. Additionally, open-source contributions are becoming increasingly significant in the AI field.\n - **Model Optimization:** Tools and techniques for optimizing AI models are becoming more accessible, allowing for enhanced performance and efficiency.\n\n**2. Breakthrough Technologies:**\n - **Generative AI:** This technology is expected to become more practical and useful for the average person, not just tech professionals. It involves AI that can generate text, images, and other content, potentially transforming content creation across various industries.\n - **Customized Local Models and Data Pipelines:** There is a trend towards customizing AI models and data pipelines for specific local needs, enhancing relevance and efficiency.\n\n**3. Potential Industry Impacts:**\n - **GPU Shortages and Cloud Costs:** The demand for GPUs (graphics processing units) for AI processing continues to rise, potentially leading to shortages and increased costs for cloud-based AI services.\n - **EUR Industry:** In the Energy, Utilities, and Resources sectors, AI and automation are being used to accelerate the transition to a composable environment. This is part of a broader digital transformation that is driving innovation in these sectors.\n - **IoT Integration:** The integration of AI with the Internet of Things (IoT) is enabling new use cases and driving innovation across various industries.\n\n**4. Societal and Economic Implications:**\n - **Accessibility and Adoption:** As AI technologies become more user-friendly and accessible, we can expect broader adoption across different segments of society, leading to significant societal and economic impacts.\n - **Innovation and Transformation:** The continued evolution and integration of AI technologies are expected to drive substantial innovation and transformation across multiple sectors, potentially changing the way we live and work.\n\nThis analysis highlights the dynamic nature of AI development and its potential to influence a wide range of industries and societal structures in 2024 and beyond."] diff --git a/tests/run_integration_tests.py b/tests/run_integration_tests.py index c1c7b212..3db18ac0 100644 --- a/tests/run_integration_tests.py +++ b/tests/run_integration_tests.py @@ -1,117 +1,164 @@ +import shutil +from typing import Optional + import os -import sys +import argparse from pathlib import Path import logging import traceback -import inspect -import difflib as df +import difflib +import json +from dotenv import load_dotenv -from motleycrew.caсhing import ( +from motleycrew.common.exceptions import IntegrationTestException +from motleycrew.common.utils import configure_logging +from motleycrew.caching import ( enable_cache, - disable_cache, set_cache_location, set_strong_cache, ) + +from examples.delegation_crewai import main as delegation_crewai_main from examples.single_llama_index import main as single_llama_index_main -CACHE_DIR = "tests/cache" -DATA_DIR = "tests/data" -STRONG_CACHE = True -LOGGING_LEVEL = logging.ERROR -logger = logging.getLogger("integration_test_logger") +INTEGRATION_TESTS = { + "single_llama_index": single_llama_index_main, + "delegation_crewai": delegation_crewai_main, + # "single_openai_tools_react": single_openai_tools_react_main, TODO: enable this test +} + +DEFAULT_CACHE_DIR = Path(__file__).parent / "itest_cache" +DEFAULT_GOLDEN_DIR = Path(__file__).parent / "itest_golden_data" + + +def get_args_parser(): + """Argument parser""" + parser = argparse.ArgumentParser( + description="Run integration tests", formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + "--test-name", + type=str, + choices=INTEGRATION_TESTS.keys(), + help="Name of the test to run (leave empty to run all tests)", + default=None, + ) + parser.add_argument("--cache-dir", type=str, help="Cache directory", default=DEFAULT_CACHE_DIR) + parser.add_argument( + "--golden-dir", type=str, help="Reference data directory", default=DEFAULT_GOLDEN_DIR + ) + parser.add_argument( + "--update-golden", + action="store_true", + help="Update reference data together with the cache", + ) + + return parser + + +def compare_results(result: str | list[str], expected_result: str | list[str]): + """Compare the received and expected results""" + if isinstance(result, str): + result = [result] + if isinstance(expected_result, str): + expected_result = [expected_result] + + diff = [] + for i, (row, expected_row) in enumerate(zip(result, expected_result)): + result_lines = row.splitlines() + expected_result_lines = expected_row.splitlines() + diff += list(difflib.unified_diff(result_lines, expected_result_lines)) + if diff: + message = "Test result != expected result.\n{}\n".format("\n".join(diff)) + raise Exception(message) -class IntegrationTestException(Exception): - """Integration tests exception""" - def __init__(self, test_name: str, *args, **kwargs): - super(IntegrationTestException, self).__init__(*args, **kwargs) - self.test_name = test_name +def build_excepted_content_file_path( + golden_dir: str, test_name: str, extension: str = "txt" +) -> str: + """Build golden data file path""" + return os.path.join(golden_dir, "{}.{}".format(test_name, extension)) - def __str__(self): - super_str = super(IntegrationTestException, self).__str__() - return "{} {}: {}".format(self.__class__, self.test_name, super_str) +def write_content(golden_dir: str, test_name: str, content: str, extension: str = "json"): + """Write golden data to file""" + file_path = build_excepted_content_file_path(golden_dir, test_name, extension) + with open(file_path, "w") as fd: + json.dump(content, fd) -def single_llama_index_test(): - """Test example single_llama_index""" - test_name = inspect.stack()[0][3] - content = single_llama_index_main() - excepted_content = read_content(test_name) - compare_results(content, excepted_content) +def read_golden_data(golden_dir: str, test_name: str, extension: str = "json"): + """Read golden data from file""" + file_path = build_excepted_content_file_path(golden_dir, test_name, extension) + with open(file_path, "r") as fd: + return json.load(fd) -def compare_results(result: str, excepted_result: str) -> list: - """Comparison of the received and expected results""" - result_lines = result.splitlines() - excepted_result_lines = excepted_result.splitlines() - diff = list(df.unified_diff(result_lines, excepted_result_lines)) - if diff: - message = "Result content != excepted content.\n{}\n".format( - "\n".join(diff[3:]) - ) - raise Exception(message) - return diff +def run_integration_tests( + cache_dir: str, + golden_dir: str, + update_golden: bool = False, + test_name: Optional[str] = None, +): + failed_tests = {} -def build_excepted_content_file_path(test_name: str, extension: str = "txt") -> str: - """Building data file path""" - return os.path.join(DATA_DIR, "{}.{}".format(test_name, extension)) + for current_test_name, test_fn in INTEGRATION_TESTS.items(): + if test_name is not None and test_name != current_test_name: + continue + logging.info("Running test: %s", current_test_name) -def write_content(test_name: str, content: str, extension: str = "txt") -> bool: - """Writing data to file""" - file_path = build_excepted_content_file_path(test_name, extension) - with open(file_path, "w") as f_o: - f_o.write(content) - return True + cache_sub_dir = os.path.join(cache_dir, current_test_name) + if update_golden: + logging.info("Update-golden flag is set. Cleaning cache directory %s", cache_sub_dir) + shutil.rmtree(cache_sub_dir, ignore_errors=True) + os.makedirs(cache_sub_dir, exist_ok=True) + os.makedirs(golden_dir, exist_ok=True) + set_strong_cache(False) + else: + set_strong_cache(True) + set_cache_location(cache_sub_dir) + try: + test_result = test_fn() + if update_golden: + logging.info( + "Skipping check and updating golden data for test: %s", current_test_name + ) + write_content(golden_dir, current_test_name, test_result) + else: + excepted_result = read_golden_data(golden_dir, current_test_name) + compare_results(test_result, excepted_result) -def read_content(test_name: str, extension: str = "txt") -> str: - """Reading data from file""" - file_path = build_excepted_content_file_path(test_name, extension) - with open(file_path, "r") as f_o: - return f_o.read() + except Exception as e: + logging.error("Test %s failed: %s", current_test_name, str(e)) + failed_tests[current_test_name] = traceback.format_exc() + for t, exception in failed_tests.items(): + logging.error("Test %s failed", t) + logging.error(exception) -def find_test_functions(): - """Searches for and returns a list of test functions""" - functions_list = [] - for func_name, func in inspect.getmembers( - sys.modules[__name__], inspect.isfunction - ): - if func_name.endswith("_test"): - functions_list.append(func) - return functions_list + if failed_tests: + raise IntegrationTestException(test_names=list(failed_tests.keys())) -if __name__ == "__main__": +def main(): + configure_logging(verbose=True) + load_dotenv() - logger.setLevel(LOGGING_LEVEL) + parser = get_args_parser() + args = parser.parse_args() enable_cache() - set_cache_location(CACHE_DIR) - set_strong_cache(STRONG_CACHE) - - data_dir = Path(DATA_DIR) - data_dir.mkdir(parents=True, exist_ok=True) - - test_exceptions = [] - test_functions = find_test_functions() + run_integration_tests( + cache_dir=args.cache_dir, + golden_dir=args.golden_dir, + update_golden=args.update_golden, + test_name=args.test_name, + ) - for f in test_functions: - try: - logger.info("Start function: {}".format(f.__name__)) - f() - except Exception as e: - msg = "{}\n{}".format(str(e), traceback.format_exc()) - test_exceptions.append(IntegrationTestException(f.__name__, msg)) - continue - for i, t_e in enumerate(test_exceptions): - if i == len(test_exceptions) - 1: - disable_cache() - raise t_e - logger.error(str(t_e)) - disable_cache() +if __name__ == "__main__": + main() diff --git a/tests/test_caching/test_http_cache.py b/tests/test_caching/test_http_cache.py index b2eec4e1..5c2f5c67 100644 --- a/tests/test_caching/test_http_cache.py +++ b/tests/test_caching/test_http_cache.py @@ -1,7 +1,7 @@ import pytest -from motleycrew.caсhing.http_cache import RequestsHttpCaching, CacheException -from motleycrew.caсhing import http_cache +from motleycrew.caching.http_cache import RequestsHttpCaching, CacheException +from motleycrew.caching import http_cache @pytest.fixture diff --git a/tests/test_tasks/test_task.py b/tests/test_tasks/test_task.py index 45f263a7..9fc2b0da 100644 --- a/tests/test_tasks/test_task.py +++ b/tests/test_tasks/test_task.py @@ -41,14 +41,14 @@ def test_set_upstream_returns_self(self, task1, task2): def test_set_upstream_sets_upstream(self, task1, task2): task2.set_upstream(task1) - assert task1.upstream_tasks == set() - assert task2.upstream_tasks == {task1} + assert task1.upstream_tasks == [] + assert task2.upstream_tasks == [task1] def test_set_upstream_sets_downstreams(self, task1, task2): task2.set_upstream(task1) - assert task1.downstream_tasks == {task2} - assert task2.downstream_tasks == set() + assert task1.downstream_tasks == [task2] + assert task2.downstream_tasks == [] def test_rshift_returns_left(self, task1, task2): result = task1 >> task2 @@ -58,28 +58,28 @@ def test_rshift_returns_left(self, task1, task2): def test_rshift_sets_downstream(self, task1, task2): task1 >> task2 - assert task1.downstream_tasks == {task2} - assert task2.downstream_tasks == set() + assert task1.downstream_tasks == [task2] + assert task2.downstream_tasks == [] def test_rshift_sets_upstream(self, task1, task2): task1 >> task2 - assert task1.upstream_tasks == set() - assert task2.upstream_tasks == {task1} + assert task1.upstream_tasks == [] + assert task2.upstream_tasks == [task1] def test_rshift_set_multiple_downstream(self, task1, task2, task3): task1 >> [task2, task3] - assert task1.downstream_tasks == {task2, task3} - assert task2.downstream_tasks == set() - assert task3.downstream_tasks == set() + assert task1.downstream_tasks == [task2, task3] + assert task2.downstream_tasks == [] + assert task3.downstream_tasks == [] def test_rshift_set_multiple_upstream(self, task1, task2, task3): task1 >> [task2, task3] - assert task1.upstream_tasks == set() - assert task2.upstream_tasks == {task1} - assert task3.upstream_tasks == {task1} + assert task1.upstream_tasks == [] + assert task2.upstream_tasks == [task1] + assert task3.upstream_tasks == [task1] def test_sequence_on_left_returns_sequence(self, task1, task2, task3): result = [task1, task2] >> task3 @@ -89,21 +89,21 @@ def test_sequence_on_left_returns_sequence(self, task1, task2, task3): def test_sequence_on_left_sets_downstream(self, task1, task2, task3): [task1, task2] >> task3 - assert task1.downstream_tasks == {task3} - assert task2.downstream_tasks == {task3} - assert task3.downstream_tasks == set() + assert task1.downstream_tasks == [task3] + assert task2.downstream_tasks == [task3] + assert task3.downstream_tasks == [] def test_sequence_on_left_sets_upstream(self, task1, task2, task3): [task1, task2] >> task3 - assert task1.upstream_tasks == set() - assert task2.upstream_tasks == set() - assert task3.upstream_tasks == {task1, task2} + assert task1.upstream_tasks == [] + assert task2.upstream_tasks == [] + assert task3.upstream_tasks == [task1, task2] def test_deduplicates(self, task1, task2): task1 >> [task2, task2] - assert task1.downstream_tasks == {task2} + assert task1.downstream_tasks == [task2] def test_error_on_direct_dependency_cycle(self, task1): with pytest.raises(TaskDependencyCycleError):