diff --git a/src/crewai/agent.py b/src/crewai/agent.py index dec0effd7..84585a4cc 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -19,6 +19,12 @@ from crewai.tools.base_tool import Tool from crewai.utilities import Converter, Prompts from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE from crewai.utilities.converter import generate_model_description +from crewai.utilities.events.agent_events import ( + AgentExecutionCompleted, + AgentExecutionError, + AgentExecutionStarted, +) +from crewai.utilities.events.events import emit from crewai.utilities.llm_utils import create_llm from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.training_handler import CrewTrainingHandler @@ -182,6 +188,7 @@ class Agent(BaseAgent): Returns: Output of the agent """ + emit(self, event=AgentExecutionStarted(agent=self, task=task)) if self.tools_handler: self.tools_handler.last_used_tool = {} # type: ignore # Incompatible types in assignment (expression has type "dict[Never, Never]", variable has type "ToolCalling") @@ -259,6 +266,9 @@ class Agent(BaseAgent): raise e self._times_executed += 1 if self._times_executed > self.max_retry_limit: + emit( + self, event=AgentExecutionError(agent=self, task=task, error=str(e)) + ) raise e result = self.execute_task(task, context, tools) @@ -271,7 +281,7 @@ class Agent(BaseAgent): for tool_result in self.tools_results: # type: ignore # Item "None" of "list[Any] | None" has no attribute "__iter__" (not iterable) if tool_result.get("result_as_answer", False): result = tool_result["result"] - + emit(self, event=AgentExecutionCompleted(agent=self, task=task, output=result)) return result def create_agent_executor( diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 93987f3b8..29b7d407e 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -43,6 +43,18 @@ from crewai.utilities import I18N, FileHandler, Logger, RPMController from crewai.utilities.constants import TRAINING_DATA_FILE from crewai.utilities.evaluators.crew_evaluator_handler import CrewEvaluator from crewai.utilities.evaluators.task_evaluator import TaskEvaluator +from crewai.utilities.events import event_bus +from crewai.utilities.events.crew_events import ( + CrewKickoffCompleted, + CrewKickoffFailed, + CrewKickoffStarted, + CrewTrainStarted, + CrewTrainCompleted, + CrewTrainFailed, + CrewTestStarted, + CrewTestCompleted, + CrewTestFailed, +) from crewai.utilities.formatter import ( aggregate_raw_outputs_from_task_outputs, aggregate_raw_outputs_from_tasks, @@ -215,6 +227,10 @@ class Crew(BaseModel): default=None, description="Knowledge for the crew.", ) + listen_to_events: Optional[bool] = Field( + default=True, + description="Whether the crew should listen to events.", + ) @field_validator("id", mode="before") @classmethod @@ -491,10 +507,19 @@ class Crew(BaseModel): self, n_iterations: int, filename: str, inputs: Optional[Dict[str, Any]] = {} ) -> None: """Trains the crew for a given number of iterations.""" - train_crew = self.copy() - train_crew._setup_for_training(filename) - try: + event_bus.emit( + self, + CrewTrainStarted( + crew_name=self.name or "crew", + n_iterations=n_iterations, + filename=filename, + inputs=inputs, + ), + ) + train_crew = self.copy() + train_crew._setup_for_training(filename) + for n_iteration in range(n_iterations): train_crew._train_iteration = n_iteration train_crew.kickoff(inputs=inputs) @@ -509,7 +534,17 @@ class Crew(BaseModel): CrewTrainingHandler(filename).save_trained_data( agent_id=str(agent.role), trained_data=result.model_dump() ) + + event_bus.emit( + self, + CrewTrainCompleted( + crew_name=self.name or "crew", + n_iterations=n_iterations, + filename=filename, + ), + ) except Exception as e: + event_bus.emit(self, CrewTrainFailed(error=str(e))) self._logger.log("error", f"Training failed: {e}", color="red") CrewTrainingHandler(TRAINING_DATA_FILE).clear() CrewTrainingHandler(filename).clear() @@ -519,60 +554,67 @@ class Crew(BaseModel): self, inputs: Optional[Dict[str, Any]] = None, ) -> CrewOutput: - for before_callback in self.before_kickoff_callbacks: - if inputs is None: - inputs = {} - inputs = before_callback(inputs) + try: + for before_callback in self.before_kickoff_callbacks: + if inputs is None: + inputs = {} + inputs = before_callback(inputs) - """Starts the crew to work on its assigned tasks.""" - self._execution_span = self._telemetry.crew_execution_span(self, inputs) - self._task_output_handler.reset() - self._logging_color = "bold_purple" - - if inputs is not None: - self._inputs = inputs - self._interpolate_inputs(inputs) - self._set_tasks_callbacks() - - i18n = I18N(prompt_file=self.prompt_file) - - for agent in self.agents: - agent.i18n = i18n - # type: ignore[attr-defined] # Argument 1 to "_interpolate_inputs" of "Crew" has incompatible type "dict[str, Any] | None"; expected "dict[str, Any]" - agent.crew = self # type: ignore[attr-defined] - # TODO: Create an AgentFunctionCalling protocol for future refactoring - if not agent.function_calling_llm: # type: ignore # "BaseAgent" has no attribute "function_calling_llm" - agent.function_calling_llm = self.function_calling_llm # type: ignore # "BaseAgent" has no attribute "function_calling_llm" - - if not agent.step_callback: # type: ignore # "BaseAgent" has no attribute "step_callback" - agent.step_callback = self.step_callback # type: ignore # "BaseAgent" has no attribute "step_callback" - - agent.create_agent_executor() - - if self.planning: - self._handle_crew_planning() - - metrics: List[UsageMetrics] = [] - - if self.process == Process.sequential: - result = self._run_sequential_process() - elif self.process == Process.hierarchical: - result = self._run_hierarchical_process() - else: - raise NotImplementedError( - f"The process '{self.process}' is not implemented yet." + event_bus.emit( + self, CrewKickoffStarted(crew_name=self.name or "crew", inputs=inputs) ) - for after_callback in self.after_kickoff_callbacks: - result = after_callback(result) + """Starts the crew to work on its assigned tasks.""" + # self._execution_span = self._telemetry.crew_execution_span(self, inputs) # TODO: drop this + self._task_output_handler.reset() + self._logging_color = "bold_purple" - metrics += [agent._token_process.get_summary() for agent in self.agents] + if inputs is not None: + self._inputs = inputs + self._interpolate_inputs(inputs) + self._set_tasks_callbacks() - self.usage_metrics = UsageMetrics() - for metric in metrics: - self.usage_metrics.add_usage_metrics(metric) + i18n = I18N(prompt_file=self.prompt_file) - return result + for agent in self.agents: + agent.i18n = i18n + # type: ignore[attr-defined] # Argument 1 to "_interpolate_inputs" of "Crew" has incompatible type "dict[str, Any] | None"; expected "dict[str, Any]" + agent.crew = self # type: ignore[attr-defined] + # TODO: Create an AgentFunctionCalling protocol for future refactoring + if not agent.function_calling_llm: # type: ignore # "BaseAgent" has no attribute "function_calling_llm" + agent.function_calling_llm = self.function_calling_llm # type: ignore # "BaseAgent" has no attribute "function_calling_llm" + + if not agent.step_callback: # type: ignore # "BaseAgent" has no attribute "step_callback" + agent.step_callback = self.step_callback # type: ignore # "BaseAgent" has no attribute "step_callback" + + agent.create_agent_executor() + + if self.planning: + self._handle_crew_planning() + + metrics: List[UsageMetrics] = [] + + if self.process == Process.sequential: + result = self._run_sequential_process() + elif self.process == Process.hierarchical: + result = self._run_hierarchical_process() + else: + raise NotImplementedError( + f"The process '{self.process}' is not implemented yet." + ) + + for after_callback in self.after_kickoff_callbacks: + result = after_callback(result) + + metrics += [agent._token_process.get_summary() for agent in self.agents] + + self.usage_metrics = UsageMetrics() + for metric in metrics: + self.usage_metrics.add_usage_metrics(metric) + return result + except Exception as e: + event_bus.emit(self, CrewKickoffFailed(error=str(e))) + raise def kickoff_for_each(self, inputs: List[Dict[str, Any]]) -> List[CrewOutput]: """Executes the Crew's workflow for each input in the list and aggregates results.""" @@ -920,7 +962,12 @@ class Crew(BaseModel): final_string_output = final_task_output.raw self._finish_execution(final_string_output) token_usage = self.calculate_usage_metrics() - + event_bus.emit( + self, + CrewKickoffCompleted( + crew_name=self.name or "crew", output=final_task_output + ), + ) return CrewOutput( raw=final_task_output.raw, pydantic=final_task_output.pydantic, @@ -1112,7 +1159,7 @@ class Crew(BaseModel): end_state_reason="Finished Execution", is_auto_end=True, ) - self._telemetry.end_crew(self, final_string_output) + # self._telemetry.end_crew(self, final_string_output) def calculate_usage_metrics(self) -> UsageMetrics: """Calculates and returns the usage metrics.""" @@ -1134,21 +1181,42 @@ class Crew(BaseModel): inputs: Optional[Dict[str, Any]] = None, ) -> None: """Test and evaluate the Crew with the given inputs for n iterations concurrently using concurrent.futures.""" - test_crew = self.copy() + try: + event_bus.emit( + self, + CrewTestStarted( + crew_name=self.name or "crew", + n_iterations=n_iterations, + openai_model_name=openai_model_name, + inputs=inputs, + ), + ) + test_crew = self.copy() + # TODO: drop this + # self._test_execution_span = test_crew._telemetry.test_execution_span( + # test_crew, + # n_iterations, + # inputs, + # openai_model_name, + # ) + evaluator = CrewEvaluator(test_crew, openai_model_name) - self._test_execution_span = test_crew._telemetry.test_execution_span( - test_crew, - n_iterations, - inputs, - openai_model_name, # type: ignore[arg-type] - ) # type: ignore[arg-type] - evaluator = CrewEvaluator(test_crew, openai_model_name) # type: ignore[arg-type] + for i in range(1, n_iterations + 1): + evaluator.set_iteration(i) + test_crew.kickoff(inputs=inputs) - for i in range(1, n_iterations + 1): - evaluator.set_iteration(i) - test_crew.kickoff(inputs=inputs) + evaluator.print_crew_evaluation_result() - evaluator.print_crew_evaluation_result() + event_bus.emit( + self, + CrewTestCompleted( + crew_name=self.name or "crew", + n_iterations=n_iterations, + ), + ) + except Exception as e: + event_bus.emit(self, CrewTestFailed(error=str(e))) + raise def __repr__(self): return f"Crew(id={self.id}, process={self.process}, number_of_agents={len(self.agents)}, number_of_tasks={len(self.tasks)})" diff --git a/src/crewai/flow/flow.py b/src/crewai/flow/flow.py index b744ba6ad..0ea3e87e4 100644 --- a/src/crewai/flow/flow.py +++ b/src/crewai/flow/flow.py @@ -19,16 +19,17 @@ from uuid import uuid4 from blinker import Signal from pydantic import BaseModel, Field, ValidationError -from crewai.flow.flow_events import ( - FlowFinishedEvent, - FlowStartedEvent, - MethodExecutionFinishedEvent, - MethodExecutionStartedEvent, -) from crewai.flow.flow_visualizer import plot_flow from crewai.flow.persistence.base import FlowPersistence from crewai.flow.utils import get_possible_return_constants from crewai.telemetry import Telemetry +from crewai.utilities.events import ( + FlowFinished, + FlowStarted, + MethodExecutionFinished, + MethodExecutionStarted, + event_bus, +) from crewai.utilities.printer import Printer logger = logging.getLogger(__name__) @@ -394,7 +395,6 @@ class FlowMeta(type): or hasattr(attr_value, "__trigger_methods__") or hasattr(attr_value, "__is_router__") ): - # Register start methods if hasattr(attr_value, "__is_start_method__"): start_methods.append(attr_name) @@ -600,7 +600,7 @@ class Flow(Generic[T], metaclass=FlowMeta): ``` """ try: - if not hasattr(self, '_state'): + if not hasattr(self, "_state"): return "" if isinstance(self._state, dict): @@ -706,40 +706,47 @@ class Flow(Generic[T], metaclass=FlowMeta): inputs: Optional dictionary containing input values and potentially a state ID to restore """ # Handle state restoration if ID is provided in inputs - if inputs and 'id' in inputs and self._persistence is not None: - restore_uuid = inputs['id'] + if inputs and "id" in inputs and self._persistence is not None: + restore_uuid = inputs["id"] stored_state = self._persistence.load_state(restore_uuid) # Override the id in the state if it exists in inputs - if 'id' in inputs: + if "id" in inputs: if isinstance(self._state, dict): - self._state['id'] = inputs['id'] + self._state["id"] = inputs["id"] elif isinstance(self._state, BaseModel): - setattr(self._state, 'id', inputs['id']) + setattr(self._state, "id", inputs["id"]) if stored_state: - self._log_flow_event(f"Loading flow state from memory for UUID: {restore_uuid}", color="yellow") + self._log_flow_event( + f"Loading flow state from memory for UUID: {restore_uuid}", + color="yellow", + ) # Restore the state self._restore_state(stored_state) else: - self._log_flow_event(f"No flow state found for UUID: {restore_uuid}", color="red") + self._log_flow_event( + f"No flow state found for UUID: {restore_uuid}", color="red" + ) # Apply any additional inputs after restoration - filtered_inputs = {k: v for k, v in inputs.items() if k != 'id'} + filtered_inputs = {k: v for k, v in inputs.items() if k != "id"} if filtered_inputs: self._initialize_state(filtered_inputs) # Start flow execution - self.event_emitter.send( + event_bus.emit( self, - event=FlowStartedEvent( + FlowStarted( type="flow_started", flow_name=self.__class__.__name__, ), ) - self._log_flow_event(f"Flow started with ID: {self.flow_id}", color="bold_magenta") + self._log_flow_event( + f"Flow started with ID: {self.flow_id}", color="bold_magenta" + ) - if inputs is not None and 'id' not in inputs: + if inputs is not None and "id" not in inputs: self._initialize_state(inputs) return asyncio.run(self.kickoff_async()) @@ -760,9 +767,9 @@ class Flow(Generic[T], metaclass=FlowMeta): final_output = self._method_outputs[-1] if self._method_outputs else None - self.event_emitter.send( + event_bus.emit( self, - event=FlowFinishedEvent( + FlowFinished( type="flow_finished", flow_name=self.__class__.__name__, result=final_output, @@ -944,9 +951,9 @@ class Flow(Generic[T], metaclass=FlowMeta): try: method = self._methods[listener_name] - self.event_emitter.send( + event_bus.emit( self, - event=MethodExecutionStartedEvent( + MethodExecutionStarted( type="method_execution_started", method_name=listener_name, flow_name=self.__class__.__name__, @@ -964,9 +971,9 @@ class Flow(Generic[T], metaclass=FlowMeta): else: listener_result = await self._execute_method(listener_name, method) - self.event_emitter.send( + event_bus.emit( self, - event=MethodExecutionFinishedEvent( + MethodExecutionFinished( type="method_execution_finished", method_name=listener_name, flow_name=self.__class__.__name__, @@ -984,7 +991,9 @@ class Flow(Generic[T], metaclass=FlowMeta): traceback.print_exc() - def _log_flow_event(self, message: str, color: str = "yellow", level: str = "info") -> None: + def _log_flow_event( + self, message: str, color: str = "yellow", level: str = "info" + ) -> None: """Centralized logging method for flow events. This method provides a consistent interface for logging flow-related events, diff --git a/src/crewai/task.py b/src/crewai/task.py index cbf651f9b..b4edf24e1 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -40,6 +40,8 @@ from crewai.telemetry.telemetry import Telemetry from crewai.tools.base_tool import BaseTool from crewai.utilities.config import process_config from crewai.utilities.converter import Converter, convert_to_model +from crewai.utilities.events.events import emit +from crewai.utilities.events.task_events import TaskCompleted, TaskStarted from crewai.utilities.i18n import I18N from crewai.utilities.printer import Printer @@ -348,6 +350,7 @@ class Task(BaseModel): tools: Optional[List[Any]], ) -> TaskOutput: """Run the core execution logic of the task.""" + agent = agent or self.agent self.agent = agent if not agent: @@ -362,7 +365,7 @@ class Task(BaseModel): tools = tools or self.tools or [] self.processed_by_agents.add(agent.role) - + emit(self, TaskStarted(task=self)) result = agent.execute_task( task=self, context=context, @@ -436,7 +439,7 @@ class Task(BaseModel): else result ) self._save_file(content) - + emit(self, TaskCompleted(task=self, output=task_output)) return task_output def prompt(self) -> str: diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index 218410ef7..00273ae6e 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional, Union import json5 from json_repair import repair_json -import crewai.utilities.events as events +import crewai.utilities.events.events as events from crewai.agents.tools_handler import ToolsHandler from crewai.task import Task from crewai.telemetry import Telemetry @@ -116,7 +116,10 @@ class ToolUsage: self._printer.print(content=f"\n\n{error}\n", color="red") return error - if isinstance(tool, CrewStructuredTool) and tool.name == self._i18n.tools("add_image")["name"]: # type: ignore + if ( + isinstance(tool, CrewStructuredTool) + and tool.name == self._i18n.tools("add_image")["name"] + ): # type: ignore try: result = self._use(tool_string=tool_string, tool=tool, calling=calling) return result @@ -181,7 +184,9 @@ class ToolUsage: if calling.arguments: try: - acceptable_args = tool.args_schema.model_json_schema()["properties"].keys() # type: ignore + acceptable_args = tool.args_schema.model_json_schema()[ + "properties" + ].keys() # type: ignore arguments = { k: v for k, v in calling.arguments.items() @@ -460,9 +465,7 @@ class ToolUsage: def on_tool_error(self, tool: Any, tool_calling: ToolCalling, e: Exception) -> None: event_data = self._prepare_event_data(tool, tool_calling) - events.emit( - source=self, event=ToolUsageError(**{**event_data, "error": str(e)}) - ) + events.emit(self, event=ToolUsageError(**{**event_data, "error": str(e)})) def on_tool_use_finished( self, tool: Any, tool_calling: ToolCalling, from_cache: bool, started_at: float @@ -476,7 +479,7 @@ class ToolUsage: "from_cache": from_cache, } ) - events.emit(source=self, event=ToolUsageFinished(**event_data)) + events.emit(self, event=ToolUsageFinished(**event_data)) def _prepare_event_data(self, tool: Any, tool_calling: ToolCalling) -> dict: return { diff --git a/src/crewai/utilities/events/__init__.py b/src/crewai/utilities/events/__init__.py new file mode 100644 index 000000000..701199b41 --- /dev/null +++ b/src/crewai/utilities/events/__init__.py @@ -0,0 +1,36 @@ +from .crew_events import ( + CrewKickoffStarted, + CrewKickoffCompleted, + CrewKickoffFailed, +) +from .agent_events import AgentExecutionStarted, AgentExecutionCompleted +from .task_events import TaskStarted, TaskCompleted +from .flow_events import FlowStarted, FlowFinished, MethodExecutionStarted, MethodExecutionFinished +from .event_bus import event_bus, EventTypes +from .events import emit, on +from .event_bus import EventBus +from .event_listener import EventListener + +event_bus = EventBus() +event_listener = EventListener() + +__all__ = [ + AgentExecutionStarted, + AgentExecutionCompleted, + CrewKickoffStarted, + CrewKickoffCompleted, + CrewKickoffFailed, + TaskStarted, + TaskCompleted, + FlowStarted, + FlowFinished, + MethodExecutionStarted, + MethodExecutionFinished, + EventTypes, + emit, + on, + event_bus +] + + + diff --git a/src/crewai/utilities/events/agent_events.py b/src/crewai/utilities/events/agent_events.py new file mode 100644 index 000000000..40ad793d8 --- /dev/null +++ b/src/crewai/utilities/events/agent_events.py @@ -0,0 +1,35 @@ +from typing import Any + +from .crew_events import CrewEvent + + +class AgentExecutionStarted(CrewEvent): + """Event emitted when an agent starts executing a task""" + + agent: Any # type: ignore + task: Any # type: ignore + type: str = "agent_execution_started" + + model_config = {"arbitrary_types_allowed": True} + + +class AgentExecutionCompleted(CrewEvent): + """Event emitted when an agent completes executing a task""" + + agent: Any + task: Any + output: str + type: str = "agent_execution_completed" + + model_config = {"arbitrary_types_allowed": True} + + +class AgentExecutionError(CrewEvent): + """Event emitted when an agent encounters an error during execution""" + + agent: Any + task: Any + error: str + type: str = "agent_execution_error" + + model_config = {"arbitrary_types_allowed": True} diff --git a/src/crewai/utilities/events/crew_events.py b/src/crewai/utilities/events/crew_events.py new file mode 100644 index 000000000..e34d3b240 --- /dev/null +++ b/src/crewai/utilities/events/crew_events.py @@ -0,0 +1,91 @@ +from datetime import datetime +from enum import Enum +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Field + + +class RunType(Enum): + KICKOFF = "kickoff" + TEST = "test" + TRAIN = "train" + + +class CrewEvent(BaseModel): + """Base class for all crew events""" + + timestamp: datetime = Field(default_factory=datetime.now) + type: str + + +class CrewKickoffStarted(CrewEvent): + """Event emitted when a crew starts execution""" + + crew_name: Optional[str] + inputs: Optional[Dict[str, Any]] + type: str = "crew_kickoff_started" + + +class CrewKickoffCompleted(CrewEvent): + """Event emitted when a crew completes execution""" + + crew_name: Optional[str] + output: Any + type: str = "crew_kickoff_completed" + + +class CrewKickoffFailed(CrewEvent): + """Event emitted when a crew fails to complete execution""" + + error: str + type: str = "crew_kickoff_failed" + + +class CrewTrainStarted(CrewEvent): + """Event emitted when a crew starts training""" + + crew_name: Optional[str] + n_iterations: int + filename: str + inputs: Optional[Dict[str, Any]] + type: str = "crew_train_started" + + +class CrewTrainCompleted(CrewEvent): + """Event emitted when a crew completes training""" + + crew_name: Optional[str] + n_iterations: int + filename: str + type: str = "crew_train_completed" + + +class CrewTrainFailed(CrewEvent): + """Event emitted when a crew fails to complete training""" + + error: str + type: str = "crew_train_failed" + + +class CrewTestStarted(CrewEvent): + """Event emitted when a crew starts testing""" + + crew_name: Optional[str] + n_iterations: int + openai_model_name: Optional[str] + inputs: Optional[Dict[str, Any]] + type: str = "crew_test_started" + + +class CrewTestCompleted(CrewEvent): + """Event emitted when a crew completes testing""" + + crew_name: Optional[str] + type: str = "crew_test_completed" + + +class CrewTestFailed(CrewEvent): + """Event emitted when a crew fails to complete testing""" + + error: str + type: str = "crew_test_failed" diff --git a/src/crewai/utilities/events/event_bus.py b/src/crewai/utilities/events/event_bus.py new file mode 100644 index 000000000..6651a09af --- /dev/null +++ b/src/crewai/utilities/events/event_bus.py @@ -0,0 +1,99 @@ +import threading +from contextlib import contextmanager +from typing import Any, Callable, Dict, List, Type + +from blinker import Signal + +from .event_types import EventTypes + + +class EventBus: + """ + A singleton event bus that uses blinker signals for event handling. + Allows both internal (Flow/Crew) and external event handling. + """ + + _instance = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + if cls._instance is None: # prevent race condition + cls._instance = super(EventBus, cls).__new__(cls) + cls._instance._initialize() + return cls._instance + + def _initialize(self): + """Initialize the event bus internal state""" + self._signal = Signal("event_bus") + self._handlers: Dict[Type[EventTypes], List[Callable]] = {} + + def on(self, event_type: Type[EventTypes]) -> Callable: + """ + Decorator to register an event handler for a specific event type. + + Usage: + @event_bus.on(CrewKickoffStarted) + def handle_kickoff(source, event): + print(f"Crew kickoff started: {event}") + """ + + def decorator(handler: Callable[[Any, EventTypes], None]): + if event_type not in self._handlers: + self._handlers[event_type] = [] + self._handlers[event_type].append(handler) + return handler + + return decorator + + def emit(self, source: Any, event: EventTypes) -> None: + """ + Emit an event to all registered handlers + + Args: + source: The object emitting the event + event: The event instance to emit + """ + event_type = type(event) + if event_type in self._handlers: + for handler in self._handlers[event_type]: + handler(source, event) + self._signal.send(source, event=event) + + def clear_handlers(self) -> None: + """Clear all registered event handlers - useful for testing""" + self._handlers.clear() + + def register_handler( + self, event_type: Type[EventTypes], handler: Callable[[Any, EventTypes], None] + ) -> None: + """Register an event handler for a specific event type""" + if event_type not in self._handlers: + self._handlers[event_type] = [] + self._handlers[event_type].append(handler) + + @contextmanager + def scoped_handlers(self): + """ + Context manager for temporary event handling scope. + Useful for testing or temporary event handling. + + Usage: + with event_bus.scoped_handlers(): + @event_bus.on(CrewKickoffStarted) + def temp_handler(source, event): + print("Temporary handler") + # Do stuff... + # Handlers are cleared after the context + """ + previous_handlers = self._handlers.copy() + self._handlers.clear() + try: + yield + finally: + self._handlers = previous_handlers + + +# Global instance +event_bus = EventBus() diff --git a/src/crewai/utilities/events/event_listener.py b/src/crewai/utilities/events/event_listener.py new file mode 100644 index 000000000..4b59b4630 --- /dev/null +++ b/src/crewai/utilities/events/event_listener.py @@ -0,0 +1,101 @@ +from crewai.telemetry.telemetry import Telemetry +from crewai.utilities.evaluators.task_evaluator import TaskEvaluator + +from .agent_events import ( + AgentExecutionCompleted, + AgentExecutionStarted, +) +from .crew_events import ( + CrewKickoffCompleted, + CrewKickoffStarted, + CrewTestCompleted, + CrewTestStarted, +) +from .event_bus import EventBus +from .flow_events import ( + FlowFinished, + FlowStarted, + MethodExecutionFinished, + MethodExecutionStarted, +) +from .task_events import TaskCompleted, TaskStarted + + +class EventListener: + _telemetry = Telemetry() + + def __init__(self): + print("Initializing EventListener") + self._setup_listeners() + self._telemetry.set_tracer() + + def _setup_listeners(self): + event_bus = EventBus() + + @event_bus.on(CrewKickoffStarted) + def on_crew_started(source, event): + print(f"🚀 Crew '{event.crew_name}' started", event.timestamp) + print("event.inputs", event.inputs) + self._telemetry.crew_execution_span(source, event.inputs) + + @event_bus.on(CrewKickoffCompleted) + def on_crew_completed(source, event): + final_string_output = event.output.raw + self._telemetry.end_crew(source, final_string_output) + + @event_bus.on(CrewTestStarted) + def on_crew_test_started(source, event): + cloned_crew = source.copy() + cloned_crew._telemetry.test_execution_span( + cloned_crew, + event.n_iterations, + event.inputs, + event.openai_model_name, + ) + print(f"🚀 Crew '{event.crew_name}' started test") + + @event_bus.on(CrewTestCompleted) + def on_crew_test_completed(source, event): + print(f"👍 Crew '{event.crew_name}' completed test") + + @event_bus.on(TaskStarted) + def on_task_started(source, event): + print(f"📋 Task started: {event.task.description}") + + @event_bus.on(TaskCompleted) + def on_task_completed(source, event): + print(f"✓ Task completed: {event.task.description}") + print(f" Output: {event.output}") + result = TaskEvaluator(event.task.agent).evaluate(event.task, event.output) + print(f" Evaluation: {result.quality}") + if result.quality > 5: + print(f" ✅ Passed: {result.suggestions}") + else: + print(f" ❌ Failed: {result.suggestions}") + + @event_bus.on(AgentExecutionStarted) + def on_agent_execution_started(source, event): + print( + f"🤖 Agent '{event.agent.role}' started task: {event.task.description}" + ) + + @event_bus.on(AgentExecutionCompleted) + def on_agent_execution_completed(source, event): + print(f"👍 Agent '{event.agent.role}' completed task") + print(f" Output: {event.output}") + + @event_bus.on(FlowStarted) + def on_flow_started(source, event): + print(f"🤖 Flow Started: '{event.flow_name}'") + + @event_bus.on(FlowFinished) + def on_flow_finished(source, event): + print(f"👍 Flow Finished: '{event.flow_name}'") + + @event_bus.on(MethodExecutionStarted) + def on_method_execution_started(source, event): + print(f"🤖 Flow Method Started: '{event.method_name}'") + + @event_bus.on(MethodExecutionFinished) + def on_method_execution_finished(source, event): + print(f"👍 Flow Method Finished: '{event.method_name}'") diff --git a/src/crewai/utilities/events/event_types.py b/src/crewai/utilities/events/event_types.py new file mode 100644 index 000000000..ed568eefd --- /dev/null +++ b/src/crewai/utilities/events/event_types.py @@ -0,0 +1,47 @@ +from typing import Union + +from .agent_events import ( + AgentExecutionCompleted, + AgentExecutionStarted, +) +from .crew_events import ( + CrewKickoffCompleted, + CrewKickoffFailed, + CrewKickoffStarted, + CrewTestCompleted, + CrewTestFailed, + CrewTestStarted, + CrewTrainCompleted, + CrewTrainFailed, + CrewTrainStarted, +) +from .flow_events import ( + FlowFinished, + FlowStarted, + MethodExecutionFinished, + MethodExecutionStarted, +) +from .task_events import ( + TaskCompleted, + TaskStarted, +) + +EventTypes = Union[ + CrewKickoffStarted, + CrewKickoffCompleted, + CrewKickoffFailed, + CrewTestStarted, + CrewTestCompleted, + CrewTestFailed, + CrewTrainStarted, + CrewTrainCompleted, + CrewTrainFailed, + AgentExecutionStarted, + AgentExecutionCompleted, + TaskStarted, + TaskCompleted, + FlowStarted, + FlowFinished, + MethodExecutionStarted, + MethodExecutionFinished, +] diff --git a/src/crewai/utilities/events.py b/src/crewai/utilities/events/events.py similarity index 58% rename from src/crewai/utilities/events.py rename to src/crewai/utilities/events/events.py index 11175e0d2..203148bb0 100644 --- a/src/crewai/utilities/events.py +++ b/src/crewai/utilities/events/events.py @@ -1,7 +1,19 @@ from functools import wraps -from typing import Any, Callable, Dict, Generic, List, Type, TypeVar +from typing import ( + Any, + Callable, + Dict, + Generic, + List, + Type, + TypeVar, + TYPE_CHECKING, +) +from datetime import datetime +from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Field +from .event_types import EventTypes T = TypeVar("T") EVT = TypeVar("EVT", bound=BaseModel) @@ -27,18 +39,15 @@ class Emitter(Generic[T, EVT]): func(source, event) +# Global event emitter instance default_emitter = Emitter[Any, BaseModel]() -def emit(source: Any, event: BaseModel, raise_on_error: bool = False) -> None: - try: - default_emitter.emit(source, event) - except Exception as e: - if raise_on_error: - raise e - else: - print(f"Error emitting event: {e}") +def emit(source: Any, event: BaseModel) -> None: + """Emit an event to all registered listeners""" + default_emitter.emit(source, event) -def on(event_type: Type[BaseModel]) -> Callable: +def on(event_type: Type[EventTypes]) -> Callable: + """Register a listener for a specific event type""" return default_emitter.on(event_type) diff --git a/src/crewai/utilities/events/flow_events.py b/src/crewai/utilities/events/flow_events.py new file mode 100644 index 000000000..27ef6446a --- /dev/null +++ b/src/crewai/utilities/events/flow_events.py @@ -0,0 +1,34 @@ +from typing import Any, Dict, Optional + +from .crew_events import CrewEvent + + +class FlowStarted(CrewEvent): + """Event emitted when a flow starts execution""" + + flow_name: str + type: str = "flow_started" + + +class MethodExecutionStarted(CrewEvent): + """Event emitted when a flow method starts execution""" + + flow_name: str + method_name: str + type: str = "method_execution_started" + + +class MethodExecutionFinished(CrewEvent): + """Event emitted when a flow method completes execution""" + + flow_name: str + method_name: str + type: str = "method_execution_finished" + + +class FlowFinished(CrewEvent): + """Event emitted when a flow completes execution""" + + flow_name: str + result: Optional[Any] = None + type: str = "flow_finished" diff --git a/src/crewai/utilities/events/task_events.py b/src/crewai/utilities/events/task_events.py new file mode 100644 index 000000000..c0888409c --- /dev/null +++ b/src/crewai/utilities/events/task_events.py @@ -0,0 +1,22 @@ +from typing import Any + +from crewai.utilities.events.crew_events import CrewEvent + + +class TaskStarted(CrewEvent): + """Event emitted when a task starts""" + + task: Any + type: str = "task_started" + + model_config = {"arbitrary_types_allowed": True} + + +class TaskCompleted(CrewEvent): + """Event emitted when a task completes""" + + task: Any + output: Any + type: str = "task_completed" + + model_config = {"arbitrary_types_allowed": True} diff --git a/tests/utilities/cassettes/test_agent_emits_execution_error_event.yaml b/tests/utilities/cassettes/test_agent_emits_execution_error_event.yaml new file mode 100644 index 000000000..2125bce2b --- /dev/null +++ b/tests/utilities/cassettes/test_agent_emits_execution_error_event.yaml @@ -0,0 +1,230 @@ +interactions: +- request: + body: !!binary | + Cp0mCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS9CUKEgoQY3Jld2FpLnRl + bGVtZXRyeRKkBwoQsRaqiDfyxK42TccQwJG7SRII4z1RDvMH0b0qDENyZXcgQ3JlYXRlZDABOdiW + FcGxJCEYQXB3JMGxJCEYShsKDmNyZXdhaV92ZXJzaW9uEgkKBzAuMTAwLjBKGgoOcHl0aG9uX3Zl + cnNpb24SCAoGMy4xMi44Si4KCGNyZXdfa2V5EiIKIGU1ODA3MDFkNTJlYjY1YWZmMjRlZWZlNzhj + NzQ2MjhjSjEKB2NyZXdfaWQSJgokN2UyNDlhZmMtZDlkYS00OTVmLWE4NGItMjI0MWFkY2U3ZGVi + ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3 + X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrRAgoLY3Jl + d19hZ2VudHMSwQIKvgJbeyJrZXkiOiAiYWQxNTMxNjFjNWM1YTg1NmFhMGQwNmIyNDljNGM2NGEi + LCAiaWQiOiAiMjI4ZWZlMTYtZjEwMC00ZjA0LTllYzItMDQxNTc2ZDMwODNmIiwgInJvbGUiOiAi + YmFzZV9hZ2VudCIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0i + OiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIs + ICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBm + YWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K/wEKCmNyZXdf + dGFza3MS8AEK7QFbeyJrZXkiOiAiMWIxNWVmMjM5MTViMjc1NWU4OWEwZWMzYjI2YTEzZDIiLCAi + aWQiOiAiMzNiM2YxYTktODM5Yy00YzgxLWFkOTgtNjFiYzMzMzljM2Y2IiwgImFzeW5jX2V4ZWN1 + dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJiYXNl + X2FnZW50IiwgImFnZW50X2tleSI6ICJhZDE1MzE2MWM1YzVhODU2YWEwZDA2YjI0OWM0YzY0YSIs + ICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChC7VQnwwfAXyqbifjiX/Q9QEghiZojr + aZisPioMVGFzayBDcmVhdGVkMAE56HQ2wbEkIRhBeNo2wbEkIRhKLgoIY3Jld19rZXkSIgogZTU4 + MDcwMWQ1MmViNjVhZmYyNGVlZmU3OGM3NDYyOGNKMQoHY3Jld19pZBImCiQ3ZTI0OWFmYy1kOWRh + LTQ5NWYtYTg0Yi0yMjQxYWRjZTdkZWJKLgoIdGFza19rZXkSIgogMWIxNWVmMjM5MTViMjc1NWU4 + OWEwZWMzYjI2YTEzZDJKMQoHdGFza19pZBImCiQzM2IzZjFhOS04MzljLTRjODEtYWQ5OC02MWJj + MzMzOWMzZjZ6AhgBhQEAAQAAEqQHChAtKhMjNUrbCPSzpZno1kdhEgg4MmdqKqFAXSoMQ3JldyBD + cmVhdGVkMAE5SBC4xrEkIRhBmIXFxrEkIRhKGwoOY3Jld2FpX3ZlcnNpb24SCQoHMC4xMDAuMEoa + Cg5weXRob25fdmVyc2lvbhIICgYzLjEyLjhKLgoIY3Jld19rZXkSIgogZTU4MDcwMWQ1MmViNjVh + ZmYyNGVlZmU3OGM3NDYyOGNKMQoHY3Jld19pZBImCiQxNzg4Yjg4MC05NzhhLTQzMmMtYTViYi05 + ZmY0ZjkxNGMwMWVKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkS + AhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMS + AhgBStECCgtjcmV3X2FnZW50cxLBAgq+Alt7ImtleSI6ICJhZDE1MzE2MWM1YzVhODU2YWEwZDA2 + YjI0OWM0YzY0YSIsICJpZCI6ICIyMjhlZmUxNi1mMTAwLTRmMDQtOWVjMi0wNDE1NzZkMzA4M2Yi + LCAicm9sZSI6ICJiYXNlX2FnZW50IiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIw + LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdw + dC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119 + XUr/AQoKY3Jld190YXNrcxLwAQrtAVt7ImtleSI6ICIxYjE1ZWYyMzkxNWIyNzU1ZTg5YTBlYzNi + MjZhMTNkMiIsICJpZCI6ICIzM2IzZjFhOS04MzljLTRjODEtYWQ5OC02MWJjMzMzOWMzZjYiLCAi + YXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9y + b2xlIjogImJhc2VfYWdlbnQiLCAiYWdlbnRfa2V5IjogImFkMTUzMTYxYzVjNWE4NTZhYTBkMDZi + MjQ5YzRjNjRhIiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEFPj8mjm/h4Yp972 + 93VhBIcSCC4p9zkalotLKgxUYXNrIENyZWF0ZWQwATlQ6eDGsSQhGEGogeHGsSQhGEouCghjcmV3 + X2tleRIiCiBlNTgwNzAxZDUyZWI2NWFmZjI0ZWVmZTc4Yzc0NjI4Y0oxCgdjcmV3X2lkEiYKJDE3 + ODhiODgwLTk3OGEtNDMyYy1hNWJiLTlmZjRmOTE0YzAxZUouCgh0YXNrX2tleRIiCiAxYjE1ZWYy + MzkxNWIyNzU1ZTg5YTBlYzNiMjZhMTNkMkoxCgd0YXNrX2lkEiYKJDMzYjNmMWE5LTgzOWMtNGM4 + MS1hZDk4LTYxYmMzMzM5YzNmNnoCGAGFAQABAAASpAcKEM7q3Vt0B41b8yIp9m3apr8SCNFRy5YU + USxvKgxDcmV3IENyZWF0ZWQwATnwwfjHsSQhGEE4dAPIsSQhGEobCg5jcmV3YWlfdmVyc2lvbhIJ + CgcwLjEwMC4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMuMTIuOEouCghjcmV3X2tleRIiCiBlNTgw + NzAxZDUyZWI2NWFmZjI0ZWVmZTc4Yzc0NjI4Y0oxCgdjcmV3X2lkEiYKJDQyYWExMjM2LTAyZmIt + NGU2MS1hMmYwLTMzOTQwZDUzNTljM0ocCgxjcmV3X3Byb2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtj + cmV3X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJfb2ZfdGFza3MSAhgBShsKFWNyZXdfbnVtYmVy + X29mX2FnZW50cxICGAFK0QIKC2NyZXdfYWdlbnRzEsECCr4CW3sia2V5IjogImFkMTUzMTYxYzVj + NWE4NTZhYTBkMDZiMjQ5YzRjNjRhIiwgImlkIjogIjIyOGVmZTE2LWYxMDAtNGYwNC05ZWMyLTA0 + MTU3NmQzMDgzZiIsICJyb2xlIjogImJhc2VfYWdlbnQiLCAidmVyYm9zZT8iOiBmYWxzZSwgIm1h + eF9pdGVyIjogMjAsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIs + ICJsbG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxs + b3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNf + bmFtZXMiOiBbXX1dSv8BCgpjcmV3X3Rhc2tzEvABCu0BW3sia2V5IjogIjFiMTVlZjIzOTE1YjI3 + NTVlODlhMGVjM2IyNmExM2QyIiwgImlkIjogIjMzYjNmMWE5LTgzOWMtNGM4MS1hZDk4LTYxYmMz + MzM5YzNmNiIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxz + ZSwgImFnZW50X3JvbGUiOiAiYmFzZV9hZ2VudCIsICJhZ2VudF9rZXkiOiAiYWQxNTMxNjFjNWM1 + YTg1NmFhMGQwNmIyNDljNGM2NGEiLCAidG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQ + Gm3H7iAWI9H+2713+jzGiBIIZtKcjlj6IXgqDFRhc2sgQ3JlYXRlZDABOahOIsixJCEYQXD+Isix + JCEYSi4KCGNyZXdfa2V5EiIKIGU1ODA3MDFkNTJlYjY1YWZmMjRlZWZlNzhjNzQ2MjhjSjEKB2Ny + ZXdfaWQSJgokNDJhYTEyMzYtMDJmYi00ZTYxLWEyZjAtMzM5NDBkNTM1OWMzSi4KCHRhc2tfa2V5 + EiIKIDFiMTVlZjIzOTE1YjI3NTVlODlhMGVjM2IyNmExM2QySjEKB3Rhc2tfaWQSJgokMzNiM2Yx + YTktODM5Yy00YzgxLWFkOTgtNjFiYzMzMzljM2Y2egIYAYUBAAEAABKkBwoQXRdWLcLxR1WXiLgw + RBPKChIIvt3M2oEaowsqDENyZXcgQ3JlYXRlZDABOXgVEsmxJCEYQUAAHsmxJCEYShsKDmNyZXdh + aV92ZXJzaW9uEgkKBzAuMTAwLjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4xMi44Si4KCGNyZXdf + a2V5EiIKIGU1ODA3MDFkNTJlYjY1YWZmMjRlZWZlNzhjNzQ2MjhjSjEKB2NyZXdfaWQSJgokMGFl + NTlkZWQtMTg0Yy00OGNiLWE2NTktNDljYjQ5OTE4NmMyShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1 + ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90YXNrcxICGAFKGwoV + Y3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrRAgoLY3Jld19hZ2VudHMSwQIKvgJbeyJrZXkiOiAi + YWQxNTMxNjFjNWM1YTg1NmFhMGQwNmIyNDljNGM2NGEiLCAiaWQiOiAiMjI4ZWZlMTYtZjEwMC00 + ZjA0LTllYzItMDQxNTc2ZDMwODNmIiwgInJvbGUiOiAiYmFzZV9hZ2VudCIsICJ2ZXJib3NlPyI6 + IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGlu + Z19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/Ijog + ZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6 + IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K/wEKCmNyZXdfdGFza3MS8AEK7QFbeyJrZXkiOiAiMWIx + NWVmMjM5MTViMjc1NWU4OWEwZWMzYjI2YTEzZDIiLCAiaWQiOiAiMzNiM2YxYTktODM5Yy00Yzgx + LWFkOTgtNjFiYzMzMzljM2Y2IiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lu + cHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJiYXNlX2FnZW50IiwgImFnZW50X2tleSI6ICJh + ZDE1MzE2MWM1YzVhODU2YWEwZDA2YjI0OWM0YzY0YSIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgB + hQEAAQAAEo4CChDKds56rQdRT8dQWNa+o3W8Egigg8CiorCTbSoMVGFzayBDcmVhdGVkMAE5IMku + ybEkIRhBIEYvybEkIRhKLgoIY3Jld19rZXkSIgogZTU4MDcwMWQ1MmViNjVhZmYyNGVlZmU3OGM3 + NDYyOGNKMQoHY3Jld19pZBImCiQwYWU1OWRlZC0xODRjLTQ4Y2ItYTY1OS00OWNiNDk5MTg2YzJK + LgoIdGFza19rZXkSIgogMWIxNWVmMjM5MTViMjc1NWU4OWEwZWMzYjI2YTEzZDJKMQoHdGFza19p + ZBImCiQzM2IzZjFhOS04MzljLTRjODEtYWQ5OC02MWJjMzMzOWMzZjZ6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '4896' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Tue, 04 Feb 2025 23:21:22 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are failing_agent. You + are an agent that will fail\nYour personal goal is: Fail execution\nTo give + my best complete final answer to the task respond using the exact following + format:\n\nThought: I now can give a great answer\nFinal Answer: Your final + answer must be the great and the most complete as possible, it must be outcome + described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", + "content": "\nCurrent Task: This will fail\n\nThis is the expect criteria for + your final answer: hi\nyou MUST return the actual complete content as the final + answer, not a summary.\n\nBegin! This is VERY important to you, use the tools + available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '831' + content-type: + - application/json + cookie: + - _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxMWEsVzvCdMy7AcEMz0E1bIsZE0N\",\n \"object\": + \"chat.completion\",\n \"created\": 1738711278,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: hi\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 158,\n \"completion_tokens\": 12,\n \"total_tokens\": 170,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n" + headers: + CF-RAY: + - 90ce656b29f31726-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 23:21:23 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=EdoOe3Ie16YFhyq0JMG_D28BpfQRp_kw01d0usxe.4I-1738711283-1.0.1.1-_S91JqcT20T_.dkcf3nGz_8EvOultyOqoshsb1sa2ovKyLsNvktLklUhWKhcOsCRP7QjoUV.7fah2qCmS1cIbQ; + path=/; expires=Tue, 04-Feb-25 23:51:23 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '5144' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999810' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_40f9e17937c2aa2f80e75dd6c546aeac + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/utilities/cassettes/test_crew_emits_end_kickoff_event.yaml b/tests/utilities/cassettes/test_crew_emits_end_kickoff_event.yaml new file mode 100644 index 000000000..c20dc4d92 --- /dev/null +++ b/tests/utilities/cassettes/test_crew_emits_end_kickoff_event.yaml @@ -0,0 +1,315 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are base_agent. You are + a helpful assistant that just says hi\nYour personal goal is: Just say hi\nTo + give my best complete final answer to the task respond using the exact following + format:\n\nThought: I now can give a great answer\nFinal Answer: Your final + answer must be the great and the most complete as possible, it must be outcome + described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", + "content": "\nCurrent Task: Just say hi\n\nThis is the expect criteria for your + final answer: hi\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '836' + content-type: + - application/json + cookie: + - __cf_bm=4s6sWmJ49B9F_wNc1STtdZF1nikfl6uN9_ov3Xzfa8U-1738698987-1.0.1.1-lmbRRS1MHrDbnU93Gh16CP3qNczxxIrQnyBU7vpHSwNf6PdmuWOHKd1mkl5SBx6rg7p1NLaNUMyqDDcE0Mvjzw; + _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJK2OCJSkUj1plgbj59b4dC39QV2\",\n \"object\": + \"chat.completion\",\n \"created\": 1738698990,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: hi\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 161,\n \"completion_tokens\": 12,\n \"total_tokens\": 173,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n" + headers: + CF-RAY: + - 90cd396c0ab71698-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 19:56:30 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '951' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999810' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_2c3cb5caed61ccd1e058ef3e6301c691 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + Cq0TCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkShBMKEgoQY3Jld2FpLnRl + bGVtZXRyeRKkBwoQzBQBWCz+GLuI1awj3OPWrRIIGpT16t5bk6MqDENyZXcgQ3JlYXRlZDABOUBz + OyuEGSEYQYDBSCuEGSEYShsKDmNyZXdhaV92ZXJzaW9uEgkKBzAuMTAwLjBKGgoOcHl0aG9uX3Zl + cnNpb24SCAoGMy4xMi44Si4KCGNyZXdfa2V5EiIKIGU1ODA3MDFkNTJlYjY1YWZmMjRlZWZlNzhj + NzQ2MjhjSjEKB2NyZXdfaWQSJgokMDE3NjQ5ZWMtYTBlMS00MzYxLWFlNjgtYzA1N2E3ZGM5YzI5 + ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3 + X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrRAgoLY3Jl + d19hZ2VudHMSwQIKvgJbeyJrZXkiOiAiYWQxNTMxNjFjNWM1YTg1NmFhMGQwNmIyNDljNGM2NGEi + LCAiaWQiOiAiOGU3NzgyN2QtN2Y2OC00ZDA2LWI2YTctOWI4YjRkMGE0YzMzIiwgInJvbGUiOiAi + YmFzZV9hZ2VudCIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0i + OiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIs + ICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBm + YWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K/wEKCmNyZXdf + dGFza3MS8AEK7QFbeyJrZXkiOiAiMWIxNWVmMjM5MTViMjc1NWU4OWEwZWMzYjI2YTEzZDIiLCAi + aWQiOiAiOTJiZDIzMWYtYzAxMC00ZDI3LWIxNGYtZjE5NjEyZTBmZTkzIiwgImFzeW5jX2V4ZWN1 + dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJiYXNl + X2FnZW50IiwgImFnZW50X2tleSI6ICJhZDE1MzE2MWM1YzVhODU2YWEwZDA2YjI0OWM0YzY0YSIs + ICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChC22Au0eMkAAjV6cfU1NrNIEggxb1Bq + Xnll/ioMVGFzayBDcmVhdGVkMAE5IOJaK4QZIRhBwG5bK4QZIRhKLgoIY3Jld19rZXkSIgogZTU4 + MDcwMWQ1MmViNjVhZmYyNGVlZmU3OGM3NDYyOGNKMQoHY3Jld19pZBImCiQwMTc2NDllYy1hMGUx + LTQzNjEtYWU2OC1jMDU3YTdkYzljMjlKLgoIdGFza19rZXkSIgogMWIxNWVmMjM5MTViMjc1NWU4 + OWEwZWMzYjI2YTEzZDJKMQoHdGFza19pZBImCiQ5MmJkMjMxZi1jMDEwLTRkMjctYjE0Zi1mMTk2 + MTJlMGZlOTN6AhgBhQEAAQAAEqQHChC63jCLGR8RP8RmYiHrdNVeEggZ39ffmGm5xyoMQ3JldyBD + cmVhdGVkMAE5GFEe04QZIRhBELEq04QZIRhKGwoOY3Jld2FpX3ZlcnNpb24SCQoHMC4xMDAuMEoa + Cg5weXRob25fdmVyc2lvbhIICgYzLjEyLjhKLgoIY3Jld19rZXkSIgogZTU4MDcwMWQ1MmViNjVh + ZmYyNGVlZmU3OGM3NDYyOGNKMQoHY3Jld19pZBImCiQ5MTY4YmQxNC0yN2Q2LTQ3NWMtODljOC01 + NjJjOTAyMGIxOTBKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkS + AhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMS + AhgBStECCgtjcmV3X2FnZW50cxLBAgq+Alt7ImtleSI6ICJhZDE1MzE2MWM1YzVhODU2YWEwZDA2 + YjI0OWM0YzY0YSIsICJpZCI6ICI4ZTc3ODI3ZC03ZjY4LTRkMDYtYjZhNy05YjhiNGQwYTRjMzMi + LCAicm9sZSI6ICJiYXNlX2FnZW50IiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIw + LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdw + dC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119 + XUr/AQoKY3Jld190YXNrcxLwAQrtAVt7ImtleSI6ICIxYjE1ZWYyMzkxNWIyNzU1ZTg5YTBlYzNi + MjZhMTNkMiIsICJpZCI6ICI5MmJkMjMxZi1jMDEwLTRkMjctYjE0Zi1mMTk2MTJlMGZlOTMiLCAi + YXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9y + b2xlIjogImJhc2VfYWdlbnQiLCAiYWdlbnRfa2V5IjogImFkMTUzMTYxYzVjNWE4NTZhYTBkMDZi + MjQ5YzRjNjRhIiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEOo6FGs7r9hHrN+f + qhMTUysSCJgbYV+vQMbCKgxUYXNrIENyZWF0ZWQwATlAxjrThBkhGEEYIDvThBkhGEouCghjcmV3 + X2tleRIiCiBlNTgwNzAxZDUyZWI2NWFmZjI0ZWVmZTc4Yzc0NjI4Y0oxCgdjcmV3X2lkEiYKJDkx + NjhiZDE0LTI3ZDYtNDc1Yy04OWM4LTU2MmM5MDIwYjE5MEouCgh0YXNrX2tleRIiCiAxYjE1ZWYy + MzkxNWIyNzU1ZTg5YTBlYzNiMjZhMTNkMkoxCgd0YXNrX2lkEiYKJDkyYmQyMzFmLWMwMTAtNGQy + Ny1iMTRmLWYxOTYxMmUwZmU5M3oCGAGFAQABAAA= + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2480' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Tue, 04 Feb 2025 19:56:31 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "user", "content": "Assess the quality of the task + completed based on the description, expected output, and actual results.\n\nTask + Description:\nJust say hi\n\nExpected Output:\nhi\n\nActual Output:\nhi\n\nPlease + provide:\n- Bullet points suggestions to improve future similar tasks\n- A score + from 0 to 10 evaluating on completion, quality, and overall performance- Entities + extracted from the task output, if any, their type, description, and relationships"}], + "model": "gpt-4o-mini", "tool_choice": {"type": "function", "function": {"name": + "TaskEvaluation"}}, "tools": [{"type": "function", "function": {"name": "TaskEvaluation", + "description": "Correctly extracted `TaskEvaluation` with all the required parameters + with correct types", "parameters": {"$defs": {"Entity": {"properties": {"name": + {"description": "The name of the entity.", "title": "Name", "type": "string"}, + "type": {"description": "The type of the entity.", "title": "Type", "type": + "string"}, "description": {"description": "Description of the entity.", "title": + "Description", "type": "string"}, "relationships": {"description": "Relationships + of the entity.", "items": {"type": "string"}, "title": "Relationships", "type": + "array"}}, "required": ["name", "type", "description", "relationships"], "title": + "Entity", "type": "object"}}, "properties": {"suggestions": {"description": + "Suggestions to improve future similar tasks.", "items": {"type": "string"}, + "title": "Suggestions", "type": "array"}, "quality": {"description": "A score + from 0 to 10 evaluating on completion, quality, and overall performance, all + taking into account the task description, expected output, and the result of + the task.", "title": "Quality", "type": "number"}, "entities": {"description": + "Entities extracted from the task output.", "items": {"$ref": "#/$defs/Entity"}, + "title": "Entities", "type": "array"}}, "required": ["entities", "quality", + "suggestions"], "type": "object"}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1962' + content-type: + - application/json + cookie: + - __cf_bm=4s6sWmJ49B9F_wNc1STtdZF1nikfl6uN9_ov3Xzfa8U-1738698987-1.0.1.1-lmbRRS1MHrDbnU93Gh16CP3qNczxxIrQnyBU7vpHSwNf6PdmuWOHKd1mkl5SBx6rg7p1NLaNUMyqDDcE0Mvjzw; + _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJK3bJiyqGhPeqdCcCjoeNavGHrR\",\n \"object\": + \"chat.completion\",\n \"created\": 1738698991,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_uAFkclWHIRqgrXFrQFcEoUIS\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"TaskEvaluation\",\n + \ \"arguments\": \"{\\\"suggestions\\\":[\\\"Include additional + context for the greeting to make it more meaningful.\\\",\\\"Specify if you + want a casual or formal tone for greetings.\\\",\\\"Provide examples of variations + of the greeting if necessary.\\\"],\\\"quality\\\":10,\\\"entities\\\":[],\\\"relationships\\\":[]}\"\n + \ }\n }\n ],\n \"refusal\": null\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 273,\n \"completion_tokens\": 50,\n + \ \"total_tokens\": 323,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_bd83329f63\"\n}\n" + headers: + CF-RAY: + - 90cd3973589f1698-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 19:56:32 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '1408' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999876' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_519fd27ca3d5da4d541c4331654e0520 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/utilities/cassettes/test_crew_emits_end_task_event.yaml b/tests/utilities/cassettes/test_crew_emits_end_task_event.yaml new file mode 100644 index 000000000..09cb318d2 --- /dev/null +++ b/tests/utilities/cassettes/test_crew_emits_end_task_event.yaml @@ -0,0 +1,357 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are base_agent. You are + a helpful assistant that just says hi\nYour personal goal is: Just say hi\nTo + give my best complete final answer to the task respond using the exact following + format:\n\nThought: I now can give a great answer\nFinal Answer: Your final + answer must be the great and the most complete as possible, it must be outcome + described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", + "content": "\nCurrent Task: Just say hi\n\nThis is the expect criteria for your + final answer: hi\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '836' + content-type: + - application/json + cookie: + - __cf_bm=nedOdWE1YnKQYt1kSbrcA.zhwa3bZDzmZqTOjZYER0c-1738700521-1.0.1.1-xQk9iXOvqvyXNhkIOgc8Ws2WYcT1mJFkDCvCC8xA5joFD8QfNrBIAr_Qs6sIxt2EzXyeFwBA6gA8ZgWApCHx0Q; + _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJlK2np8dMxYgsDIuyz2TSKKELWh\",\n \"object\": + \"chat.completion\",\n \"created\": 1738700682,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: hi\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 161,\n \"completion_tokens\": 12,\n \"total_tokens\": 173,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n" + headers: + CF-RAY: + - 90cd62c1fdb0fa6a-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 20:24:42 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '326' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999810' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_22be86be6fd9d69ca8d310ef534e7bec + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + Cp0mCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS9CUKEgoQY3Jld2FpLnRl + bGVtZXRyeRKkBwoQfRmsiIy66Scw43CcUw5ZiBIIfYDEhWJTOTkqDENyZXcgQ3JlYXRlZDABOcD6 + XvsOGyEYQWC2bPsOGyEYShsKDmNyZXdhaV92ZXJzaW9uEgkKBzAuMTAwLjBKGgoOcHl0aG9uX3Zl + cnNpb24SCAoGMy4xMi44Si4KCGNyZXdfa2V5EiIKIGU1ODA3MDFkNTJlYjY1YWZmMjRlZWZlNzhj + NzQ2MjhjSjEKB2NyZXdfaWQSJgokMDMzNTZiYmEtMzJmZC00OThmLTgxYTItYzc1ZDBkMzc2N2Qx + ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3 + X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrRAgoLY3Jl + d19hZ2VudHMSwQIKvgJbeyJrZXkiOiAiYWQxNTMxNjFjNWM1YTg1NmFhMGQwNmIyNDljNGM2NGEi + LCAiaWQiOiAiZjY5YTRmYTMtMzQ4OC00MmFhLTlhMTQtMGEzZmEyOWJmYjZjIiwgInJvbGUiOiAi + YmFzZV9hZ2VudCIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0i + OiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIs + ICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBm + YWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K/wEKCmNyZXdf + dGFza3MS8AEK7QFbeyJrZXkiOiAiMWIxNWVmMjM5MTViMjc1NWU4OWEwZWMzYjI2YTEzZDIiLCAi + aWQiOiAiNDllNzk0MmMtZTJiMy00YmE1LTg5MTUtMTYwYjQxMDU2ZmVlIiwgImFzeW5jX2V4ZWN1 + dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJiYXNl + X2FnZW50IiwgImFnZW50X2tleSI6ICJhZDE1MzE2MWM1YzVhODU2YWEwZDA2YjI0OWM0YzY0YSIs + ICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChCLgjutPq/3tkNyPqVjfbZhEgjhB7lb + clxzdyoMVGFzayBDcmVhdGVkMAE5CC9++w4bIRhBeMN++w4bIRhKLgoIY3Jld19rZXkSIgogZTU4 + MDcwMWQ1MmViNjVhZmYyNGVlZmU3OGM3NDYyOGNKMQoHY3Jld19pZBImCiQwMzM1NmJiYS0zMmZk + LTQ5OGYtODFhMi1jNzVkMGQzNzY3ZDFKLgoIdGFza19rZXkSIgogMWIxNWVmMjM5MTViMjc1NWU4 + OWEwZWMzYjI2YTEzZDJKMQoHdGFza19pZBImCiQ0OWU3OTQyYy1lMmIzLTRiYTUtODkxNS0xNjBi + NDEwNTZmZWV6AhgBhQEAAQAAEqQHChC7SpRSs6eG9XFmYuMQgghQEghpZMlScOy2DyoMQ3JldyBD + cmVhdGVkMAE50O2cAA8bIRhBOFmrAA8bIRhKGwoOY3Jld2FpX3ZlcnNpb24SCQoHMC4xMDAuMEoa + Cg5weXRob25fdmVyc2lvbhIICgYzLjEyLjhKLgoIY3Jld19rZXkSIgogZTU4MDcwMWQ1MmViNjVh + ZmYyNGVlZmU3OGM3NDYyOGNKMQoHY3Jld19pZBImCiRlYTQ1MWY2OS00Zjk3LTQ4MjYtOWNlYi04 + NTAzMDk2MTQ2MDlKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkS + AhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMS + AhgBStECCgtjcmV3X2FnZW50cxLBAgq+Alt7ImtleSI6ICJhZDE1MzE2MWM1YzVhODU2YWEwZDA2 + YjI0OWM0YzY0YSIsICJpZCI6ICJmNjlhNGZhMy0zNDg4LTQyYWEtOWExNC0wYTNmYTI5YmZiNmMi + LCAicm9sZSI6ICJiYXNlX2FnZW50IiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIw + LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdw + dC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119 + XUr/AQoKY3Jld190YXNrcxLwAQrtAVt7ImtleSI6ICIxYjE1ZWYyMzkxNWIyNzU1ZTg5YTBlYzNi + MjZhMTNkMiIsICJpZCI6ICI0OWU3OTQyYy1lMmIzLTRiYTUtODkxNS0xNjBiNDEwNTZmZWUiLCAi + YXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9y + b2xlIjogImJhc2VfYWdlbnQiLCAiYWdlbnRfa2V5IjogImFkMTUzMTYxYzVjNWE4NTZhYTBkMDZi + MjQ5YzRjNjRhIiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEM1q5zTl6Q6WKHPQ + eIqEm7sSCCWi2wvaFqpfKgxUYXNrIENyZWF0ZWQwATmIObwADxshGEEAo7wADxshGEouCghjcmV3 + X2tleRIiCiBlNTgwNzAxZDUyZWI2NWFmZjI0ZWVmZTc4Yzc0NjI4Y0oxCgdjcmV3X2lkEiYKJGVh + NDUxZjY5LTRmOTctNDgyNi05Y2ViLTg1MDMwOTYxNDYwOUouCgh0YXNrX2tleRIiCiAxYjE1ZWYy + MzkxNWIyNzU1ZTg5YTBlYzNiMjZhMTNkMkoxCgd0YXNrX2lkEiYKJDQ5ZTc5NDJjLWUyYjMtNGJh + NS04OTE1LTE2MGI0MTA1NmZlZXoCGAGFAQABAAASpAcKEEhIZzGdZRAdvfcluDR5qvESCFMGo60X + V/dYKgxDcmV3IENyZWF0ZWQwATnY8LgBDxshGEGQrsUBDxshGEobCg5jcmV3YWlfdmVyc2lvbhIJ + CgcwLjEwMC4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMuMTIuOEouCghjcmV3X2tleRIiCiBlNTgw + NzAxZDUyZWI2NWFmZjI0ZWVmZTc4Yzc0NjI4Y0oxCgdjcmV3X2lkEiYKJDA3MDJmOGU1LWRjMTYt + NDlhYi1hMWY1LThjOWQyY2IwMDYwYkocCgxjcmV3X3Byb2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtj + cmV3X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJfb2ZfdGFza3MSAhgBShsKFWNyZXdfbnVtYmVy + X29mX2FnZW50cxICGAFK0QIKC2NyZXdfYWdlbnRzEsECCr4CW3sia2V5IjogImFkMTUzMTYxYzVj + NWE4NTZhYTBkMDZiMjQ5YzRjNjRhIiwgImlkIjogImY2OWE0ZmEzLTM0ODgtNDJhYS05YTE0LTBh + M2ZhMjliZmI2YyIsICJyb2xlIjogImJhc2VfYWdlbnQiLCAidmVyYm9zZT8iOiBmYWxzZSwgIm1h + eF9pdGVyIjogMjAsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIs + ICJsbG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxs + b3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNf + bmFtZXMiOiBbXX1dSv8BCgpjcmV3X3Rhc2tzEvABCu0BW3sia2V5IjogIjFiMTVlZjIzOTE1YjI3 + NTVlODlhMGVjM2IyNmExM2QyIiwgImlkIjogIjQ5ZTc5NDJjLWUyYjMtNGJhNS04OTE1LTE2MGI0 + MTA1NmZlZSIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxz + ZSwgImFnZW50X3JvbGUiOiAiYmFzZV9hZ2VudCIsICJhZ2VudF9rZXkiOiAiYWQxNTMxNjFjNWM1 + YTg1NmFhMGQwNmIyNDljNGM2NGEiLCAidG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQ + PkgvMtq3aT5YPXE8gCRxPBIIMx/zQOuC+8sqDFRhc2sgQ3JlYXRlZDABOYjk2AEPGyEYQZiI2QEP + GyEYSi4KCGNyZXdfa2V5EiIKIGU1ODA3MDFkNTJlYjY1YWZmMjRlZWZlNzhjNzQ2MjhjSjEKB2Ny + ZXdfaWQSJgokMDcwMmY4ZTUtZGMxNi00OWFiLWExZjUtOGM5ZDJjYjAwNjBiSi4KCHRhc2tfa2V5 + EiIKIDFiMTVlZjIzOTE1YjI3NTVlODlhMGVjM2IyNmExM2QySjEKB3Rhc2tfaWQSJgokNDllNzk0 + MmMtZTJiMy00YmE1LTg5MTUtMTYwYjQxMDU2ZmVlegIYAYUBAAEAABKkBwoQex7nA0gUUrZHbN6F + gWp/gBIIKPd4fiRi7DwqDENyZXcgQ3JlYXRlZDABOTiIrAIPGyEYQdCAtwIPGyEYShsKDmNyZXdh + aV92ZXJzaW9uEgkKBzAuMTAwLjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4xMi44Si4KCGNyZXdf + a2V5EiIKIGU1ODA3MDFkNTJlYjY1YWZmMjRlZWZlNzhjNzQ2MjhjSjEKB2NyZXdfaWQSJgokMGNm + YjUzZWItMDA2Mi00YmVmLTk1ZTgtMDgwMjQ3NmNkMWRlShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1 + ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90YXNrcxICGAFKGwoV + Y3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrRAgoLY3Jld19hZ2VudHMSwQIKvgJbeyJrZXkiOiAi + YWQxNTMxNjFjNWM1YTg1NmFhMGQwNmIyNDljNGM2NGEiLCAiaWQiOiAiZjY5YTRmYTMtMzQ4OC00 + MmFhLTlhMTQtMGEzZmEyOWJmYjZjIiwgInJvbGUiOiAiYmFzZV9hZ2VudCIsICJ2ZXJib3NlPyI6 + IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGlu + Z19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/Ijog + ZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6 + IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K/wEKCmNyZXdfdGFza3MS8AEK7QFbeyJrZXkiOiAiMWIx + NWVmMjM5MTViMjc1NWU4OWEwZWMzYjI2YTEzZDIiLCAiaWQiOiAiNDllNzk0MmMtZTJiMy00YmE1 + LTg5MTUtMTYwYjQxMDU2ZmVlIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lu + cHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJiYXNlX2FnZW50IiwgImFnZW50X2tleSI6ICJh + ZDE1MzE2MWM1YzVhODU2YWEwZDA2YjI0OWM0YzY0YSIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgB + hQEAAQAAEo4CChD+ymllejCksajGYDua8tgNEghnjlejrbuw2SoMVGFzayBDcmVhdGVkMAE5EDrI + Ag8bIRhBGIzIAg8bIRhKLgoIY3Jld19rZXkSIgogZTU4MDcwMWQ1MmViNjVhZmYyNGVlZmU3OGM3 + NDYyOGNKMQoHY3Jld19pZBImCiQwY2ZiNTNlYi0wMDYyLTRiZWYtOTVlOC0wODAyNDc2Y2QxZGVK + LgoIdGFza19rZXkSIgogMWIxNWVmMjM5MTViMjc1NWU4OWEwZWMzYjI2YTEzZDJKMQoHdGFza19p + ZBImCiQ0OWU3OTQyYy1lMmIzLTRiYTUtODkxNS0xNjBiNDEwNTZmZWV6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '4896' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Tue, 04 Feb 2025 20:24:47 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "user", "content": "Assess the quality of the task + completed based on the description, expected output, and actual results.\n\nTask + Description:\nJust say hi\n\nExpected Output:\nhi\n\nActual Output:\nhi\n\nPlease + provide:\n- Bullet points suggestions to improve future similar tasks\n- A score + from 0 to 10 evaluating on completion, quality, and overall performance- Entities + extracted from the task output, if any, their type, description, and relationships"}], + "model": "gpt-4o-mini", "tool_choice": {"type": "function", "function": {"name": + "TaskEvaluation"}}, "tools": [{"type": "function", "function": {"name": "TaskEvaluation", + "description": "Correctly extracted `TaskEvaluation` with all the required parameters + with correct types", "parameters": {"$defs": {"Entity": {"properties": {"name": + {"description": "The name of the entity.", "title": "Name", "type": "string"}, + "type": {"description": "The type of the entity.", "title": "Type", "type": + "string"}, "description": {"description": "Description of the entity.", "title": + "Description", "type": "string"}, "relationships": {"description": "Relationships + of the entity.", "items": {"type": "string"}, "title": "Relationships", "type": + "array"}}, "required": ["name", "type", "description", "relationships"], "title": + "Entity", "type": "object"}}, "properties": {"suggestions": {"description": + "Suggestions to improve future similar tasks.", "items": {"type": "string"}, + "title": "Suggestions", "type": "array"}, "quality": {"description": "A score + from 0 to 10 evaluating on completion, quality, and overall performance, all + taking into account the task description, expected output, and the result of + the task.", "title": "Quality", "type": "number"}, "entities": {"description": + "Entities extracted from the task output.", "items": {"$ref": "#/$defs/Entity"}, + "title": "Entities", "type": "array"}}, "required": ["entities", "quality", + "suggestions"], "type": "object"}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1962' + content-type: + - application/json + cookie: + - __cf_bm=nedOdWE1YnKQYt1kSbrcA.zhwa3bZDzmZqTOjZYER0c-1738700521-1.0.1.1-xQk9iXOvqvyXNhkIOgc8Ws2WYcT1mJFkDCvCC8xA5joFD8QfNrBIAr_Qs6sIxt2EzXyeFwBA6gA8ZgWApCHx0Q; + _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJlLVC3gCB9gRI0ZSkoPCZY7EwpQ\",\n \"object\": + \"chat.completion\",\n \"created\": 1738700683,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_mgwImOITW8lkjzAyf9Pp76cL\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"TaskEvaluation\",\n + \ \"arguments\": \"{\\\"suggestions\\\":[\\\"Provide context or + additional information to make tasks more engaging.\\\",\\\"Encourage variations + in responses to make the interaction more dynamic.\\\"],\\\"quality\\\":10,\\\"entities\\\":[{\\\"name\\\":\\\"hi\\\",\\\"type\\\":\\\"greeting\\\",\\\"description\\\":\\\"A + common word used to initiate a conversation or express friendliness.\\\",\\\"relationships\\\":[\\\"initiates + conversation\\\",\\\"expresses friendliness\\\"]}]}\"\n }\n }\n + \ ],\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 273,\n \"completion_tokens\": 71,\n \"total_tokens\": 344,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_bd83329f63\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 90cd62c4ba41fa6a-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 20:24:50 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '7347' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999876' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_aec28dd3fe998d628754e8429623bf9e + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/utilities/cassettes/test_crew_emits_kickoff_events.yaml b/tests/utilities/cassettes/test_crew_emits_kickoff_events.yaml new file mode 100644 index 000000000..2233bde21 --- /dev/null +++ b/tests/utilities/cassettes/test_crew_emits_kickoff_events.yaml @@ -0,0 +1,245 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are base_agent. You are + a helpful assistant that just says hi\nYour personal goal is: Just say hi\nTo + give my best complete final answer to the task respond using the exact following + format:\n\nThought: I now can give a great answer\nFinal Answer: Your final + answer must be the great and the most complete as possible, it must be outcome + described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", + "content": "\nCurrent Task: Just say hi\n\nThis is the expect criteria for your + final answer: hi\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '836' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJIrSWAFqDEsNtLRhcM8vMHO9Ejw\",\n \"object\": + \"chat.completion\",\n \"created\": 1738698917,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: hi\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 161,\n \"completion_tokens\": 12,\n \"total_tokens\": 173,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n" + headers: + CF-RAY: + - 90cd37a83f5f176a-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 19:55:18 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=rKQWp4fbAvcCp4rasEN6DqiTjQfiWYpLfjcLpWcmzi0-1738698918-1.0.1.1-qlcCSdBY3KWbzVms0eLtz5ub5SSLGs_sRLxTdNhDk_purQuz9k6EFp8PHJfN3aP_sLnuyKnFlppM3.2k_dCtPQ; + path=/; expires=Tue, 04-Feb-25 20:25:18 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=Oi91zDXvjWohBYXSVqK4hFsq3_GZePEIIbi7b7wrjcI-1738698918130-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '894' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999810' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_864253996bbc0f797f9a2c1b9247a0d5 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "user", "content": "Assess the quality of the task + completed based on the description, expected output, and actual results.\n\nTask + Description:\nJust say hi\n\nExpected Output:\nhi\n\nActual Output:\nhi\n\nPlease + provide:\n- Bullet points suggestions to improve future similar tasks\n- A score + from 0 to 10 evaluating on completion, quality, and overall performance- Entities + extracted from the task output, if any, their type, description, and relationships"}], + "model": "gpt-4o-mini", "tool_choice": {"type": "function", "function": {"name": + "TaskEvaluation"}}, "tools": [{"type": "function", "function": {"name": "TaskEvaluation", + "description": "Correctly extracted `TaskEvaluation` with all the required parameters + with correct types", "parameters": {"$defs": {"Entity": {"properties": {"name": + {"description": "The name of the entity.", "title": "Name", "type": "string"}, + "type": {"description": "The type of the entity.", "title": "Type", "type": + "string"}, "description": {"description": "Description of the entity.", "title": + "Description", "type": "string"}, "relationships": {"description": "Relationships + of the entity.", "items": {"type": "string"}, "title": "Relationships", "type": + "array"}}, "required": ["name", "type", "description", "relationships"], "title": + "Entity", "type": "object"}}, "properties": {"suggestions": {"description": + "Suggestions to improve future similar tasks.", "items": {"type": "string"}, + "title": "Suggestions", "type": "array"}, "quality": {"description": "A score + from 0 to 10 evaluating on completion, quality, and overall performance, all + taking into account the task description, expected output, and the result of + the task.", "title": "Quality", "type": "number"}, "entities": {"description": + "Entities extracted from the task output.", "items": {"$ref": "#/$defs/Entity"}, + "title": "Entities", "type": "array"}}, "required": ["entities", "quality", + "suggestions"], "type": "object"}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1962' + content-type: + - application/json + cookie: + - __cf_bm=rKQWp4fbAvcCp4rasEN6DqiTjQfiWYpLfjcLpWcmzi0-1738698918-1.0.1.1-qlcCSdBY3KWbzVms0eLtz5ub5SSLGs_sRLxTdNhDk_purQuz9k6EFp8PHJfN3aP_sLnuyKnFlppM3.2k_dCtPQ; + _cfuvid=Oi91zDXvjWohBYXSVqK4hFsq3_GZePEIIbi7b7wrjcI-1738698918130-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJIsVEppA04iGQh0k6sanKnVObrO\",\n \"object\": + \"chat.completion\",\n \"created\": 1738698918,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_AQ3iizjGWjEvk1SmhGCzjbf1\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"TaskEvaluation\",\n + \ \"arguments\": \"{\\\"suggestions\\\":[\\\"Provide context for + the greeting, like a specific scenario or recipient.\\\",\\\"Encourage responses + or follow-ups to promote engagement.\\\",\\\"Specify the tone or formality of + the greeting, if relevant.\\\"],\\\"quality\\\":10,\\\"entities\\\":[{\\\"name\\\":\\\"hi\\\",\\\"type\\\":\\\"greeting\\\",\\\"description\\\":\\\"A + common informal expression used to initiate conversation or acknowledge someone.\\\",\\\"relationships\\\":[\\\"used + in conversation\\\",\\\"expresses friendliness\\\"]}]}\"\n }\n }\n + \ ],\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 273,\n \"completion_tokens\": 84,\n \"total_tokens\": 357,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_bd83329f63\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 90cd37aec8c8176a-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 19:55:21 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '3269' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999876' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_e6e67a3f5c6f2d48e0351cdce95edd97 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/utilities/cassettes/test_crew_emits_start_kickoff_event.yaml b/tests/utilities/cassettes/test_crew_emits_start_kickoff_event.yaml new file mode 100644 index 000000000..82333fe7d --- /dev/null +++ b/tests/utilities/cassettes/test_crew_emits_start_kickoff_event.yaml @@ -0,0 +1,243 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are base_agent. You are + a helpful assistant that just says hi\nYour personal goal is: Just say hi\nTo + give my best complete final answer to the task respond using the exact following + format:\n\nThought: I now can give a great answer\nFinal Answer: Your final + answer must be the great and the most complete as possible, it must be outcome + described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", + "content": "\nCurrent Task: Just say hi\n\nThis is the expect criteria for your + final answer: hi\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '836' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJJzafmayYpGTsTAWbOyZkmQJNa5\",\n \"object\": + \"chat.completion\",\n \"created\": 1738698987,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: hi\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 161,\n \"completion_tokens\": 12,\n \"total_tokens\": 173,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 90cd395b0e641698-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 19:56:27 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=4s6sWmJ49B9F_wNc1STtdZF1nikfl6uN9_ov3Xzfa8U-1738698987-1.0.1.1-lmbRRS1MHrDbnU93Gh16CP3qNczxxIrQnyBU7vpHSwNf6PdmuWOHKd1mkl5SBx6rg7p1NLaNUMyqDDcE0Mvjzw; + path=/; expires=Tue, 04-Feb-25 20:26:27 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '839' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999810' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_577b484a927b455c40ed80f9fd4d9106 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "user", "content": "Assess the quality of the task + completed based on the description, expected output, and actual results.\n\nTask + Description:\nJust say hi\n\nExpected Output:\nhi\n\nActual Output:\nhi\n\nPlease + provide:\n- Bullet points suggestions to improve future similar tasks\n- A score + from 0 to 10 evaluating on completion, quality, and overall performance- Entities + extracted from the task output, if any, their type, description, and relationships"}], + "model": "gpt-4o-mini", "tool_choice": {"type": "function", "function": {"name": + "TaskEvaluation"}}, "tools": [{"type": "function", "function": {"name": "TaskEvaluation", + "description": "Correctly extracted `TaskEvaluation` with all the required parameters + with correct types", "parameters": {"$defs": {"Entity": {"properties": {"name": + {"description": "The name of the entity.", "title": "Name", "type": "string"}, + "type": {"description": "The type of the entity.", "title": "Type", "type": + "string"}, "description": {"description": "Description of the entity.", "title": + "Description", "type": "string"}, "relationships": {"description": "Relationships + of the entity.", "items": {"type": "string"}, "title": "Relationships", "type": + "array"}}, "required": ["name", "type", "description", "relationships"], "title": + "Entity", "type": "object"}}, "properties": {"suggestions": {"description": + "Suggestions to improve future similar tasks.", "items": {"type": "string"}, + "title": "Suggestions", "type": "array"}, "quality": {"description": "A score + from 0 to 10 evaluating on completion, quality, and overall performance, all + taking into account the task description, expected output, and the result of + the task.", "title": "Quality", "type": "number"}, "entities": {"description": + "Entities extracted from the task output.", "items": {"$ref": "#/$defs/Entity"}, + "title": "Entities", "type": "array"}}, "required": ["entities", "quality", + "suggestions"], "type": "object"}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1962' + content-type: + - application/json + cookie: + - __cf_bm=4s6sWmJ49B9F_wNc1STtdZF1nikfl6uN9_ov3Xzfa8U-1738698987-1.0.1.1-lmbRRS1MHrDbnU93Gh16CP3qNczxxIrQnyBU7vpHSwNf6PdmuWOHKd1mkl5SBx6rg7p1NLaNUMyqDDcE0Mvjzw; + _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJJz10KP7iadNPdKsbcsvHBa7cic\",\n \"object\": + \"chat.completion\",\n \"created\": 1738698987,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_czeHQgy5eiOVa0zlrtcfwepe\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"TaskEvaluation\",\n + \ \"arguments\": \"{\\\"suggestions\\\":[\\\"Provide more context + or details for similar tasks to enhance output expectations.\\\",\\\"Encourage + creativity in responses for simple tasks to engage users more effectively.\\\"],\\\"quality\\\":10,\\\"entities\\\":[] + }\"\n }\n }\n ],\n \"refusal\": null\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 273,\n \"completion_tokens\": 40,\n + \ \"total_tokens\": 313,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_bd83329f63\"\n}\n" + headers: + CF-RAY: + - 90cd39615b281698-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 19:56:29 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '1411' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999876' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_3e717a80c7d9c5ea19893dd990aaae26 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/utilities/cassettes/test_crew_emits_start_task_event.yaml b/tests/utilities/cassettes/test_crew_emits_start_task_event.yaml new file mode 100644 index 000000000..e470049a7 --- /dev/null +++ b/tests/utilities/cassettes/test_crew_emits_start_task_event.yaml @@ -0,0 +1,245 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are base_agent. You are + a helpful assistant that just says hi\nYour personal goal is: Just say hi\nTo + give my best complete final answer to the task respond using the exact following + format:\n\nThought: I now can give a great answer\nFinal Answer: Your final + answer must be the great and the most complete as possible, it must be outcome + described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", + "content": "\nCurrent Task: Just say hi\n\nThis is the expect criteria for your + final answer: hi\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '836' + content-type: + - application/json + cookie: + - __cf_bm=4s6sWmJ49B9F_wNc1STtdZF1nikfl6uN9_ov3Xzfa8U-1738698987-1.0.1.1-lmbRRS1MHrDbnU93Gh16CP3qNczxxIrQnyBU7vpHSwNf6PdmuWOHKd1mkl5SBx6rg7p1NLaNUMyqDDcE0Mvjzw; + _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJiiHEQwIXsiG0Sd5wofcuhxVbo9\",\n \"object\": + \"chat.completion\",\n \"created\": 1738700520,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: hi\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 161,\n \"completion_tokens\": 12,\n \"total_tokens\": 173,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n" + headers: + CF-RAY: + - 90cd5ecd0f7667ee-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 20:22:01 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=nedOdWE1YnKQYt1kSbrcA.zhwa3bZDzmZqTOjZYER0c-1738700521-1.0.1.1-xQk9iXOvqvyXNhkIOgc8Ws2WYcT1mJFkDCvCC8xA5joFD8QfNrBIAr_Qs6sIxt2EzXyeFwBA6gA8ZgWApCHx0Q; + path=/; expires=Tue, 04-Feb-25 20:52:01 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '450' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999810' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_10eaafc81640a98a0a4789d270dd94d9 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "user", "content": "Assess the quality of the task + completed based on the description, expected output, and actual results.\n\nTask + Description:\nJust say hi\n\nExpected Output:\nhi\n\nActual Output:\nhi\n\nPlease + provide:\n- Bullet points suggestions to improve future similar tasks\n- A score + from 0 to 10 evaluating on completion, quality, and overall performance- Entities + extracted from the task output, if any, their type, description, and relationships"}], + "model": "gpt-4o-mini", "tool_choice": {"type": "function", "function": {"name": + "TaskEvaluation"}}, "tools": [{"type": "function", "function": {"name": "TaskEvaluation", + "description": "Correctly extracted `TaskEvaluation` with all the required parameters + with correct types", "parameters": {"$defs": {"Entity": {"properties": {"name": + {"description": "The name of the entity.", "title": "Name", "type": "string"}, + "type": {"description": "The type of the entity.", "title": "Type", "type": + "string"}, "description": {"description": "Description of the entity.", "title": + "Description", "type": "string"}, "relationships": {"description": "Relationships + of the entity.", "items": {"type": "string"}, "title": "Relationships", "type": + "array"}}, "required": ["name", "type", "description", "relationships"], "title": + "Entity", "type": "object"}}, "properties": {"suggestions": {"description": + "Suggestions to improve future similar tasks.", "items": {"type": "string"}, + "title": "Suggestions", "type": "array"}, "quality": {"description": "A score + from 0 to 10 evaluating on completion, quality, and overall performance, all + taking into account the task description, expected output, and the result of + the task.", "title": "Quality", "type": "number"}, "entities": {"description": + "Entities extracted from the task output.", "items": {"$ref": "#/$defs/Entity"}, + "title": "Entities", "type": "array"}}, "required": ["entities", "quality", + "suggestions"], "type": "object"}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1962' + content-type: + - application/json + cookie: + - __cf_bm=nedOdWE1YnKQYt1kSbrcA.zhwa3bZDzmZqTOjZYER0c-1738700521-1.0.1.1-xQk9iXOvqvyXNhkIOgc8Ws2WYcT1mJFkDCvCC8xA5joFD8QfNrBIAr_Qs6sIxt2EzXyeFwBA6gA8ZgWApCHx0Q; + _cfuvid=Cl48aI8.jSRja0Pqr6Jrh3mAnigd4rDn6lhGicyjMPY-1738698987673-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.8 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AxJijOhk12Ua6lS23IwtZTachfjq9\",\n \"object\": + \"chat.completion\",\n \"created\": 1738700521,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_DSteeMHHPf5RanJb8qjCo4qx\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"TaskEvaluation\",\n + \ \"arguments\": \"{\\\"suggestions\\\":[\\\"Consider adding context + for the greeting to make it more engaging.\\\",\\\"Specify if any additional + information or tone is desired in the greeting.\\\"],\\\"quality\\\":10,\\\"entities\\\":[{\\\"name\\\":\\\"greeting\\\",\\\"type\\\":\\\"text\\\",\\\"description\\\":\\\"A + simple greeting phrase\\\",\\\"relationships\\\":[\\\"is a\\\",\\\"is part of + a conversation\\\"]}]}\"\n }\n }\n ],\n \"refusal\": + null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 273,\n \"completion_tokens\": + 67,\n \"total_tokens\": 340,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_bd83329f63\"\n}\n" + headers: + CF-RAY: + - 90cd5ed20cb267ee-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Feb 2025 20:22:02 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '1624' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999876' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_4ee944acdd3928afbf6c5562403b064a + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/utilities/test_events.py b/tests/utilities/test_events.py new file mode 100644 index 000000000..15ac7d82d --- /dev/null +++ b/tests/utilities/test_events.py @@ -0,0 +1,184 @@ +import pytest +from datetime import datetime +from crewai.utilities.events.events import on, emit +from crewai.utilities.events.agent_events import ( + AgentExecutionStarted, + AgentExecutionCompleted, + AgentExecutionError, +) +from crewai.utilities.events.task_events import TaskStarted, TaskCompleted +from crewai.utilities.events.crew_events import CrewKickoffStarted, CrewKickoffCompleted +from crewai.crew import Crew +from crewai.agent import Agent +from crewai.task import Task +from unittest.mock import patch +from unittest import mock + +base_agent = Agent( + role="base_agent", + llm="gpt-4o-mini", + goal="Just say hi", + backstory="You are a helpful assistant that just says hi", +) + +base_task = Task( + description="Just say hi", + expected_output="hi", + agent=base_agent, +) + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_emits_start_kickoff_event(): + # Setup event listener + received_events = [] + + @on(CrewKickoffStarted) + def handle_crew_start(source, event): + received_events.append(event) + + # Create a simple crew + crew = Crew(agents=[base_agent], tasks=[base_task], name="TestCrew") + + # Run the crew + crew.kickoff() + + # Verify the event was emitted + assert len(received_events) == 1 + assert received_events[0].crew_name == "TestCrew" + assert isinstance(received_events[0].timestamp, datetime) + assert received_events[0].type == "crew_kickoff_started" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_emits_end_kickoff_event(): + # Setup event listener + received_events = [] + + @on(CrewKickoffCompleted) + def handle_crew_end(source, event): + received_events.append(event) + + # Create a simple crew + crew = Crew(agents=[base_agent], tasks=[base_task], name="TestCrew") + + # Run the crew + crew.kickoff() + + # Verify the event was emitted + assert len(received_events) == 1 + assert received_events[0].crew_name == "TestCrew" + assert isinstance(received_events[0].timestamp, datetime) + assert received_events[0].type == "crew_kickoff_completed" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_emits_start_task_event(): + # Setup event listener + received_events = [] + + @on(TaskStarted) + def handle_task_start(source, event): + received_events.append(event) + + # Create a simple crew + crew = Crew(agents=[base_agent], tasks=[base_task], name="TestCrew") + + # Run the crew + crew.kickoff() + + # Verify the event was emitted + assert len(received_events) == 1 + assert isinstance(received_events[0].timestamp, datetime) + assert received_events[0].type == "task_started" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_emits_end_task_event(): + # Setup event listener + received_events = [] + + @on(TaskCompleted) + def handle_task_end(source, event): + received_events.append(event) + + # Create a simple crew + crew = Crew(agents=[base_agent], tasks=[base_task], name="TestCrew") + + # Run the crew + crew.kickoff() + + # Verify the event was emitted + assert len(received_events) == 1 + assert isinstance(received_events[0].timestamp, datetime) + assert received_events[0].type == "task_completed" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_agent_emits_execution_error_event(): + # Setup event listener + received_events = [] + + @on(AgentExecutionError) + def handle_agent_error(source, event): + received_events.append(event) + + # Create an agent that will fail + failing_agent = Agent( + role="failing_agent", + goal="Fail execution", + backstory="You are an agent that will fail", + max_retry_limit=1, # Set low retry limit for testing + ) + + # Create a task that will trigger an error + failing_task = Task( + description="This will fail", agent=failing_agent, expected_output="hi" + ) + + error_message = "Forced error for testing" + # Mock the agent executor to raise an exception + with patch.object(failing_agent.agent_executor, "invoke") as mock_invoke: + mock_invoke.side_effect = Exception(error_message) + assert failing_agent._times_executed == 0 + assert failing_agent.max_retry_limit == 1 + + # Execute task which should fail and emit error + with pytest.raises(Exception) as e: + failing_agent.execute_task(failing_task) + + print("error message: ", e.value.args[0]) + + # assert e.value.args[0] == error_message + # assert failing_agent._times_executed == 2 # Initial attempt + 1 retry + + # Verify the invoke was called twice (initial + retry) + mock_invoke.assert_has_calls( + [ + mock.call( + { + "input": "This will fail\n\nThis is the expect criteria for your final answer: hi\nyou MUST return the actual complete content as the final answer, not a summary.", + "tool_names": "", + "tools": "", + "ask_for_human_input": False, + } + ), + mock.call( + { + "input": "This will fail\n\nThis is the expect criteria for your final answer: hi\nyou MUST return the actual complete content as the final answer, not a summary.", + "tool_names": "", + "tools": "", + "ask_for_human_input": False, + } + ), + ] + ) + print("made it here") + + # Verify the error event was emitted + assert len(received_events) == 1 + assert isinstance(received_events[0].timestamp, datetime) + assert received_events[0].type == "agent_execution_error" + assert received_events[0].agent == failing_agent + assert received_events[0].task == failing_task + assert error_message in received_events[0].error