eaglelandsonce commited on
Commit
c631b4c
1 Parent(s): 501bc76

Upload 17 files

Browse files
crewai/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from crewai.agent import Agent
2
+ from crewai.crew import Crew
3
+ from crewai.process import Process
4
+ from crewai.task import Task
crewai/agent.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from typing import Any, List, Optional
3
+
4
+ from langchain.agents.format_scratchpad import format_log_to_str
5
+ from langchain.chat_models import ChatOpenAI
6
+ from langchain.memory import ConversationSummaryMemory
7
+ from langchain.tools.render import render_text_description
8
+ from langchain_core.runnables.config import RunnableConfig
9
+ from pydantic import (
10
+ UUID4,
11
+ BaseModel,
12
+ ConfigDict,
13
+ Field,
14
+ InstanceOf,
15
+ field_validator,
16
+ model_validator,
17
+ )
18
+ from pydantic_core import PydanticCustomError
19
+
20
+ from crewai.agents import (
21
+ CacheHandler,
22
+ CrewAgentExecutor,
23
+ CrewAgentOutputParser,
24
+ ToolsHandler,
25
+ )
26
+ from crewai.prompts import Prompts
27
+
28
+
29
+ class Agent(BaseModel):
30
+ """Represents an agent in a system.
31
+
32
+ Each agent has a role, a goal, a backstory, and an optional language model (llm).
33
+ The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents.
34
+
35
+ Attributes:
36
+ agent_executor: An instance of the CrewAgentExecutor class.
37
+ role: The role of the agent.
38
+ goal: The objective of the agent.
39
+ backstory: The backstory of the agent.
40
+ llm: The language model that will run the agent.
41
+ memory: Whether the agent should have memory or not.
42
+ verbose: Whether the agent execution should be in verbose mode.
43
+ allow_delegation: Whether the agent is allowed to delegate tasks to other agents.
44
+ """
45
+
46
+ __hash__ = object.__hash__
47
+ model_config = ConfigDict(arbitrary_types_allowed=True)
48
+ id: UUID4 = Field(
49
+ default_factory=uuid.uuid4,
50
+ frozen=True,
51
+ description="Unique identifier for the object, not set by user.",
52
+ )
53
+ role: str = Field(description="Role of the agent")
54
+ goal: str = Field(description="Objective of the agent")
55
+ backstory: str = Field(description="Backstory of the agent")
56
+ llm: Optional[Any] = Field(
57
+ default_factory=lambda: ChatOpenAI(
58
+ temperature=0.7,
59
+ model_name="gpt-4",
60
+ ),
61
+ description="Language model that will run the agent.",
62
+ )
63
+ memory: bool = Field(
64
+ default=True, description="Whether the agent should have memory or not"
65
+ )
66
+ verbose: bool = Field(
67
+ default=False, description="Verbose mode for the Agent Execution"
68
+ )
69
+ allow_delegation: bool = Field(
70
+ default=True, description="Allow delegation of tasks to agents"
71
+ )
72
+ tools: List[Any] = Field(
73
+ default_factory=list, description="Tools at agents disposal"
74
+ )
75
+ agent_executor: Optional[InstanceOf[CrewAgentExecutor]] = Field(
76
+ default=None, description="An instance of the CrewAgentExecutor class."
77
+ )
78
+ tools_handler: Optional[InstanceOf[ToolsHandler]] = Field(
79
+ default=None, description="An instance of the ToolsHandler class."
80
+ )
81
+ cache_handler: Optional[InstanceOf[CacheHandler]] = Field(
82
+ default=CacheHandler(), description="An instance of the CacheHandler class."
83
+ )
84
+
85
+ @field_validator("id", mode="before")
86
+ @classmethod
87
+ def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
88
+ if v:
89
+ raise PydanticCustomError(
90
+ "may_not_set_field", "This field is not to be set by the user.", {}
91
+ )
92
+
93
+ @model_validator(mode="after")
94
+ def check_agent_executor(self) -> "Agent":
95
+ if not self.agent_executor:
96
+ self.set_cache_handler(self.cache_handler)
97
+ return self
98
+
99
+ def execute_task(
100
+ self, task: str, context: str = None, tools: List[Any] = None
101
+ ) -> str:
102
+ """Execute a task with the agent.
103
+
104
+ Args:
105
+ task: Task to execute.
106
+ context: Context to execute the task in.
107
+ tools: Tools to use for the task.
108
+
109
+ Returns:
110
+ Output of the agent
111
+ """
112
+ if context:
113
+ task = "\n".join(
114
+ [task, "\nThis is the context you are working with:", context]
115
+ )
116
+
117
+ tools = tools or self.tools
118
+ self.agent_executor.tools = tools
119
+
120
+ return self.agent_executor.invoke(
121
+ {
122
+ "input": task,
123
+ "tool_names": self.__tools_names(tools),
124
+ "tools": render_text_description(tools),
125
+ },
126
+ RunnableConfig(callbacks=[self.tools_handler]),
127
+ )["output"]
128
+
129
+ def set_cache_handler(self, cache_handler) -> None:
130
+ self.cache_handler = cache_handler
131
+ self.tools_handler = ToolsHandler(cache=self.cache_handler)
132
+ self.__create_agent_executor()
133
+
134
+ def __create_agent_executor(self) -> CrewAgentExecutor:
135
+ """Create an agent executor for the agent.
136
+
137
+ Returns:
138
+ An instance of the CrewAgentExecutor class.
139
+ """
140
+ agent_args = {
141
+ "input": lambda x: x["input"],
142
+ "tools": lambda x: x["tools"],
143
+ "tool_names": lambda x: x["tool_names"],
144
+ "agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]),
145
+ }
146
+ executor_args = {
147
+ "tools": self.tools,
148
+ "verbose": self.verbose,
149
+ "handle_parsing_errors": True,
150
+ }
151
+
152
+ if self.memory:
153
+ summary_memory = ConversationSummaryMemory(
154
+ llm=self.llm, memory_key="chat_history", input_key="input"
155
+ )
156
+ executor_args["memory"] = summary_memory
157
+ agent_args["chat_history"] = lambda x: x["chat_history"]
158
+ prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT
159
+ else:
160
+ prompt = Prompts.TASK_EXECUTION_PROMPT
161
+
162
+ execution_prompt = prompt.partial(
163
+ goal=self.goal,
164
+ role=self.role,
165
+ backstory=self.backstory,
166
+ )
167
+
168
+ bind = self.llm.bind(stop=["\nObservation"])
169
+ inner_agent = (
170
+ agent_args
171
+ | execution_prompt
172
+ | bind
173
+ | CrewAgentOutputParser(
174
+ tools_handler=self.tools_handler, cache=self.cache_handler
175
+ )
176
+ )
177
+ self.agent_executor = CrewAgentExecutor(agent=inner_agent, **executor_args)
178
+
179
+ @staticmethod
180
+ def __tools_names(tools) -> str:
181
+ return ", ".join([t.name for t in tools])
crewai/agents/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .cache.cache_handler import CacheHandler
2
+ from .executor import CrewAgentExecutor
3
+ from .output_parser import CrewAgentOutputParser
4
+ from .tools_handler import ToolsHandler
crewai/agents/cache/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .cache_handler import CacheHandler
2
+ from .cache_hit import CacheHit
crewai/agents/cache/cache_handler.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from pydantic import PrivateAttr
4
+
5
+
6
+ class CacheHandler:
7
+ """Callback handler for tool usage."""
8
+
9
+ _cache: PrivateAttr = {}
10
+
11
+ def __init__(self):
12
+ self._cache = {}
13
+
14
+ def add(self, tool, input, output):
15
+ input = input.strip()
16
+ self._cache[f"{tool}-{input}"] = output
17
+
18
+ def read(self, tool, input) -> Optional[str]:
19
+ input = input.strip()
20
+ return self._cache.get(f"{tool}-{input}")
crewai/agents/cache/cache_hit.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.agents import AgentAction
2
+ from pydantic.v1 import BaseModel, Field
3
+
4
+ from .cache_handler import CacheHandler
5
+
6
+
7
+ class CacheHit(BaseModel):
8
+ """Cache Hit Object."""
9
+
10
+ class Config:
11
+ arbitrary_types_allowed = True
12
+
13
+ action: AgentAction = Field(description="Action taken")
14
+ cache: CacheHandler = Field(description="Cache Handler for the tool")
crewai/agents/exceptions.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.exceptions import OutputParserException
2
+
3
+
4
+ class TaskRepeatedUsageException(OutputParserException):
5
+ """Exception raised when a task is used twice in a roll."""
6
+
7
+ error: str = "TaskRepeatedUsageException"
8
+ message: str = "\nI just used the {tool} tool with input {tool_input}. So I already know the result of that.\n"
9
+
10
+ def __init__(self, tool: str, tool_input: str):
11
+ self.tool = tool
12
+ self.tool_input = tool_input
13
+ self.message = self.message.format(tool=tool, tool_input=tool_input)
14
+
15
+ super().__init__(
16
+ error=self.error, observation=self.message, send_to_llm=True, llm_output=""
17
+ )
18
+
19
+ def __str__(self):
20
+ return self.message
crewai/agents/executor.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Iterator, List, Optional, Tuple, Union
2
+
3
+ from langchain.agents import AgentExecutor
4
+ from langchain.agents.agent import ExceptionTool
5
+ from langchain.agents.tools import InvalidTool
6
+ from langchain.callbacks.manager import CallbackManagerForChainRun
7
+ from langchain_core.agents import AgentAction, AgentFinish, AgentStep
8
+ from langchain_core.exceptions import OutputParserException
9
+ from langchain_core.tools import BaseTool
10
+
11
+ from ..tools.cache_tools import CacheTools
12
+ from .cache.cache_hit import CacheHit
13
+
14
+
15
+ class CrewAgentExecutor(AgentExecutor):
16
+ def _iter_next_step(
17
+ self,
18
+ name_to_tool_map: Dict[str, BaseTool],
19
+ color_mapping: Dict[str, str],
20
+ inputs: Dict[str, str],
21
+ intermediate_steps: List[Tuple[AgentAction, str]],
22
+ run_manager: Optional[CallbackManagerForChainRun] = None,
23
+ ) -> Iterator[Union[AgentFinish, AgentAction, AgentStep]]:
24
+ """Take a single step in the thought-action-observation loop.
25
+
26
+ Override this to take control of how the agent makes and acts on choices.
27
+ """
28
+ try:
29
+ intermediate_steps = self._prepare_intermediate_steps(intermediate_steps)
30
+
31
+ # Call the LLM to see what to do.
32
+ output = self.agent.plan(
33
+ intermediate_steps,
34
+ callbacks=run_manager.get_child() if run_manager else None,
35
+ **inputs,
36
+ )
37
+ except OutputParserException as e:
38
+ if isinstance(self.handle_parsing_errors, bool):
39
+ raise_error = not self.handle_parsing_errors
40
+ else:
41
+ raise_error = False
42
+ if raise_error:
43
+ raise ValueError(
44
+ "An output parsing error occurred. "
45
+ "In order to pass this error back to the agent and have it try "
46
+ "again, pass `handle_parsing_errors=True` to the AgentExecutor. "
47
+ f"This is the error: {str(e)}"
48
+ )
49
+ text = str(e)
50
+ if isinstance(self.handle_parsing_errors, bool):
51
+ if e.send_to_llm:
52
+ observation = str(e.observation)
53
+ text = str(e.llm_output)
54
+ else:
55
+ observation = "Invalid or incomplete response"
56
+ elif isinstance(self.handle_parsing_errors, str):
57
+ observation = self.handle_parsing_errors
58
+ elif callable(self.handle_parsing_errors):
59
+ observation = self.handle_parsing_errors(e)
60
+ else:
61
+ raise ValueError("Got unexpected type of `handle_parsing_errors`")
62
+ output = AgentAction("_Exception", observation, text)
63
+ if run_manager:
64
+ run_manager.on_agent_action(output, color="green")
65
+ tool_run_kwargs = self.agent.tool_run_logging_kwargs()
66
+ observation = ExceptionTool().run(
67
+ output.tool_input,
68
+ verbose=self.verbose,
69
+ color=None,
70
+ callbacks=run_manager.get_child() if run_manager else None,
71
+ **tool_run_kwargs,
72
+ )
73
+ yield AgentStep(action=output, observation=observation)
74
+ return
75
+
76
+ # If the tool chosen is the finishing tool, then we end and return.
77
+ if isinstance(output, AgentFinish):
78
+ yield output
79
+ return
80
+
81
+ # Override tool usage to use CacheTools
82
+ if isinstance(output, CacheHit):
83
+ cache = output.cache
84
+ action = output.action
85
+ tool = CacheTools(cache_handler=cache).tool()
86
+ output = action.copy()
87
+ output.tool_input = f"tool:{action.tool}|input:{action.tool_input}"
88
+ output.tool = tool.name
89
+ name_to_tool_map[tool.name] = tool
90
+ color_mapping[tool.name] = color_mapping[action.tool]
91
+
92
+ actions: List[AgentAction]
93
+ if isinstance(output, AgentAction):
94
+ actions = [output]
95
+ else:
96
+ actions = output
97
+ for agent_action in actions:
98
+ yield agent_action
99
+ for agent_action in actions:
100
+ if run_manager:
101
+ run_manager.on_agent_action(agent_action, color="green")
102
+ # Otherwise we lookup the tool
103
+ if agent_action.tool in name_to_tool_map:
104
+ tool = name_to_tool_map[agent_action.tool]
105
+ return_direct = tool.return_direct
106
+ color = color_mapping[agent_action.tool]
107
+ tool_run_kwargs = self.agent.tool_run_logging_kwargs()
108
+ if return_direct:
109
+ tool_run_kwargs["llm_prefix"] = ""
110
+ # We then call the tool on the tool input to get an observation
111
+ observation = tool.run(
112
+ agent_action.tool_input,
113
+ verbose=self.verbose,
114
+ color=color,
115
+ callbacks=run_manager.get_child() if run_manager else None,
116
+ **tool_run_kwargs,
117
+ )
118
+ else:
119
+ tool_run_kwargs = self.agent.tool_run_logging_kwargs()
120
+ observation = InvalidTool().run(
121
+ {
122
+ "requested_tool_name": agent_action.tool,
123
+ "available_tool_names": list(name_to_tool_map.keys()),
124
+ },
125
+ verbose=self.verbose,
126
+ color=None,
127
+ callbacks=run_manager.get_child() if run_manager else None,
128
+ **tool_run_kwargs,
129
+ )
130
+ yield AgentStep(action=agent_action, observation=observation)
crewai/agents/output_parser.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from typing import Union
3
+
4
+ from langchain.agents.output_parsers import ReActSingleInputOutputParser
5
+ from langchain_core.agents import AgentAction, AgentFinish
6
+
7
+ from .cache import CacheHandler, CacheHit
8
+ from .exceptions import TaskRepeatedUsageException
9
+ from .tools_handler import ToolsHandler
10
+
11
+ FINAL_ANSWER_ACTION = "Final Answer:"
12
+ FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE = (
13
+ "Parsing LLM output produced both a final answer and a parse-able action:"
14
+ )
15
+
16
+
17
+ class CrewAgentOutputParser(ReActSingleInputOutputParser):
18
+ """Parses ReAct-style LLM calls that have a single tool input.
19
+
20
+ Expects output to be in one of two formats.
21
+
22
+ If the output signals that an action should be taken,
23
+ should be in the below format. This will result in an AgentAction
24
+ being returned.
25
+
26
+ ```
27
+ Thought: agent thought here
28
+ Action: search
29
+ Action Input: what is the temperature in SF?
30
+ ```
31
+
32
+ If the output signals that a final answer should be given,
33
+ should be in the below format. This will result in an AgentFinish
34
+ being returned.
35
+
36
+ ```
37
+ Thought: agent thought here
38
+ Final Answer: The temperature is 100 degrees
39
+ ```
40
+
41
+ It also prevents tools from being reused in a roll.
42
+ """
43
+
44
+ class Config:
45
+ arbitrary_types_allowed = True
46
+
47
+ tools_handler: ToolsHandler
48
+ cache: CacheHandler
49
+
50
+ def parse(self, text: str) -> Union[AgentAction, AgentFinish, CacheHit]:
51
+ FINAL_ANSWER_ACTION in text
52
+ regex = (
53
+ r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
54
+ )
55
+ action_match = re.search(regex, text, re.DOTALL)
56
+ if action_match:
57
+ action = action_match.group(1).strip()
58
+ action_input = action_match.group(2)
59
+ tool_input = action_input.strip(" ")
60
+ tool_input = tool_input.strip('"')
61
+
62
+ last_tool_usage = self.tools_handler.last_used_tool
63
+ if last_tool_usage:
64
+ usage = {
65
+ "tool": action,
66
+ "input": tool_input,
67
+ }
68
+ if usage == last_tool_usage:
69
+ raise TaskRepeatedUsageException(tool=action, tool_input=tool_input)
70
+
71
+ result = self.cache.read(action, tool_input)
72
+ if result:
73
+ action = AgentAction(action, tool_input, text)
74
+ return CacheHit(action=action, cache=self.cache)
75
+
76
+ return super().parse(text)
crewai/agents/tools_handler.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict
2
+
3
+ from langchain.callbacks.base import BaseCallbackHandler
4
+
5
+ from ..tools.cache_tools import CacheTools
6
+ from .cache.cache_handler import CacheHandler
7
+
8
+
9
+ class ToolsHandler(BaseCallbackHandler):
10
+ """Callback handler for tool usage."""
11
+
12
+ last_used_tool: Dict[str, Any] = {}
13
+ cache: CacheHandler = None
14
+
15
+ def __init__(self, cache: CacheHandler = None, **kwargs: Any):
16
+ """Initialize the callback handler."""
17
+ self.cache = cache
18
+ super().__init__(**kwargs)
19
+
20
+ def on_tool_start(
21
+ self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
22
+ ) -> Any:
23
+ """Run when tool starts running."""
24
+ name = serialized.get("name")
25
+ if name not in ["invalid_tool", "_Exception"]:
26
+ tools_usage = {
27
+ "tool": name,
28
+ "input": input_str,
29
+ }
30
+ self.last_used_tool = tools_usage
31
+
32
+ def on_tool_end(self, output: str, **kwargs: Any) -> Any:
33
+ """Run when tool ends running."""
34
+ if (
35
+ "is not a valid tool" not in output
36
+ and "Invalid or incomplete response" not in output
37
+ and "Invalid Format" not in output
38
+ ):
39
+ if self.last_used_tool["tool"] != CacheTools().name:
40
+ self.cache.add(
41
+ tool=self.last_used_tool["tool"],
42
+ input=self.last_used_tool["input"],
43
+ output=output,
44
+ )
crewai/crew.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import uuid
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from pydantic import (
6
+ UUID4,
7
+ BaseModel,
8
+ ConfigDict,
9
+ Field,
10
+ InstanceOf,
11
+ Json,
12
+ field_validator,
13
+ model_validator,
14
+ )
15
+ from pydantic_core import PydanticCustomError
16
+
17
+ from crewai.agent import Agent
18
+ from crewai.agents.cache import CacheHandler
19
+ from crewai.process import Process
20
+ from crewai.task import Task
21
+ from crewai.tools.agent_tools import AgentTools
22
+
23
+
24
+ class Crew(BaseModel):
25
+ """Class that represents a group of agents, how they should work together and their tasks."""
26
+
27
+ __hash__ = object.__hash__
28
+ model_config = ConfigDict(arbitrary_types_allowed=True)
29
+ tasks: List[Task] = Field(description="List of tasks", default_factory=list)
30
+ agents: List[Agent] = Field(
31
+ description="List of agents in this crew.", default_factory=list
32
+ )
33
+ process: Process = Field(
34
+ description="Process that the crew will follow.", default=Process.sequential
35
+ )
36
+ verbose: Union[int, bool] = Field(
37
+ description="Verbose mode for the Agent Execution", default=0
38
+ )
39
+ config: Optional[Union[Json, Dict[str, Any]]] = Field(
40
+ description="Configuration of the crew.", default=None
41
+ )
42
+ cache_handler: Optional[InstanceOf[CacheHandler]] = Field(
43
+ default=CacheHandler(), description="An instance of the CacheHandler class."
44
+ )
45
+ id: UUID4 = Field(
46
+ default_factory=uuid.uuid4,
47
+ frozen=True,
48
+ description="Unique identifier for the object, not set by user.",
49
+ )
50
+
51
+ @field_validator("id", mode="before")
52
+ @classmethod
53
+ def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
54
+ if v:
55
+ raise PydanticCustomError(
56
+ "may_not_set_field", "This field is not to be set by the user.", {}
57
+ )
58
+
59
+ @classmethod
60
+ @field_validator("config", mode="before")
61
+ def check_config_type(cls, v: Union[Json, Dict[str, Any]]):
62
+ if isinstance(v, Json):
63
+ return json.loads(v)
64
+ return v
65
+
66
+ @model_validator(mode="after")
67
+ def check_config(self):
68
+ if not self.config and not self.tasks and not self.agents:
69
+ raise PydanticCustomError(
70
+ "missing_keys", "Either agents and task need to be set or config.", {}
71
+ )
72
+
73
+ if self.config:
74
+ if not self.config.get("agents") or not self.config.get("tasks"):
75
+ raise PydanticCustomError(
76
+ "missing_keys_in_config", "Config should have agents and tasks", {}
77
+ )
78
+
79
+ self.agents = [Agent(**agent) for agent in self.config["agents"]]
80
+
81
+ tasks = []
82
+ for task in self.config["tasks"]:
83
+ task_agent = [agt for agt in self.agents if agt.role == task["agent"]][
84
+ 0
85
+ ]
86
+ del task["agent"]
87
+ tasks.append(Task(**task, agent=task_agent))
88
+
89
+ self.tasks = tasks
90
+
91
+ if self.agents:
92
+ for agent in self.agents:
93
+ agent.set_cache_handler(self.cache_handler)
94
+ return self
95
+
96
+ def kickoff(self) -> str:
97
+ """Kickoff the crew to work on its tasks.
98
+
99
+ Returns:
100
+ Output of the crew for each task.
101
+ """
102
+ for agent in self.agents:
103
+ agent.cache_handler = self.cache_handler
104
+
105
+ if self.process == Process.sequential:
106
+ return self.__sequential_loop()
107
+
108
+ def __sequential_loop(self) -> str:
109
+ """Loop that executes the sequential process.
110
+
111
+ Returns:
112
+ Output of the crew.
113
+ """
114
+ task_outcome = None
115
+ for task in self.tasks:
116
+ # Add delegation tools to the task if the agent allows it
117
+ if task.agent.allow_delegation:
118
+ tools = AgentTools(agents=self.agents).tools()
119
+ task.tools += tools
120
+
121
+ self.__log("debug", f"Working Agent: {task.agent.role}")
122
+ self.__log("info", f"Starting Task: {task.description} ...")
123
+
124
+ task_outcome = task.execute(task_outcome)
125
+
126
+ self.__log("debug", f"Task output: {task_outcome}")
127
+
128
+ return task_outcome
129
+
130
+ def __log(self, level, message):
131
+ """Log a message"""
132
+ level_map = {"debug": 1, "info": 2}
133
+ verbose_level = (
134
+ 2 if isinstance(self.verbose, bool) and self.verbose else self.verbose
135
+ )
136
+ if verbose_level and level_map[level] <= verbose_level:
137
+ print(message)
crewai/process.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+
3
+
4
+ class Process(str, Enum):
5
+ """
6
+ Class representing the different processes that can be used to tackle tasks
7
+ """
8
+
9
+ sequential = "sequential"
10
+ # TODO: consensual = 'consensual'
11
+ # TODO: hierarchical = 'hierarchical'
crewai/prompts.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prompts for generic agent."""
2
+
3
+ from textwrap import dedent
4
+ from typing import ClassVar
5
+
6
+ from langchain.prompts import PromptTemplate
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class Prompts(BaseModel):
11
+ """Prompts for generic agent."""
12
+
13
+ TASK_SLICE: ClassVar[str] = dedent(
14
+ """\
15
+ Begin! This is VERY important to you, your job depends on it!
16
+
17
+ Current Task: {input}"""
18
+ )
19
+
20
+ SCRATCHPAD_SLICE: ClassVar[str] = "\n{agent_scratchpad}"
21
+
22
+ MEMORY_SLICE: ClassVar[str] = dedent(
23
+ """\
24
+ This is the summary of your work so far:
25
+ {chat_history}"""
26
+ )
27
+
28
+ ROLE_PLAYING_SLICE: ClassVar[str] = dedent(
29
+ """\
30
+ You are {role}.
31
+ {backstory}
32
+
33
+ Your personal goal is: {goal}"""
34
+ )
35
+
36
+ TOOLS_SLICE: ClassVar[str] = dedent(
37
+ """\
38
+
39
+
40
+ TOOLS:
41
+ ------
42
+ You have access to the following tools:
43
+
44
+ {tools}
45
+
46
+ To use a tool, please use the exact following format:
47
+
48
+ ```
49
+ Thought: Do I need to use a tool? Yes
50
+ Action: the action to take, should be one of [{tool_names}], just the name.
51
+ Action Input: the input to the action
52
+ Observation: the result of the action
53
+ ```
54
+
55
+ When you have a response for your task, or if you do not need to use a tool, you MUST use the format:
56
+
57
+ ```
58
+ Thought: Do I need to use a tool? No
59
+ Final Answer: [your response here]
60
+ ```"""
61
+ )
62
+
63
+ VOTING_SLICE: ClassVar[str] = dedent(
64
+ """\
65
+ You are working on a crew with your co-workers and need to decide who will execute the task.
66
+
67
+ These are your format instructions:
68
+ {format_instructions}
69
+
70
+ These are your co-workers and their roles:
71
+ {coworkers}"""
72
+ )
73
+
74
+ TASK_EXECUTION_WITH_MEMORY_PROMPT: ClassVar[str] = PromptTemplate.from_template(
75
+ ROLE_PLAYING_SLICE + TOOLS_SLICE + MEMORY_SLICE + TASK_SLICE + SCRATCHPAD_SLICE
76
+ )
77
+
78
+ TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template(
79
+ ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE + SCRATCHPAD_SLICE
80
+ )
81
+
82
+ CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template(
83
+ ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE + SCRATCHPAD_SLICE
84
+ )
crewai/task.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from typing import Any, List, Optional
3
+
4
+ from pydantic import UUID4, BaseModel, Field, field_validator, model_validator
5
+ from pydantic_core import PydanticCustomError
6
+
7
+ from crewai.agent import Agent
8
+
9
+
10
+ class Task(BaseModel):
11
+ """Class that represent a task to be executed."""
12
+
13
+ __hash__ = object.__hash__
14
+ description: str = Field(description="Description of the actual task.")
15
+ agent: Optional[Agent] = Field(
16
+ description="Agent responsible for the task.", default=None
17
+ )
18
+ tools: List[Any] = Field(
19
+ default_factory=list,
20
+ description="Tools the agent are limited to use for this task.",
21
+ )
22
+ id: UUID4 = Field(
23
+ default_factory=uuid.uuid4,
24
+ frozen=True,
25
+ description="Unique identifier for the object, not set by user.",
26
+ )
27
+
28
+ @field_validator("id", mode="before")
29
+ @classmethod
30
+ def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
31
+ if v:
32
+ raise PydanticCustomError(
33
+ "may_not_set_field", "This field is not to be set by the user.", {}
34
+ )
35
+
36
+ @model_validator(mode="after")
37
+ def check_tools(self):
38
+ if not self.tools and (self.agent and self.agent.tools):
39
+ self.tools.extend(self.agent.tools)
40
+ return self
41
+
42
+ def execute(self, context: str = None) -> str:
43
+ """Execute the task.
44
+
45
+ Returns:
46
+ Output of the task.
47
+ """
48
+ if self.agent:
49
+ return self.agent.execute_task(
50
+ task=self.description, context=context, tools=self.tools
51
+ )
52
+ else:
53
+ raise Exception(
54
+ f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, either consensual or hierarchical."
55
+ )
crewai/tools/__init__.py ADDED
File without changes
crewai/tools/agent_tools.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from textwrap import dedent
2
+ from typing import List
3
+
4
+ from langchain.tools import Tool
5
+ from pydantic import BaseModel, Field
6
+
7
+ from crewai.agent import Agent
8
+
9
+
10
+ class AgentTools(BaseModel):
11
+ """Tools for generic agent."""
12
+
13
+ agents: List[Agent] = Field(description="List of agents in this crew.")
14
+
15
+ def tools(self):
16
+ return [
17
+ Tool.from_function(
18
+ func=self.delegate_work,
19
+ name="Delegate work to co-worker",
20
+ description=dedent(
21
+ f"""Useful to delegate a specific task to one of the
22
+ following co-workers: [{', '.join([agent.role for agent in self.agents])}].
23
+ The input to this tool should be a pipe (|) separated text of length
24
+ three, representing the role you want to delegate it to, the task and
25
+ information necessary. For example, `coworker|task|information`.
26
+ """
27
+ ),
28
+ ),
29
+ Tool.from_function(
30
+ func=self.ask_question,
31
+ name="Ask question to co-worker",
32
+ description=dedent(
33
+ f"""Useful to ask a question, opinion or take from on
34
+ of the following co-workers: [{', '.join([agent.role for agent in self.agents])}].
35
+ The input to this tool should be a pipe (|) separated text of length
36
+ three, representing the role you want to ask it to, the question and
37
+ information necessary. For example, `coworker|question|information`.
38
+ """
39
+ ),
40
+ ),
41
+ ]
42
+
43
+ def delegate_work(self, command):
44
+ """Useful to delegate a specific task to a coworker."""
45
+ return self.__execute(command)
46
+
47
+ def ask_question(self, command):
48
+ """Useful to ask a question, opinion or take from a coworker."""
49
+ return self.__execute(command)
50
+
51
+ def __execute(self, command):
52
+ """Execute the command."""
53
+ try:
54
+ agent, task, information = command.split("|")
55
+ except ValueError:
56
+ return "\nError executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|information`.\n"
57
+
58
+ if not agent or not task or not information:
59
+ return "\nError executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|question|information`.\n"
60
+
61
+ agent = [
62
+ available_agent
63
+ for available_agent in self.agents
64
+ if available_agent.role == agent
65
+ ]
66
+
67
+ if len(agent) == 0:
68
+ return f"\nError executing tool. Co-worker mentioned on the Action Input not found, it must to be one of the following options: {', '.join([agent.role for agent in self.agents])}.\n"
69
+
70
+ agent = agent[0]
71
+ result = agent.execute_task(task, information)
72
+ return result
crewai/tools/cache_tools.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.tools import Tool
2
+ from pydantic import BaseModel, ConfigDict, Field
3
+
4
+ from crewai.agents.cache import CacheHandler
5
+
6
+
7
+ class CacheTools(BaseModel):
8
+ """Default tools to hit the cache."""
9
+
10
+ model_config = ConfigDict(arbitrary_types_allowed=True)
11
+ name: str = "Hit Cache"
12
+ cache_handler: CacheHandler = Field(
13
+ description="Cache Handler for the crew",
14
+ default=CacheHandler(),
15
+ )
16
+
17
+ def tool(self):
18
+ return Tool.from_function(
19
+ func=self.hit_cache,
20
+ name=self.name,
21
+ description="Reads directly from the cache",
22
+ )
23
+
24
+ def hit_cache(self, key):
25
+ split = key.split("tool:")
26
+ tool = split[1].split("|input:")[0].strip()
27
+ tool_input = split[1].split("|input:")[1].strip()
28
+ return self.cache_handler.read(tool, tool_input)