diff --git a/docs/how-to/Start-a-New-CrewAI-Project.md b/docs/how-to/Start-a-New-CrewAI-Project.md index 1a6bbf6fd..ed9dd7456 100644 --- a/docs/how-to/Start-a-New-CrewAI-Project.md +++ b/docs/how-to/Start-a-New-CrewAI-Project.md @@ -79,8 +79,75 @@ research_candidates_task: {job_requirements} expected_output: > A list of 10 potential candidates with their contact information and brief profiles highlighting their suitability. + agent: researcher # THIS NEEDS TO MATCH THE AGENT NAME IN THE AGENTS.YAML FILE AND THE AGENT DEFINED IN THE Crew.PY FILE + context: # THESE NEED TO MATCH THE TASK NAMES DEFINED ABOVE AND THE TASKS.YAML FILE AND THE TASK DEFINED IN THE Crew.PY FILE + - researcher ``` +### Referencing Variables: +Your defined functions with the same name will be used. For example, you can reference the agent for specific tasks from task.yaml file. Ensure your annotated agent and function name is the same otherwise your task wont recognize the reference properly. + +#### Example References +agent.yaml +```yaml +email_summarizer: + role: > + Email Summarizer + goal: > + Summarize emails into a concise and clear summary + backstory: > + You will create a 5 bullet point summary of the report + llm: mixtal_llm +``` + +task.yaml +```yaml +email_summarizer_task: + description: > + Summarize the email into a 5 bullet point summary + expected_output: > + A 5 bullet point summary of the email + agent: email_summarizer + context: + - reporting_task + - research_task +``` + +Use the annotations are used to properly reference the agent and task in the crew.py file. +Annotations include: +- @agent +- @task +- @crew +- @llm +- @tool +- @callback +- @output_json +- @output_pydantic +- @cache_handler + +crew.py +```py +... + @llm + def mixtal_llm(self): + return ChatGroq(temperature=0, model_name="mixtral-8x7b-32768") + + @agent + def email_summarizer(self) -> Agent: + return Agent( + config=self.agents_config["email_summarizer"], + ) + ## ...other tasks defined + @task + def email_summarizer_task(self) -> Task: + return Task( + config=self.tasks_config["email_summarizer_task"], + ) +... +``` + + + ## Installing Dependencies To install the dependencies for your project, you can use Poetry. First, navigate to your project directory: diff --git a/src/crewai/cli/templates/config/tasks.yaml b/src/crewai/cli/templates/config/tasks.yaml index 684720574..f30820855 100644 --- a/src/crewai/cli/templates/config/tasks.yaml +++ b/src/crewai/cli/templates/config/tasks.yaml @@ -5,6 +5,7 @@ research_task: the current year is 2024. expected_output: > A list with 10 bullet points of the most relevant information about {topic} + agent: researcher reporting_task: description: > @@ -13,3 +14,4 @@ reporting_task: expected_output: > A fully fledge reports with the mains topics, each with a full section of information. Formatted as markdown without '```' + agent: reporting_analyst diff --git a/src/crewai/cli/templates/crew.py b/src/crewai/cli/templates/crew.py index f139b44e1..f592b2767 100644 --- a/src/crewai/cli/templates/crew.py +++ b/src/crewai/cli/templates/crew.py @@ -32,14 +32,12 @@ class {{crew_name}}Crew(): def research_task(self) -> Task: return Task( config=self.tasks_config['research_task'], - agent=self.researcher() ) @task def reporting_task(self) -> Task: return Task( config=self.tasks_config['reporting_task'], - agent=self.reporting_analyst(), output_file='report.md' ) diff --git a/src/crewai/project/__init__.py b/src/crewai/project/__init__.py index 25326a2af..0bae08a94 100644 --- a/src/crewai/project/__init__.py +++ b/src/crewai/project/__init__.py @@ -1,2 +1,25 @@ -from .annotations import agent, crew, task +from .annotations import ( + agent, + crew, + task, + output_json, + output_pydantic, + tool, + callback, + llm, + cache_handler, +) from .crew_base import CrewBase + +__all__ = [ + "agent", + "crew", + "task", + "output_json", + "output_pydantic", + "tool", + "callback", + "CrewBase", + "llm", + "cache_handler", +] diff --git a/src/crewai/project/annotations.py b/src/crewai/project/annotations.py index 31c9df03a..f6dba56a3 100644 --- a/src/crewai/project/annotations.py +++ b/src/crewai/project/annotations.py @@ -30,6 +30,37 @@ def agent(func): return func +def llm(func): + func.is_llm = True + func = memoize(func) + return func + + +def output_json(cls): + cls.is_output_json = True + return cls + + +def output_pydantic(cls): + cls.is_output_pydantic = True + return cls + + +def tool(func): + func.is_tool = True + return memoize(func) + + +def callback(func): + func.is_callback = True + return memoize(func) + + +def cache_handler(func): + func.is_cache_handler = True + return memoize(func) + + def crew(func): def wrapper(self, *args, **kwargs): instantiated_tasks = [] diff --git a/src/crewai/project/crew_base.py b/src/crewai/project/crew_base.py index 154b094c7..2f33c06af 100644 --- a/src/crewai/project/crew_base.py +++ b/src/crewai/project/crew_base.py @@ -1,6 +1,7 @@ import inspect import os from pathlib import Path +from typing import Any, Callable, Dict import yaml from dotenv import load_dotenv @@ -20,11 +21,6 @@ def CrewBase(cls): base_directory = Path(frame_info.filename).parent.resolve() break - if base_directory is None: - raise Exception( - "Unable to dynamically determine the project's base directory, you must run it from the project's root directory." - ) - original_agents_config_path = getattr( cls, "agents_config", "config/agents.yaml" ) @@ -32,12 +28,20 @@ def CrewBase(cls): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + + if self.base_directory is None: + raise Exception( + "Unable to dynamically determine the project's base directory, you must run it from the project's root directory." + ) + self.agents_config = self.load_yaml( os.path.join(self.base_directory, self.original_agents_config_path) ) self.tasks_config = self.load_yaml( os.path.join(self.base_directory, self.original_tasks_config_path) ) + self.map_all_agent_variables() + self.map_all_task_variables() @staticmethod def load_yaml(config_path: str): @@ -45,4 +49,138 @@ def CrewBase(cls): # parsedContent = YamlParser.parse(file) # type: ignore # Argument 1 to "parse" has incompatible type "TextIOWrapper"; expected "YamlParser" return yaml.safe_load(file) + def _get_all_functions(self): + return { + name: getattr(self, name) + for name in dir(self) + if callable(getattr(self, name)) + } + + def _filter_functions( + self, functions: Dict[str, Callable], attribute: str + ) -> Dict[str, Callable]: + return { + name: func + for name, func in functions.items() + if hasattr(func, attribute) + } + + def map_all_agent_variables(self) -> None: + all_functions = self._get_all_functions() + llms = self._filter_functions(all_functions, "is_llm") + tool_functions = self._filter_functions(all_functions, "is_tool") + cache_handler_functions = self._filter_functions( + all_functions, "is_cache_handler" + ) + callbacks = self._filter_functions(all_functions, "is_callback") + agents = self._filter_functions(all_functions, "is_agent") + + for agent_name, agent_info in self.agents_config.items(): + self._map_agent_variables( + agent_name, + agent_info, + agents, + llms, + tool_functions, + cache_handler_functions, + callbacks, + ) + + def _map_agent_variables( + self, + agent_name: str, + agent_info: Dict[str, Any], + agents: Dict[str, Callable], + llms: Dict[str, Callable], + tool_functions: Dict[str, Callable], + cache_handler_functions: Dict[str, Callable], + callbacks: Dict[str, Callable], + ) -> None: + if llm := agent_info.get("llm"): + self.agents_config[agent_name]["llm"] = llms[llm]() + + if tools := agent_info.get("tools"): + self.agents_config[agent_name]["tools"] = [ + tool_functions[tool]() for tool in tools + ] + + if function_calling_llm := agent_info.get("function_calling_llm"): + self.agents_config[agent_name]["function_calling_llm"] = agents[ + function_calling_llm + ]() + + if step_callback := agent_info.get("step_callback"): + self.agents_config[agent_name]["step_callback"] = callbacks[ + step_callback + ]() + + if cache_handler := agent_info.get("cache_handler"): + self.agents_config[agent_name]["cache_handler"] = ( + cache_handler_functions[cache_handler]() + ) + + def map_all_task_variables(self) -> None: + all_functions = self._get_all_functions() + agents = self._filter_functions(all_functions, "is_agent") + tasks = self._filter_functions(all_functions, "is_task") + output_json_functions = self._filter_functions( + all_functions, "is_output_json" + ) + tool_functions = self._filter_functions(all_functions, "is_tool") + callback_functions = self._filter_functions(all_functions, "is_callback") + output_pydantic_functions = self._filter_functions( + all_functions, "is_output_pydantic" + ) + + for task_name, task_info in self.tasks_config.items(): + self._map_task_variables( + task_name, + task_info, + agents, + tasks, + output_json_functions, + tool_functions, + callback_functions, + output_pydantic_functions, + ) + + def _map_task_variables( + self, + task_name: str, + task_info: Dict[str, Any], + agents: Dict[str, Callable], + tasks: Dict[str, Callable], + output_json_functions: Dict[str, Callable], + tool_functions: Dict[str, Callable], + callback_functions: Dict[str, Callable], + output_pydantic_functions: Dict[str, Callable], + ) -> None: + if context_list := task_info.get("context"): + self.tasks_config[task_name]["context"] = [ + tasks[context_task_name]() for context_task_name in context_list + ] + + if tools := task_info.get("tools"): + self.tasks_config[task_name]["tools"] = [ + tool_functions[tool]() for tool in tools + ] + + if agent_name := task_info.get("agent"): + self.tasks_config[task_name]["agent"] = agents[agent_name]() + + if output_json := task_info.get("output_json"): + self.tasks_config[task_name]["output_json"] = output_json_functions[ + output_json + ] + + if output_pydantic := task_info.get("output_pydantic"): + self.tasks_config[task_name]["output_pydantic"] = ( + output_pydantic_functions[output_pydantic] + ) + + if callbacks := task_info.get("callbacks"): + self.tasks_config[task_name]["callbacks"] = [ + callback_functions[callback]() for callback in callbacks + ] + return WrappedClass