eaglelandsonce commited on
Commit
94ea28c
1 Parent(s): baf2279

Upload 27 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,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from typing import Any, List, Optional
3
+
4
+ from langchain.agents.agent import RunnableAgent
5
+ from langchain.agents.format_scratchpad import format_log_to_str
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 langchain_openai import ChatOpenAI
10
+ from pydantic import (
11
+ UUID4,
12
+ BaseModel,
13
+ ConfigDict,
14
+ Field,
15
+ InstanceOf,
16
+ PrivateAttr,
17
+ field_validator,
18
+ model_validator,
19
+ )
20
+ from pydantic_core import PydanticCustomError
21
+
22
+ from crewai.agents import (
23
+ CacheHandler,
24
+ CrewAgentExecutor,
25
+ CrewAgentOutputParser,
26
+ ToolsHandler,
27
+ )
28
+ from crewai.utilities import I18N, Logger, Prompts, RPMController
29
+
30
+
31
+ class Agent(BaseModel):
32
+ """Represents an agent in a system.
33
+
34
+ Each agent has a role, a goal, a backstory, and an optional language model (llm).
35
+ The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents.
36
+
37
+ Attributes:
38
+ agent_executor: An instance of the CrewAgentExecutor class.
39
+ role: The role of the agent.
40
+ goal: The objective of the agent.
41
+ backstory: The backstory of the agent.
42
+ llm: The language model that will run the agent.
43
+ max_iter: Maximum number of iterations for an agent to execute a task.
44
+ memory: Whether the agent should have memory or not.
45
+ max_rpm: Maximum number of requests per minute for the agent execution to be respected.
46
+ verbose: Whether the agent execution should be in verbose mode.
47
+ allow_delegation: Whether the agent is allowed to delegate tasks to other agents.
48
+ tools: Tools at agents disposal
49
+ """
50
+
51
+ __hash__ = object.__hash__ # type: ignore
52
+ _logger: Logger = PrivateAttr()
53
+ _rpm_controller: RPMController = PrivateAttr(default=None)
54
+ _request_within_rpm_limit: Any = PrivateAttr(default=None)
55
+
56
+ model_config = ConfigDict(arbitrary_types_allowed=True)
57
+ id: UUID4 = Field(
58
+ default_factory=uuid.uuid4,
59
+ frozen=True,
60
+ description="Unique identifier for the object, not set by user.",
61
+ )
62
+ role: str = Field(description="Role of the agent")
63
+ goal: str = Field(description="Objective of the agent")
64
+ backstory: str = Field(description="Backstory of the agent")
65
+ max_rpm: Optional[int] = Field(
66
+ default=None,
67
+ description="Maximum number of requests per minute for the agent execution to be respected.",
68
+ )
69
+ memory: bool = Field(
70
+ default=True, description="Whether the agent should have memory or not"
71
+ )
72
+ verbose: bool = Field(
73
+ default=False, description="Verbose mode for the Agent Execution"
74
+ )
75
+ allow_delegation: bool = Field(
76
+ default=True, description="Allow delegation of tasks to agents"
77
+ )
78
+ tools: List[Any] = Field(
79
+ default_factory=list, description="Tools at agents disposal"
80
+ )
81
+ max_iter: Optional[int] = Field(
82
+ default=15, description="Maximum iterations for an agent to execute a task"
83
+ )
84
+ agent_executor: InstanceOf[CrewAgentExecutor] = Field(
85
+ default=None, description="An instance of the CrewAgentExecutor class."
86
+ )
87
+ tools_handler: InstanceOf[ToolsHandler] = Field(
88
+ default=None, description="An instance of the ToolsHandler class."
89
+ )
90
+ cache_handler: InstanceOf[CacheHandler] = Field(
91
+ default=CacheHandler(), description="An instance of the CacheHandler class."
92
+ )
93
+ i18n: I18N = Field(default=I18N(), description="Internationalization settings.")
94
+ llm: Any = Field(
95
+ default_factory=lambda: ChatOpenAI(
96
+ model="gpt-4",
97
+ ),
98
+ description="Language model that will run the agent.",
99
+ )
100
+
101
+ @field_validator("id", mode="before")
102
+ @classmethod
103
+ def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
104
+ if v:
105
+ raise PydanticCustomError(
106
+ "may_not_set_field", "This field is not to be set by the user.", {}
107
+ )
108
+
109
+ @model_validator(mode="after")
110
+ def set_private_attrs(self):
111
+ """Set private attributes."""
112
+ self._logger = Logger(self.verbose)
113
+ if self.max_rpm and not self._rpm_controller:
114
+ self._rpm_controller = RPMController(
115
+ max_rpm=self.max_rpm, logger=self._logger
116
+ )
117
+ return self
118
+
119
+ @model_validator(mode="after")
120
+ def check_agent_executor(self) -> "Agent":
121
+ """Check if the agent executor is set."""
122
+ if not self.agent_executor:
123
+ self.set_cache_handler(self.cache_handler)
124
+ return self
125
+
126
+ def execute_task(
127
+ self,
128
+ task: str,
129
+ context: Optional[str] = None,
130
+ tools: Optional[List[Any]] = None,
131
+ ) -> str:
132
+ """Execute a task with the agent.
133
+
134
+ Args:
135
+ task: Task to execute.
136
+ context: Context to execute the task in.
137
+ tools: Tools to use for the task.
138
+
139
+ Returns:
140
+ Output of the agent
141
+ """
142
+
143
+ if context:
144
+ task = self.i18n.slice("task_with_context").format(
145
+ task=task, context=context
146
+ )
147
+
148
+ tools = tools or self.tools
149
+ self.agent_executor.tools = tools
150
+
151
+ result = self.agent_executor.invoke(
152
+ {
153
+ "input": task,
154
+ "tool_names": self.__tools_names(tools),
155
+ "tools": render_text_description(tools),
156
+ },
157
+ RunnableConfig(callbacks=[self.tools_handler]),
158
+ )["output"]
159
+
160
+ if self.max_rpm:
161
+ self._rpm_controller.stop_rpm_counter()
162
+
163
+ return result
164
+
165
+ def set_cache_handler(self, cache_handler: CacheHandler) -> None:
166
+ """Set the cache handler for the agent.
167
+
168
+ Args:
169
+ cache_handler: An instance of the CacheHandler class.
170
+ """
171
+ self.cache_handler = cache_handler
172
+ self.tools_handler = ToolsHandler(cache=self.cache_handler)
173
+ self._create_agent_executor()
174
+
175
+ def set_rpm_controller(self, rpm_controller: RPMController) -> None:
176
+ """Set the rpm controller for the agent.
177
+
178
+ Args:
179
+ rpm_controller: An instance of the RPMController class.
180
+ """
181
+ if not self._rpm_controller:
182
+ self._rpm_controller = rpm_controller
183
+ self._create_agent_executor()
184
+
185
+ def _create_agent_executor(self) -> None:
186
+ """Create an agent executor for the agent.
187
+
188
+ Returns:
189
+ An instance of the CrewAgentExecutor class.
190
+ """
191
+ agent_args = {
192
+ "input": lambda x: x["input"],
193
+ "tools": lambda x: x["tools"],
194
+ "tool_names": lambda x: x["tool_names"],
195
+ "agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]),
196
+ }
197
+ executor_args = {
198
+ "i18n": self.i18n,
199
+ "tools": self.tools,
200
+ "verbose": self.verbose,
201
+ "handle_parsing_errors": True,
202
+ "max_iterations": self.max_iter,
203
+ }
204
+
205
+ if self._rpm_controller:
206
+ executor_args["request_within_rpm_limit"] = (
207
+ self._rpm_controller.check_or_wait
208
+ )
209
+
210
+ if self.memory:
211
+ summary_memory = ConversationSummaryMemory(
212
+ llm=self.llm, input_key="input", memory_key="chat_history"
213
+ )
214
+ executor_args["memory"] = summary_memory
215
+ agent_args["chat_history"] = lambda x: x["chat_history"]
216
+ prompt = Prompts(i18n=self.i18n).task_execution_with_memory()
217
+ else:
218
+ prompt = Prompts(i18n=self.i18n).task_execution()
219
+
220
+ execution_prompt = prompt.partial(
221
+ goal=self.goal,
222
+ role=self.role,
223
+ backstory=self.backstory,
224
+ )
225
+
226
+ bind = self.llm.bind(stop=[self.i18n.slice("observation")])
227
+ inner_agent = (
228
+ agent_args
229
+ | execution_prompt
230
+ | bind
231
+ | CrewAgentOutputParser(
232
+ tools_handler=self.tools_handler,
233
+ cache=self.cache_handler,
234
+ i18n=self.i18n,
235
+ )
236
+ )
237
+ self.agent_executor = CrewAgentExecutor(
238
+ agent=RunnableAgent(runnable=inner_agent), **executor_args
239
+ )
240
+
241
+ @staticmethod
242
+ def __tools_names(tools) -> str:
243
+ 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,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+
4
+ class CacheHandler:
5
+ """Callback handler for tool usage."""
6
+
7
+ _cache: dict = {}
8
+
9
+ def __init__(self):
10
+ self._cache = {}
11
+
12
+ def add(self, tool, input, output):
13
+ input = input.strip()
14
+ self._cache[f"{tool}-{input}"] = output
15
+
16
+ def read(self, tool, input) -> Optional[str]:
17
+ input = input.strip()
18
+ return self._cache.get(f"{tool}-{input}")
crewai/agents/cache/cache_hit.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from .cache_handler import CacheHandler
6
+
7
+
8
+ class CacheHit(BaseModel):
9
+ """Cache Hit Object."""
10
+
11
+ class Config:
12
+ arbitrary_types_allowed = True
13
+
14
+ # Making it Any instead of AgentAction to avoind
15
+ # pydantic v1 vs v2 incompatibility, langchain should
16
+ # soon be updated to pydantic v2
17
+ action: Any = Field(description="Action taken")
18
+ cache: CacheHandler = Field(description="Cache Handler for the tool")
crewai/agents/exceptions.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.exceptions import OutputParserException
2
+
3
+ from crewai.utilities import I18N
4
+
5
+
6
+ class TaskRepeatedUsageException(OutputParserException):
7
+ """Exception raised when a task is used twice in a roll."""
8
+
9
+ i18n: I18N = I18N()
10
+ error: str = "TaskRepeatedUsageException"
11
+ message: str
12
+
13
+ def __init__(self, i18n: I18N, tool: str, tool_input: str, text: str):
14
+ self.i18n = i18n
15
+ self.text = text
16
+ self.tool = tool
17
+ self.tool_input = tool_input
18
+ self.message = self.i18n.errors("task_repeated_usage").format(
19
+ tool=tool, tool_input=tool_input
20
+ )
21
+
22
+ super().__init__(
23
+ error=self.error,
24
+ observation=self.message,
25
+ send_to_llm=True,
26
+ llm_output=self.text,
27
+ )
28
+
29
+ def __str__(self):
30
+ return self.message
crewai/agents/executor.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
3
+
4
+ from langchain.agents import AgentExecutor
5
+ from langchain.agents.agent import ExceptionTool
6
+ from langchain.agents.tools import InvalidTool
7
+ from langchain.callbacks.manager import CallbackManagerForChainRun
8
+ from langchain_core.agents import AgentAction, AgentFinish, AgentStep
9
+ from langchain_core.exceptions import OutputParserException
10
+ from langchain_core.pydantic_v1 import root_validator
11
+ from langchain_core.tools import BaseTool
12
+ from langchain_core.utils.input import get_color_mapping
13
+
14
+ from crewai.agents.cache.cache_hit import CacheHit
15
+ from crewai.tools.cache_tools import CacheTools
16
+ from crewai.utilities import I18N
17
+
18
+
19
+ class CrewAgentExecutor(AgentExecutor):
20
+ i18n: I18N = I18N()
21
+ iterations: int = 0
22
+ request_within_rpm_limit: Any = None
23
+ max_iterations: Optional[int] = 15
24
+ force_answer_max_iterations: Optional[int] = None
25
+
26
+ @root_validator()
27
+ def set_force_answer_max_iterations(cls, values: Dict) -> Dict:
28
+ values["force_answer_max_iterations"] = values["max_iterations"] - 2
29
+ return values
30
+
31
+ def _should_force_answer(self) -> bool:
32
+ return True if self.iterations == self.force_answer_max_iterations else False
33
+
34
+ def _force_answer(self, output: AgentAction):
35
+ return AgentStep(
36
+ action=output, observation=self.i18n.errors("force_final_answer")
37
+ )
38
+
39
+ def _call(
40
+ self,
41
+ inputs: Dict[str, str],
42
+ run_manager: Optional[CallbackManagerForChainRun] = None,
43
+ ) -> Dict[str, Any]:
44
+ """Run text through and get agent response."""
45
+ # Construct a mapping of tool name to tool for easy lookup
46
+ name_to_tool_map = {tool.name: tool for tool in self.tools}
47
+ # We construct a mapping from each tool to a color, used for logging.
48
+ color_mapping = get_color_mapping(
49
+ [tool.name for tool in self.tools], excluded_colors=["green", "red"]
50
+ )
51
+ intermediate_steps: List[Tuple[AgentAction, str]] = []
52
+ # Let's start tracking the number of iterations and time elapsed
53
+ self.iterations = 0
54
+ time_elapsed = 0.0
55
+ start_time = time.time()
56
+ # We now enter the agent loop (until it returns something).
57
+ while self._should_continue(self.iterations, time_elapsed):
58
+ if not self.request_within_rpm_limit or self.request_within_rpm_limit():
59
+ next_step_output = self._take_next_step(
60
+ name_to_tool_map,
61
+ color_mapping,
62
+ inputs,
63
+ intermediate_steps,
64
+ run_manager=run_manager,
65
+ )
66
+ if isinstance(next_step_output, AgentFinish):
67
+ return self._return(
68
+ next_step_output, intermediate_steps, run_manager=run_manager
69
+ )
70
+
71
+ intermediate_steps.extend(next_step_output)
72
+ if len(next_step_output) == 1:
73
+ next_step_action = next_step_output[0]
74
+ # See if tool should return directly
75
+ tool_return = self._get_tool_return(next_step_action)
76
+ if tool_return is not None:
77
+ return self._return(
78
+ tool_return, intermediate_steps, run_manager=run_manager
79
+ )
80
+ self.iterations += 1
81
+ time_elapsed = time.time() - start_time
82
+ output = self.agent.return_stopped_response(
83
+ self.early_stopping_method, intermediate_steps, **inputs
84
+ )
85
+ return self._return(output, intermediate_steps, run_manager=run_manager)
86
+
87
+ def _iter_next_step(
88
+ self,
89
+ name_to_tool_map: Dict[str, BaseTool],
90
+ color_mapping: Dict[str, str],
91
+ inputs: Dict[str, str],
92
+ intermediate_steps: List[Tuple[AgentAction, str]],
93
+ run_manager: Optional[CallbackManagerForChainRun] = None,
94
+ ) -> Iterator[Union[AgentFinish, AgentAction, AgentStep]]:
95
+ """Take a single step in the thought-action-observation loop.
96
+
97
+ Override this to take control of how the agent makes and acts on choices.
98
+ """
99
+ try:
100
+ intermediate_steps = self._prepare_intermediate_steps(intermediate_steps)
101
+
102
+ # Call the LLM to see what to do.
103
+ output = self.agent.plan(
104
+ intermediate_steps,
105
+ callbacks=run_manager.get_child() if run_manager else None,
106
+ **inputs,
107
+ )
108
+ if self._should_force_answer():
109
+ if isinstance(output, AgentAction) or isinstance(output, AgentFinish):
110
+ output = output
111
+ elif isinstance(output, CacheHit):
112
+ output = output.action
113
+ else:
114
+ raise ValueError(
115
+ f"Unexpected output type from agent: {type(output)}"
116
+ )
117
+ yield self._force_answer(output)
118
+ return
119
+
120
+ except OutputParserException as e:
121
+ if isinstance(self.handle_parsing_errors, bool):
122
+ raise_error = not self.handle_parsing_errors
123
+ else:
124
+ raise_error = False
125
+ if raise_error:
126
+ raise ValueError(
127
+ "An output parsing error occurred. "
128
+ "In order to pass this error back to the agent and have it try "
129
+ "again, pass `handle_parsing_errors=True` to the AgentExecutor. "
130
+ f"This is the error: {str(e)}"
131
+ )
132
+ text = str(e)
133
+ if isinstance(self.handle_parsing_errors, bool):
134
+ if e.send_to_llm:
135
+ observation = str(e.observation)
136
+ text = str(e.llm_output)
137
+ else:
138
+ observation = "Invalid or incomplete response"
139
+ elif isinstance(self.handle_parsing_errors, str):
140
+ observation = self.handle_parsing_errors
141
+ elif callable(self.handle_parsing_errors):
142
+ observation = self.handle_parsing_errors(e)
143
+ else:
144
+ raise ValueError("Got unexpected type of `handle_parsing_errors`")
145
+ output = AgentAction("_Exception", observation, text)
146
+ if run_manager:
147
+ run_manager.on_agent_action(output, color="green")
148
+ tool_run_kwargs = self.agent.tool_run_logging_kwargs()
149
+ observation = ExceptionTool().run(
150
+ output.tool_input,
151
+ verbose=self.verbose,
152
+ color=None,
153
+ callbacks=run_manager.get_child() if run_manager else None,
154
+ **tool_run_kwargs,
155
+ )
156
+
157
+ if self._should_force_answer():
158
+ yield self._force_answer(output)
159
+ return
160
+
161
+ yield AgentStep(action=output, observation=observation)
162
+ return
163
+
164
+ # If the tool chosen is the finishing tool, then we end and return.
165
+ if isinstance(output, AgentFinish):
166
+ yield output
167
+ return
168
+
169
+ # Override tool usage to use CacheTools
170
+ if isinstance(output, CacheHit):
171
+ cache = output.cache
172
+ action = output.action
173
+ tool = CacheTools(cache_handler=cache).tool()
174
+ output = action.copy()
175
+ output.tool_input = f"tool:{action.tool}|input:{action.tool_input}"
176
+ output.tool = tool.name
177
+ name_to_tool_map[tool.name] = tool
178
+ color_mapping[tool.name] = color_mapping[action.tool]
179
+
180
+ actions: List[AgentAction]
181
+ actions = [output] if isinstance(output, AgentAction) else output
182
+ yield from actions
183
+ for agent_action in actions:
184
+ if run_manager:
185
+ run_manager.on_agent_action(agent_action, color="green")
186
+ # Otherwise we lookup the tool
187
+ if agent_action.tool in name_to_tool_map:
188
+ tool = name_to_tool_map[agent_action.tool]
189
+ return_direct = tool.return_direct
190
+ color = color_mapping[agent_action.tool]
191
+ tool_run_kwargs = self.agent.tool_run_logging_kwargs()
192
+ if return_direct:
193
+ tool_run_kwargs["llm_prefix"] = ""
194
+ # We then call the tool on the tool input to get an observation
195
+ observation = tool.run(
196
+ agent_action.tool_input,
197
+ verbose=self.verbose,
198
+ color=color,
199
+ callbacks=run_manager.get_child() if run_manager else None,
200
+ **tool_run_kwargs,
201
+ )
202
+ else:
203
+ tool_run_kwargs = self.agent.tool_run_logging_kwargs()
204
+ observation = InvalidTool().run(
205
+ {
206
+ "requested_tool_name": agent_action.tool,
207
+ "available_tool_names": list(name_to_tool_map.keys()),
208
+ },
209
+ verbose=self.verbose,
210
+ color=None,
211
+ callbacks=run_manager.get_child() if run_manager else None,
212
+ **tool_run_kwargs,
213
+ )
214
+ yield AgentStep(action=agent_action, observation=observation)
crewai/agents/output_parser.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 crewai.agents.cache import CacheHandler, CacheHit
8
+ from crewai.agents.exceptions import TaskRepeatedUsageException
9
+ from crewai.agents.tools_handler import ToolsHandler
10
+ from crewai.utilities import I18N
11
+
12
+ FINAL_ANSWER_ACTION = "Final Answer:"
13
+ FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE = (
14
+ "Parsing LLM output produced both a final answer and a parse-able action:"
15
+ )
16
+
17
+
18
+ class CrewAgentOutputParser(ReActSingleInputOutputParser):
19
+ """Parses ReAct-style LLM calls that have a single tool input.
20
+
21
+ Expects output to be in one of two formats.
22
+
23
+ If the output signals that an action should be taken,
24
+ should be in the below format. This will result in an AgentAction
25
+ being returned.
26
+
27
+ ```
28
+ Thought: agent thought here
29
+ Action: search
30
+ Action Input: what is the temperature in SF?
31
+ ```
32
+
33
+ If the output signals that a final answer should be given,
34
+ should be in the below format. This will result in an AgentFinish
35
+ being returned.
36
+
37
+ ```
38
+ Thought: agent thought here
39
+ Final Answer: The temperature is 100 degrees
40
+ ```
41
+
42
+ It also prevents tools from being reused in a roll.
43
+ """
44
+
45
+ class Config:
46
+ arbitrary_types_allowed = True
47
+
48
+ tools_handler: ToolsHandler
49
+ cache: CacheHandler
50
+ i18n: I18N
51
+
52
+ def parse(self, text: str) -> Union[AgentAction, AgentFinish, CacheHit]:
53
+ regex = (
54
+ r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
55
+ )
56
+ if action_match := re.search(regex, text, re.DOTALL):
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
+ if last_tool_usage := self.tools_handler.last_used_tool:
63
+ usage = {
64
+ "tool": action,
65
+ "input": tool_input,
66
+ }
67
+ if usage == last_tool_usage:
68
+ raise TaskRepeatedUsageException(
69
+ text=text,
70
+ tool=action,
71
+ tool_input=tool_input,
72
+ i18n=self.i18n,
73
+ )
74
+
75
+ if self.cache.read(action, tool_input):
76
+ action = AgentAction(action, tool_input, text)
77
+ return CacheHit(action=action, cache=self.cache)
78
+
79
+ 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
14
+
15
+ def __init__(self, cache: CacheHandler, **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,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ PrivateAttr,
13
+ field_validator,
14
+ model_validator,
15
+ )
16
+ from pydantic_core import PydanticCustomError
17
+
18
+ from crewai.agent import Agent
19
+ from crewai.agents.cache import CacheHandler
20
+ from crewai.process import Process
21
+ from crewai.task import Task
22
+ from crewai.telemtry import Telemetry
23
+ from crewai.tools.agent_tools import AgentTools
24
+ from crewai.utilities import I18N, Logger, RPMController
25
+
26
+
27
+ class Crew(BaseModel):
28
+ """
29
+ Represents a group of agents, defining how they should collaborate and the tasks they should perform.
30
+
31
+ Attributes:
32
+ tasks: List of tasks assigned to the crew.
33
+ agents: List of agents part of this crew.
34
+ manager_llm: The language model that will run manager agent.
35
+ process: The process flow that the crew will follow (e.g., sequential).
36
+ verbose: Indicates the verbosity level for logging during execution.
37
+ config: Configuration settings for the crew.
38
+ _cache_handler: Handles caching for the crew's operations.
39
+ max_rpm: Maximum number of requests per minute for the crew execution to be respected.
40
+ id: A unique identifier for the crew instance.
41
+ """
42
+
43
+ __hash__ = object.__hash__ # type: ignore
44
+ _rpm_controller: RPMController = PrivateAttr()
45
+ _logger: Logger = PrivateAttr()
46
+ _cache_handler: InstanceOf[CacheHandler] = PrivateAttr(default=CacheHandler())
47
+ model_config = ConfigDict(arbitrary_types_allowed=True)
48
+ tasks: List[Task] = Field(default_factory=list)
49
+ agents: List[Agent] = Field(default_factory=list)
50
+ process: Process = Field(default=Process.sequential)
51
+ verbose: Union[int, bool] = Field(default=0)
52
+ manager_llm: Optional[Any] = Field(
53
+ description="Language model that will run the agent.", default=None
54
+ )
55
+ config: Optional[Union[Json, Dict[str, Any]]] = Field(default=None)
56
+ id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
57
+ max_rpm: Optional[int] = Field(
58
+ default=None,
59
+ description="Maximum number of requests per minute for the crew execution to be respected.",
60
+ )
61
+ language: str = Field(
62
+ default="en",
63
+ description="Language used for the crew, defaults to English.",
64
+ )
65
+
66
+ @field_validator("id", mode="before")
67
+ @classmethod
68
+ def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
69
+ """Prevent manual setting of the 'id' field by users."""
70
+ if v:
71
+ raise PydanticCustomError(
72
+ "may_not_set_field", "The 'id' field cannot be set by the user.", {}
73
+ )
74
+
75
+ @field_validator("config", mode="before")
76
+ @classmethod
77
+ def check_config_type(
78
+ cls, v: Union[Json, Dict[str, Any]]
79
+ ) -> Union[Json, Dict[str, Any]]:
80
+ """Validates that the config is a valid type.
81
+ Args:
82
+ v: The config to be validated.
83
+ Returns:
84
+ The config if it is valid.
85
+ """
86
+
87
+ # TODO: Improve typing
88
+ return json.loads(v) if isinstance(v, Json) else v # type: ignore
89
+
90
+ @model_validator(mode="after")
91
+ def set_private_attrs(self) -> "Crew":
92
+ """Set private attributes."""
93
+ self._cache_handler = CacheHandler()
94
+ self._logger = Logger(self.verbose)
95
+ self._rpm_controller = RPMController(max_rpm=self.max_rpm, logger=self._logger)
96
+ self._telemetry = Telemetry()
97
+ self._telemetry.crew_creation(self)
98
+ return self
99
+
100
+ @model_validator(mode="after")
101
+ def check_manager_llm(self):
102
+ """Validates that the language model is set when using hierarchical process."""
103
+ if self.process == Process.hierarchical and not self.manager_llm:
104
+ raise PydanticCustomError(
105
+ "missing_manager_llm",
106
+ "Attribute `manager_llm` is required when using hierarchical process.",
107
+ {},
108
+ )
109
+ return self
110
+
111
+ @model_validator(mode="after")
112
+ def check_config(self):
113
+ """Validates that the crew is properly configured with agents and tasks."""
114
+ if not self.config and not self.tasks and not self.agents:
115
+ raise PydanticCustomError(
116
+ "missing_keys",
117
+ "Either 'agents' and 'tasks' need to be set or 'config'.",
118
+ {},
119
+ )
120
+
121
+ if self.config:
122
+ self._setup_from_config()
123
+
124
+ if self.agents:
125
+ for agent in self.agents:
126
+ agent.set_cache_handler(self._cache_handler)
127
+ if self.max_rpm:
128
+ agent.set_rpm_controller(self._rpm_controller)
129
+ return self
130
+
131
+ def _setup_from_config(self):
132
+ assert self.config is not None, "Config should not be None."
133
+
134
+ """Initializes agents and tasks from the provided config."""
135
+ if not self.config.get("agents") or not self.config.get("tasks"):
136
+ raise PydanticCustomError(
137
+ "missing_keys_in_config", "Config should have 'agents' and 'tasks'.", {}
138
+ )
139
+
140
+ self.agents = [Agent(**agent) for agent in self.config["agents"]]
141
+ self.tasks = [self._create_task(task) for task in self.config["tasks"]]
142
+
143
+ def _create_task(self, task_config: Dict[str, Any]) -> Task:
144
+ """Creates a task instance from its configuration.
145
+
146
+ Args:
147
+ task_config: The configuration of the task.
148
+
149
+ Returns:
150
+ A task instance.
151
+ """
152
+ task_agent = next(
153
+ agt for agt in self.agents if agt.role == task_config["agent"]
154
+ )
155
+ del task_config["agent"]
156
+ return Task(**task_config, agent=task_agent)
157
+
158
+ def kickoff(self) -> str:
159
+ """Starts the crew to work on its assigned tasks."""
160
+ for agent in self.agents:
161
+ agent.i18n = I18N(language=self.language)
162
+
163
+ if self.process == Process.sequential:
164
+ return self._run_sequential_process()
165
+ if self.process == Process.hierarchical:
166
+ return self._run_hierarchical_process()
167
+
168
+ raise NotImplementedError(
169
+ f"The process '{self.process}' is not implemented yet."
170
+ )
171
+
172
+ def _run_sequential_process(self) -> str:
173
+ """Executes tasks sequentially and returns the final output."""
174
+ task_output = ""
175
+ for task in self.tasks:
176
+ if task.agent is not None and task.agent.allow_delegation:
177
+ agents_for_delegation = [
178
+ agent for agent in self.agents if agent != task.agent
179
+ ]
180
+ task.tools += AgentTools(agents=agents_for_delegation).tools()
181
+
182
+ role = task.agent.role if task.agent is not None else "None"
183
+ self._logger.log("debug", f"Working Agent: {role}")
184
+ self._logger.log("info", f"Starting Task: {task.description}")
185
+
186
+ output = task.execute(context=task_output)
187
+ if not task.async_execution:
188
+ task_output = output
189
+
190
+ role = task.agent.role if task.agent is not None else "None"
191
+ self._logger.log("debug", f"[{role}] Task output: {task_output}\n\n")
192
+
193
+ if self.max_rpm:
194
+ self._rpm_controller.stop_rpm_counter()
195
+
196
+ return task_output
197
+
198
+ def _run_hierarchical_process(self) -> str:
199
+ """Creates and assigns a manager agent to make sure the crew completes the tasks."""
200
+
201
+ i18n = I18N(language=self.language)
202
+ manager = Agent(
203
+ role=i18n.retrieve("hierarchical_manager_agent", "role"),
204
+ goal=i18n.retrieve("hierarchical_manager_agent", "goal"),
205
+ backstory=i18n.retrieve("hierarchical_manager_agent", "backstory"),
206
+ tools=AgentTools(agents=self.agents).tools(),
207
+ llm=self.manager_llm,
208
+ verbose=True,
209
+ )
210
+
211
+ task_output = ""
212
+ for task in self.tasks:
213
+ self._logger.log("debug", f"Working Agent: {manager.role}")
214
+ self._logger.log("info", f"Starting Task: {task.description}")
215
+
216
+ task_output = task.execute(
217
+ agent=manager, context=task_output, tools=manager.tools
218
+ )
219
+
220
+ self._logger.log(
221
+ "debug", f"[{manager.role}] Task output: {task_output}\n\n"
222
+ )
223
+
224
+ if self.max_rpm:
225
+ self._rpm_controller.stop_rpm_counter()
226
+
227
+ return task_output
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
+ hierarchical = "hierarchical"
11
+ # TODO: consensual = 'consensual'
crewai/task.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import uuid
3
+ from typing import Any, List, Optional
4
+
5
+ from pydantic import UUID4, BaseModel, Field, field_validator, model_validator
6
+ from pydantic_core import PydanticCustomError
7
+
8
+ from crewai.agent import Agent
9
+ from crewai.tasks.task_output import TaskOutput
10
+ from crewai.utilities import I18N
11
+
12
+
13
+ class Task(BaseModel):
14
+ """Class that represent a task to be executed."""
15
+
16
+ class Config:
17
+ arbitrary_types_allowed = True
18
+
19
+ __hash__ = object.__hash__ # type: ignore
20
+ i18n: I18N = I18N()
21
+ thread: threading.Thread = None
22
+ description: str = Field(description="Description of the actual task.")
23
+ callback: Optional[Any] = Field(
24
+ description="Callback to be executed after the task is completed.", default=None
25
+ )
26
+ agent: Optional[Agent] = Field(
27
+ description="Agent responsible for execution the task.", default=None
28
+ )
29
+ expected_output: Optional[str] = Field(
30
+ description="Clear definition of expected output for the task.",
31
+ default=None,
32
+ )
33
+ context: Optional[List["Task"]] = Field(
34
+ description="Other tasks that will have their output used as context for this task.",
35
+ default=None,
36
+ )
37
+ async_execution: Optional[bool] = Field(
38
+ description="Whether the task should be executed asynchronously or not.",
39
+ default=False,
40
+ )
41
+ output: Optional[TaskOutput] = Field(
42
+ description="Task output, it's final result after being executed", default=None
43
+ )
44
+ tools: List[Any] = Field(
45
+ default_factory=list,
46
+ description="Tools the agent is limited to use for this task.",
47
+ )
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
+
54
+ @field_validator("id", mode="before")
55
+ @classmethod
56
+ def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
57
+ if v:
58
+ raise PydanticCustomError(
59
+ "may_not_set_field", "This field is not to be set by the user.", {}
60
+ )
61
+
62
+ @model_validator(mode="after")
63
+ def check_tools(self):
64
+ """Check if the tools are set."""
65
+ if not self.tools and self.agent and self.agent.tools:
66
+ self.tools.extend(self.agent.tools)
67
+ return self
68
+
69
+ def execute(
70
+ self,
71
+ agent: Agent | None = None,
72
+ context: Optional[str] = None,
73
+ tools: Optional[List[Any]] = None,
74
+ ) -> str:
75
+ """Execute the task.
76
+
77
+ Returns:
78
+ Output of the task.
79
+ """
80
+
81
+ agent = agent or self.agent
82
+ if not agent:
83
+ raise Exception(
84
+ 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, like hierarchical."
85
+ )
86
+
87
+ if self.context:
88
+ context = []
89
+ for task in self.context:
90
+ if task.async_execution:
91
+ task.thread.join()
92
+ context.append(task.output.result)
93
+ context = "\n".join(context)
94
+
95
+ tools = tools or self.tools
96
+
97
+ if self.async_execution:
98
+ self.thread = threading.Thread(
99
+ target=self._execute, args=(agent, self._prompt(), context, tools)
100
+ )
101
+ self.thread.start()
102
+ else:
103
+ result = self._execute(
104
+ agent=agent,
105
+ task_prompt=self._prompt(),
106
+ context=context,
107
+ tools=tools,
108
+ )
109
+ return result
110
+
111
+ def _execute(self, agent, task_prompt, context, tools):
112
+ result = agent.execute_task(task=task_prompt, context=context, tools=tools)
113
+ self.output = TaskOutput(description=self.description, result=result)
114
+ self.callback(self.output) if self.callback else None
115
+ return result
116
+
117
+ def _prompt(self) -> str:
118
+ """Prompt the task.
119
+
120
+ Returns:
121
+ Prompt of the task.
122
+ """
123
+ tasks_slices = [self.description]
124
+
125
+ if self.expected_output:
126
+ output = self.i18n.slice("expected_output").format(
127
+ expected_output=self.expected_output
128
+ )
129
+ tasks_slices = [self.description, output]
130
+ return "\n".join(tasks_slices)
crewai/tasks/__init__.py ADDED
File without changes
crewai/tasks/task_output.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel, Field, model_validator
4
+
5
+
6
+ class TaskOutput(BaseModel):
7
+ """Class that represents the result of a task."""
8
+
9
+ description: str = Field(description="Description of the task")
10
+ summary: Optional[str] = Field(description="Summary of the task", default=None)
11
+ result: str = Field(description="Result of the task")
12
+
13
+ @model_validator(mode="after")
14
+ def set_summary(self):
15
+ excerpt = " ".join(self.description.split(" ")[:10])
16
+ self.summary = f"{excerpt}..."
17
+ return self
crewai/telemtry/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .telemetry import Telemetry
crewai/telemtry/telemetry.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import platform
4
+ import socket
5
+
6
+ import pkg_resources
7
+ from opentelemetry import trace
8
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
9
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
10
+ from opentelemetry.sdk.trace import TracerProvider
11
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
12
+ from opentelemetry.trace import Status, StatusCode
13
+
14
+
15
+ class Telemetry:
16
+ """A class to handle anonymous telemetry for the crewai package.
17
+
18
+ The data being collected is for development purpose, all data is anonymous.
19
+
20
+ There is NO data being collected on the prompts, tasks descriptions
21
+ agents backstories or goals nor responses or any data that is being
22
+ processed by the agents, nor any secrets and env vars.
23
+
24
+ Data collected includes:
25
+ - Version of crewAI
26
+ - Version of Python
27
+ - General OS (e.g. number of CPUs, macOS/Windows/Linux)
28
+ - Number of agents and tasks in a crew
29
+ - Crew Process being used
30
+ - If Agents are using memory or allowing delegation
31
+ - If Tasks are being executed in parallel or sequentially
32
+ - Language model being used
33
+ - Roles of agents in a crew
34
+ - Tools names available
35
+ """
36
+
37
+ def __init__(self):
38
+ telemetry_endpoint = "http://telemetry.crewai.com:4318"
39
+ self.resource = Resource(attributes={SERVICE_NAME: "crewAI-telemetry"})
40
+ provider = TracerProvider(resource=self.resource)
41
+ processor = BatchSpanProcessor(
42
+ OTLPSpanExporter(endpoint=f"{telemetry_endpoint}/v1/traces")
43
+ )
44
+ provider.add_span_processor(processor)
45
+ trace.set_tracer_provider(provider)
46
+
47
+ def crew_creation(self, crew):
48
+ """Records the creation of a crew."""
49
+ try:
50
+ tracer = trace.get_tracer("crewai.telemetry")
51
+ span = tracer.start_span("Crew Created")
52
+ self.add_attribute(
53
+ span, "crewai_version", pkg_resources.get_distribution("crewai").version
54
+ )
55
+ self.add_attribute(span, "python_version", platform.python_version())
56
+ self.add_attribute(span, "hostname", socket.gethostname())
57
+ self.add_attribute(span, "crewid", str(crew.id))
58
+ self.add_attribute(span, "crew_process", crew.process)
59
+ self.add_attribute(span, "crew_language", crew.language)
60
+ self.add_attribute(span, "crew_number_of_tasks", len(crew.tasks))
61
+ self.add_attribute(span, "crew_number_of_agents", len(crew.agents))
62
+ self.add_attribute(
63
+ span,
64
+ "crew_agents",
65
+ json.dumps(
66
+ [
67
+ {
68
+ "id": str(agent.id),
69
+ "role": agent.role,
70
+ "memory_enabled?": agent.memory,
71
+ "llm": json.dumps(self._safe_llm_attributes(agent.llm)),
72
+ "delegation_enabled?": agent.allow_delegation,
73
+ "tools_names": [tool.name for tool in agent.tools],
74
+ }
75
+ for agent in crew.agents
76
+ ]
77
+ ),
78
+ )
79
+ self.add_attribute(
80
+ span,
81
+ "crew_tasks",
82
+ json.dumps(
83
+ [
84
+ {
85
+ "id": str(task.id),
86
+ "async_execution?": task.async_execution,
87
+ "tools_names": [tool.name for tool in task.tools],
88
+ }
89
+ for task in crew.tasks
90
+ ]
91
+ ),
92
+ )
93
+ self.add_attribute(span, "platform", platform.platform())
94
+ self.add_attribute(span, "platform_release", platform.release())
95
+ self.add_attribute(span, "platform_system", platform.system())
96
+ self.add_attribute(span, "platform_version", platform.version())
97
+ self.add_attribute(span, "cpus", os.cpu_count())
98
+ span.set_status(Status(StatusCode.OK))
99
+ span.end()
100
+ except Exception:
101
+ pass
102
+
103
+ def add_attribute(self, span, key, value):
104
+ """Add an attribute to a span."""
105
+ try:
106
+ return span.set_attribute(key, value)
107
+ except Exception:
108
+ pass
109
+
110
+ def _safe_llm_attributes(self, llm):
111
+ attributes = ["name", "model_name", "base_url", "model", "top_k", "temperature"]
112
+ safe_attributes = {k: v for k, v in vars(llm).items() if k in attributes}
113
+ safe_attributes["class"] = llm.__class__.__name__
114
+ return safe_attributes
crewai/tools/__init__.py ADDED
File without changes
crewai/tools/agent_tools.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from langchain.tools import Tool
4
+ from pydantic import BaseModel, Field
5
+
6
+ from crewai.agent import Agent
7
+ from crewai.utilities import I18N
8
+
9
+
10
+ class AgentTools(BaseModel):
11
+ """Default tools around agent delegation"""
12
+
13
+ agents: List[Agent] = Field(description="List of agents in this crew.")
14
+ i18n: I18N = Field(default=I18N(), description="Internationalization settings.")
15
+
16
+ def tools(self):
17
+ return [
18
+ Tool.from_function(
19
+ func=self.delegate_work,
20
+ name="Delegate work to co-worker",
21
+ description=self.i18n.tools("delegate_work").format(
22
+ coworkers=", ".join([agent.role for agent in self.agents])
23
+ ),
24
+ ),
25
+ Tool.from_function(
26
+ func=self.ask_question,
27
+ name="Ask question to co-worker",
28
+ description=self.i18n.tools("ask_question").format(
29
+ coworkers=", ".join([agent.role for agent in self.agents])
30
+ ),
31
+ ),
32
+ ]
33
+
34
+ def delegate_work(self, command):
35
+ """Useful to delegate a specific task to a coworker."""
36
+ return self._execute(command)
37
+
38
+ def ask_question(self, command):
39
+ """Useful to ask a question, opinion or take from a coworker."""
40
+ return self._execute(command)
41
+
42
+ def _execute(self, command):
43
+ """Execute the command."""
44
+ try:
45
+ agent, task, context = command.split("|")
46
+ except ValueError:
47
+ return self.i18n.errors("agent_tool_missing_param")
48
+
49
+ if not agent or not task or not context:
50
+ return self.i18n.errors("agent_tool_missing_param")
51
+
52
+ agent = [
53
+ available_agent
54
+ for available_agent in self.agents
55
+ if available_agent.role == agent
56
+ ]
57
+
58
+ if not agent:
59
+ return self.i18n.errors("agent_tool_unexsiting_coworker").format(
60
+ coworkers=", ".join([agent.role for agent in self.agents])
61
+ )
62
+
63
+ agent = agent[0]
64
+ return agent.execute_task(task, context)
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)
crewai/translations/el.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "hierarchical_manager_agent": {
3
+ "role": "Διευθυντής Ομάδας",
4
+ "goal": "Διαχειρίσου την ομάδα σου για να ολοκληρώσει την εργασία με τον καλύτερο δυνατό τρόπο.",
5
+ "backstory": "Είσαι ένας έμπειρος διευθυντής με την ικανότητα να βγάζεις το καλύτερο από την ομάδα σου.\nΕίσαι επίσης γνωστός για την ικανότητά σου να αναθέτεις εργασίες στους σωστούς ανθρώπους και να κάνεις τις σωστές ερωτήσεις για να πάρεις το καλύτερο από την ομάδα σου.\nΑκόμα κι αν δεν εκτελείς εργασίες μόνος σου, έχεις πολλή εμπειρία στον τομέα, που σου επιτρέπει να αξιολογείς σωστά τη δουλειά των μελών της ομάδας σου."
6
+ },
7
+ "slices": {
8
+ "observation": "\nΠαρατήρηση",
9
+ "task": "Αρχή! Αυτό είναι ΠΟΛΥ σημαντικό για εσάς, η δουλειά σας εξαρτάται από αυτό!\n\nΤρέχουσα εργασία: {input}",
10
+ "memory": "Αυτή είναι η περίληψη της μέχρι τώρα δουλειάς σας:\n{chat_history}",
11
+ "role_playing": "Είσαι {role}.\n{backstory}\n\nΟ προσωπικός σας στόχος είναι: {goal}",
12
+ "tools": "ΕΡΓΑΛΕΙΑ:\n------\nΈχετε πρόσβαση μόνο στα ακόλουθα εργαλεία:\n\n{tools}\n\nΓια να χρησιμοποιήσετε ένα εργαλείο, χρησιμοποιήστε την ακόλουθη ακριβώς μορφή:\n\n```\nΣκέψη: Χρειάζεται να χρησιμοποιήσω κάποιο εργαλείο; Ναί\nΔράση: η ενέργεια που πρέπει να γίνει, πρέπει να είναι μία από τις[{tool_names}], μόνο το όνομα.\nΕνέργεια προς εισαγωγή: η είσοδος στη δράση\nΠαρατήρηση: το αποτέλεσμα της δράσης\n```\n\nΌταν έχετε μια απάντηση για την εργασία σας ή εάν δεν χρειάζεται να χρησιμοποιήσετε ένα εργαλείο, ΠΡΕΠΕΙ να χρησιμοποιήσετε τη μορφή:\n\n```\nΣκέψη: Χρειάζεται να χρησιμοποιήσω κάποιο εργαλείο; Οχι\nΤελική απάντηση: [η απάντησή σας εδώ]```",
13
+ "task_with_context": "{task}\nΑυτό είναι το πλαίσιο με το οποίο εργάζεστε:\n{context}",
14
+ "expected_output": "Η τελική σας απάντηση πρέπει να είναι: {expected_output}"
15
+ },
16
+ "errors": {
17
+ "force_final_answer": "Στην πραγματικότητα, χρησιμοποίησα πάρα πολλά εργαλεία, οπότε θα σταματήσω τώρα και θα σας δώσω την απόλυτη ΚΑΛΥΤΕΡΗ τελική μου απάντηση ΤΩΡΑ, χρησιμοποιώντας την αναμενόμενη μορφή: ```\nΣκέφτηκα: Χρειάζεται να χρησιμοποιήσω ένα εργαλείο; Όχι\nΤελική απάντηση: [η απάντησή σας εδώ]```",
18
+ "agent_tool_missing_param": "\nΣφάλμα κατά την εκτέλεση του εργαλείου. Λείπουν ακριβώς 3 διαχωρισμένες τιμές σωλήνων (|). Για παράδειγμα, `coworker|task|context`. Πρέπει να φροντίσω να περάσω το πλαίσιο ως πλαίσιο.\n",
19
+ "agent_tool_unexsiting_coworker": "\nΣφάλμα κατά την εκτέλεση του εργαλείου. Ο συνάδελφος που αναφέρεται στο Ενέργεια προς εισαγωγή δεν βρέθηκε, πρέπει να είναι μία από τις ακόλουθες επιλογές: {coworkers}.\n",
20
+ "task_repeated_usage": "Μόλις χρησιμοποίησα το {tool} εργαλείο με είσοδο {tool_input}. Άρα ξέρω ήδη το αποτέλεσμα αυτού και δεν χρειάζεται να το χρησιμοποιήσω τώρα.\n"
21
+ },
22
+ "tools": {
23
+ "delegate_work": "Χρήσιμο για την ανάθεση μιας συγκεκριμένης εργασίας σε έναν από τους παρακάτω συναδέλφους: {coworkers}.\nΗ είσοδος σε αυτό το εργαλείο θα πρέπει να είναι ένα κείμενο χωρισμένο σε σωλήνα (|) μήκους 3 (τρία), που αντιπροσωπεύει τον συνάδελφο στον οποίο θέλετε να του ζητήσετε (μία από τις επιλογές), την εργασία και όλο το πραγματικό πλαί��ιο που έχετε για την εργασία .\nΓια παράδειγμα, `coworker|task|context`.",
24
+ "ask_question": "Χρήσιμο για να κάνετε μια ερώτηση, γνώμη ή αποδοχή από τους παρακάτω συναδέλφους: {coworkers}.\nΗ είσοδος σε αυτό το εργαλείο θα πρέπει να είναι ένα κείμενο χωρισμένο σε σωλήνα (|) μήκους 3 (τρία), που αντιπροσωπεύει τον συνάδελφο στον οποίο θέλετε να το ρωτήσετε (μία από τις επιλογές), την ερώτηση και όλο το πραγματικό πλαίσιο που έχετε για την ερώτηση.\nΓια παράδειγμα, `coworker|question|context`."
25
+ }
26
+ }
crewai/translations/en.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "hierarchical_manager_agent": {
3
+ "role": "Crew Manager",
4
+ "goal": "Manage the team to complete the task in the best way possible.",
5
+ "backstory": "You are a seasoned manager with a knack for getting the best out of your team.\nYou are also known for your ability to delegate work to the right people, and to ask the right questions to get the best out of your team.\nEven though you don't perform tasks by yourself, you have a lot of experience in the field, which allows you to properly evaluate the work of your team members."
6
+ },
7
+ "slices": {
8
+ "observation": "\nObservation",
9
+ "task": "Begin! This is VERY important to you, your job depends on it!\n\nCurrent Task: {input}",
10
+ "memory": "This is the summary of your work so far:\n{chat_history}",
11
+ "role_playing": "You are {role}.\n{backstory}\n\nYour personal goal is: {goal}",
12
+ "tools": "TOOLS:\n------\nYou have access to only the following tools:\n\n{tools}\n\nTo use a tool, please use the exact following format:\n\n```\nThought: Do I need to use a tool? Yes\nAction: the action to take, should be one of [{tool_names}], just the name.\nAction Input: the input to the action\nObservation: the result of the action\n```\n\nWhen you have a response for your task, or if you do not need to use a tool, you MUST use the format:\n\n```\nThought: Do I need to use a tool? No\nFinal Answer: [your response here]```",
13
+ "task_with_context": "{task}\nThis is the context you're working with:\n{context}",
14
+ "expected_output": "Your final answer must be: {expected_output}"
15
+ },
16
+ "errors": {
17
+ "force_final_answer": "Actually, I used too many tools, so I'll stop now and give you my absolute BEST Final answer NOW, using the expected format: ```\nThought: Do I need to use a tool? No\nFinal Answer: [your response here]```",
18
+ "agent_tool_missing_param": "\nError executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|context`. I need to make sure to pass context as context.\n",
19
+ "agent_tool_unexsiting_coworker": "\nError executing tool. Co-worker mentioned on the Action Input not found, it must to be one of the following options: {coworkers}.\n",
20
+ "task_repeated_usage": "I just used the {tool} tool with input {tool_input}. So I already know the result of that and don't need to use it now.\n"
21
+ },
22
+ "tools": {
23
+ "delegate_work": "Useful to delegate a specific task to one of the following co-workers: {coworkers}.\nThe input to this tool should be a pipe (|) separated text of length 3 (three), representing the co-worker you want to ask it to (one of the options), the task and all actual context you have for the task.\nFor example, `coworker|task|context`.",
24
+ "ask_question": "Useful to ask a question, opinion or take from on of the following co-workers: {coworkers}.\nThe input to this tool should be a pipe (|) separated text of length 3 (three), representing the co-worker you want to ask it to (one of the options), the question and all actual context you have for the question.\n For example, `coworker|question|context`."
25
+ }
26
+ }
crewai/utilities/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .i18n import I18N
2
+ from .logger import Logger
3
+ from .prompts import Prompts
4
+ from .rpm_controller import RPMController
crewai/utilities/i18n.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from typing import Dict, Optional
4
+
5
+ from pydantic import BaseModel, Field, PrivateAttr, ValidationError, model_validator
6
+
7
+
8
+ class I18N(BaseModel):
9
+ _translations: Dict[str, Dict[str, str]] = PrivateAttr()
10
+ language: Optional[str] = Field(
11
+ default="en",
12
+ description="Language used to load translations",
13
+ )
14
+
15
+ @model_validator(mode="after")
16
+ def load_translation(self) -> "I18N":
17
+ """Load translations from a JSON file based on the specified language."""
18
+ try:
19
+ dir_path = os.path.dirname(os.path.realpath(__file__))
20
+ prompts_path = os.path.join(
21
+ dir_path, f"../translations/{self.language}.json"
22
+ )
23
+
24
+ with open(prompts_path, "r") as f:
25
+ self._translations = json.load(f)
26
+ except FileNotFoundError:
27
+ raise ValidationError(
28
+ f"Translation file for language '{self.language}' not found."
29
+ )
30
+ except json.JSONDecodeError:
31
+ raise ValidationError(f"Error decoding JSON from the prompts file.")
32
+
33
+ if not self._translations:
34
+ self._translations = {}
35
+
36
+ return self
37
+
38
+ def slice(self, slice: str) -> str:
39
+ return self.retrieve("slices", slice)
40
+
41
+ def errors(self, error: str) -> str:
42
+ return self.retrieve("errors", error)
43
+
44
+ def tools(self, error: str) -> str:
45
+ return self.retrieve("tools", error)
46
+
47
+ def retrieve(self, kind, key) -> str:
48
+ try:
49
+ return self._translations[kind][key]
50
+ except:
51
+ raise ValidationError(f"Translation for '{kind}':'{key}' not found.")
crewai/utilities/logger.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Logger:
2
+ def __init__(self, verbose_level=0):
3
+ verbose_level = (
4
+ 2 if isinstance(verbose_level, bool) and verbose_level else verbose_level
5
+ )
6
+ self.verbose_level = verbose_level
7
+
8
+ def log(self, level, message):
9
+ level_map = {"debug": 1, "info": 2}
10
+ if self.verbose_level and level_map.get(level, 0) <= self.verbose_level:
11
+ print(f"[{level.upper()}]: {message}")
crewai/utilities/prompts.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import ClassVar
2
+
3
+ from langchain.prompts import PromptTemplate, BasePromptTemplate
4
+ from pydantic import BaseModel, Field
5
+
6
+ from crewai.utilities import I18N
7
+
8
+
9
+ class Prompts(BaseModel):
10
+ """Manages and generates prompts for a generic agent with support for different languages."""
11
+
12
+ i18n: I18N = Field(default=I18N())
13
+
14
+ SCRATCHPAD_SLICE: ClassVar[str] = "\n{agent_scratchpad}"
15
+
16
+ def task_execution_with_memory(self) -> BasePromptTemplate:
17
+ """Generate a prompt for task execution with memory components."""
18
+ return self._build_prompt(["role_playing", "tools", "memory", "task"])
19
+
20
+ def task_execution_without_tools(self) -> BasePromptTemplate:
21
+ """Generate a prompt for task execution without tools components."""
22
+ return self._build_prompt(["role_playing", "task"])
23
+
24
+ def task_execution(self) -> BasePromptTemplate:
25
+ """Generate a standard prompt for task execution."""
26
+ return self._build_prompt(["role_playing", "tools", "task"])
27
+
28
+ def _build_prompt(self, components: list[str]) -> BasePromptTemplate:
29
+ """Constructs a prompt string from specified components."""
30
+ prompt_parts = [self.i18n.slice(component) for component in components]
31
+ prompt_parts.append(self.SCRATCHPAD_SLICE)
32
+ return PromptTemplate.from_template("".join(prompt_parts))
crewai/utilities/rpm_controller.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import time
3
+ from typing import Union
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator
6
+
7
+ from crewai.utilities.logger import Logger
8
+
9
+
10
+ class RPMController(BaseModel):
11
+ model_config = ConfigDict(arbitrary_types_allowed=True)
12
+ max_rpm: Union[int, None] = Field(default=None)
13
+ logger: Logger = Field(default=None)
14
+ _current_rpm: int = PrivateAttr(default=0)
15
+ _timer: threading.Timer | None = PrivateAttr(default=None)
16
+ _lock: threading.Lock = PrivateAttr(default=None)
17
+ _shutdown_flag = False
18
+
19
+ @model_validator(mode="after")
20
+ def reset_counter(self):
21
+ if self.max_rpm:
22
+ if not self._shutdown_flag:
23
+ self._lock = threading.Lock()
24
+ self._reset_request_count()
25
+ return self
26
+
27
+ def check_or_wait(self):
28
+ if not self.max_rpm:
29
+ return True
30
+
31
+ with self._lock:
32
+ if self._current_rpm < self.max_rpm:
33
+ self._current_rpm += 1
34
+ return True
35
+ else:
36
+ self.logger.log(
37
+ "info", "Max RPM reached, waiting for next minute to start."
38
+ )
39
+ self._wait_for_next_minute()
40
+ self._current_rpm = 1
41
+ return True
42
+
43
+ def stop_rpm_counter(self):
44
+ if self._timer:
45
+ self._timer.cancel()
46
+ self._timer = None
47
+
48
+ def _wait_for_next_minute(self):
49
+ time.sleep(60)
50
+ self._current_rpm = 0
51
+
52
+ def _reset_request_count(self):
53
+ with self._lock:
54
+ self._current_rpm = 0
55
+ if self._timer:
56
+ self._shutdown_flag = True
57
+ self._timer.cancel()
58
+ self._timer = threading.Timer(60.0, self._reset_request_count)
59
+ self._timer.start()