diff --git a/src/crewai/cli/plus_api.py b/src/crewai/cli/plus_api.py
index 4a578ce89..26f0953f0 100644
--- a/src/crewai/cli/plus_api.py
+++ b/src/crewai/cli/plus_api.py
@@ -17,6 +17,7 @@ class PlusAPI:
ORGANIZATIONS_RESOURCE = "/crewai_plus/api/v1/me/organizations"
CREWS_RESOURCE = "/crewai_plus/api/v1/crews"
AGENTS_RESOURCE = "/crewai_plus/api/v1/agents"
+ TRACING_RESOURCE = "/crewai_plus/api/v1/tracing"
def __init__(self, api_key: str) -> None:
self.api_key = api_key
@@ -114,3 +115,25 @@ class PlusAPI:
def get_organizations(self) -> requests.Response:
return self._make_request("GET", self.ORGANIZATIONS_RESOURCE)
+
+ def send_trace_batch(self, payload) -> requests.Response:
+ return self._make_request("POST", self.TRACING_RESOURCE, json=payload)
+
+ def initialize_trace_batch(self, payload) -> requests.Response:
+ return self._make_request(
+ "POST", f"{self.TRACING_RESOURCE}/batches", json=payload
+ )
+
+ def send_trace_events(self, trace_batch_id: str, payload) -> requests.Response:
+ return self._make_request(
+ "POST",
+ f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}/events",
+ json=payload,
+ )
+
+ def finalize_trace_batch(self, trace_batch_id: str, payload) -> requests.Response:
+ return self._make_request(
+ "PATCH",
+ f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}/finalize",
+ json=payload,
+ )
diff --git a/src/crewai/crew.py b/src/crewai/crew.py
index 5f7d168ef..3cac711fa 100644
--- a/src/crewai/crew.py
+++ b/src/crewai/crew.py
@@ -1,3 +1,4 @@
+import os
import asyncio
import json
import re
@@ -72,6 +73,11 @@ from crewai.utilities.events.crew_events import (
)
from crewai.utilities.events.crewai_event_bus import crewai_event_bus
from crewai.utilities.events.event_listener import EventListener
+from crewai.utilities.events.listeners.tracing.trace_listener import (
+ TraceCollectionListener,
+)
+
+
from crewai.utilities.formatter import (
aggregate_raw_outputs_from_task_outputs,
aggregate_raw_outputs_from_tasks,
@@ -238,6 +244,10 @@ class Crew(FlowTrackable, BaseModel):
default_factory=SecurityConfig,
description="Security configuration for the crew, including fingerprinting.",
)
+ token_usage: Optional[UsageMetrics] = Field(
+ default=None,
+ description="Metrics for the LLM usage during all tasks execution.",
+ )
@field_validator("id", mode="before")
@classmethod
@@ -269,6 +279,9 @@ class Crew(FlowTrackable, BaseModel):
self._cache_handler = CacheHandler()
event_listener = EventListener()
+ if os.getenv("CREWAI_TRACING_ENABLED", "false").lower() == "true":
+ trace_listener = TraceCollectionListener()
+ trace_listener.setup_listeners(crewai_event_bus)
event_listener.verbose = self.verbose
event_listener.formatter.verbose = self.verbose
self._logger = Logger(verbose=self.verbose)
@@ -1044,11 +1057,13 @@ class Crew(FlowTrackable, BaseModel):
final_string_output = final_task_output.raw
self._finish_execution(final_string_output)
- token_usage = self.calculate_usage_metrics()
+ self.token_usage = self.calculate_usage_metrics()
crewai_event_bus.emit(
self,
CrewKickoffCompletedEvent(
- crew_name=self.name, output=final_task_output
+ crew_name=self.name,
+ output=final_task_output,
+ total_tokens=self.token_usage.total_tokens,
),
)
return CrewOutput(
@@ -1056,7 +1071,7 @@ class Crew(FlowTrackable, BaseModel):
pydantic=final_task_output.pydantic,
json_dict=final_task_output.json_dict,
tasks_output=task_outputs,
- token_usage=token_usage,
+ token_usage=self.token_usage,
)
def _process_async_tasks(
@@ -1226,7 +1241,6 @@ class Crew(FlowTrackable, BaseModel):
if self.external_memory:
copied_data["external_memory"] = self.external_memory.model_copy(deep=True)
-
copied_data.pop("agents", None)
copied_data.pop("tasks", None)
diff --git a/src/crewai/flow/flow.py b/src/crewai/flow/flow.py
index 9bd9e3b6a..9d75dfe5a 100644
--- a/src/crewai/flow/flow.py
+++ b/src/crewai/flow/flow.py
@@ -2,6 +2,7 @@ import asyncio
import copy
import inspect
import logging
+import os
from typing import (
Any,
Callable,
@@ -32,6 +33,9 @@ from crewai.utilities.events.flow_events import (
MethodExecutionFinishedEvent,
MethodExecutionStartedEvent,
)
+from crewai.utilities.events.listeners.tracing.trace_listener import (
+ TraceCollectionListener,
+)
from crewai.utilities.printer import Printer
logger = logging.getLogger(__name__)
@@ -465,7 +469,9 @@ class Flow(Generic[T], metaclass=FlowMeta):
# Initialize state with initial values
self._state = self._create_initial_state()
-
+ if os.getenv("CREWAI_TRACING_ENABLED", "false").lower() == "true":
+ trace_listener = TraceCollectionListener()
+ trace_listener.setup_listeners(crewai_event_bus)
# Apply any additional kwargs
if kwargs:
self._initialize_state(kwargs)
diff --git a/src/crewai/lite_agent.py b/src/crewai/lite_agent.py
index 0e99fb563..2bd85fbe9 100644
--- a/src/crewai/lite_agent.py
+++ b/src/crewai/lite_agent.py
@@ -28,7 +28,7 @@ from pydantic import (
InstanceOf,
PrivateAttr,
model_validator,
- field_validator
+ field_validator,
)
from crewai.agents.agent_builder.base_agent import BaseAgent
@@ -210,7 +210,9 @@ class LiteAgent(FlowTrackable, BaseModel):
"""Set up the LLM and other components after initialization."""
self.llm = create_llm(self.llm)
if not isinstance(self.llm, BaseLLM):
- raise ValueError(f"Expected LLM instance of type BaseLLM, got {type(self.llm).__name__}")
+ raise ValueError(
+ f"Expected LLM instance of type BaseLLM, got {type(self.llm).__name__}"
+ )
# Initialize callbacks
token_callback = TokenCalcHandler(token_cost_process=self._token_process)
@@ -233,7 +235,9 @@ class LiteAgent(FlowTrackable, BaseModel):
from crewai.tasks.llm_guardrail import LLMGuardrail
if not isinstance(self.llm, BaseLLM):
- raise TypeError(f"Guardrail requires LLM instance of type BaseLLM, got {type(self.llm).__name__}")
+ raise TypeError(
+ f"Guardrail requires LLM instance of type BaseLLM, got {type(self.llm).__name__}"
+ )
self._guardrail = LLMGuardrail(description=self.guardrail, llm=self.llm)
@@ -515,7 +519,8 @@ class LiteAgent(FlowTrackable, BaseModel):
enforce_rpm_limit(self.request_within_rpm_limit)
- # Emit LLM call started event
+ llm = cast(LLM, self.llm)
+ model = llm.model if hasattr(llm, "model") else "unknown"
crewai_event_bus.emit(
self,
event=LLMCallStartedEvent(
@@ -523,6 +528,7 @@ class LiteAgent(FlowTrackable, BaseModel):
tools=None,
callbacks=self._callbacks,
from_agent=self,
+ model=model,
),
)
@@ -543,6 +549,7 @@ class LiteAgent(FlowTrackable, BaseModel):
response=answer,
call_type=LLMCallType.LLM_CALL,
from_agent=self,
+ model=model,
),
)
except Exception as e:
diff --git a/src/crewai/llm.py b/src/crewai/llm.py
index 9296c26de..40c026684 100644
--- a/src/crewai/llm.py
+++ b/src/crewai/llm.py
@@ -61,6 +61,7 @@ load_dotenv()
litellm.suppress_debug_info = True
+
class FilteredStream(io.TextIOBase):
_lock = None
@@ -78,7 +79,8 @@ class FilteredStream(io.TextIOBase):
# Skip common noisy LiteLLM banners and any other lines that contain "litellm"
if (
"litellm.info:" in lower_s
- or "Consider using a smaller input or implementing a text splitting strategy" in lower_s
+ or "Consider using a smaller input or implementing a text splitting strategy"
+ in lower_s
):
return 0
@@ -286,6 +288,8 @@ class AccumulatedToolArgs(BaseModel):
class LLM(BaseLLM):
+ completion_cost: Optional[float] = None
+
def __init__(
self,
model: str,
@@ -532,7 +536,11 @@ class LLM(BaseLLM):
assert hasattr(crewai_event_bus, "emit")
crewai_event_bus.emit(
self,
- event=LLMStreamChunkEvent(chunk=chunk_content, from_task=from_task, from_agent=from_agent),
+ event=LLMStreamChunkEvent(
+ chunk=chunk_content,
+ from_task=from_task,
+ from_agent=from_agent,
+ ),
)
# --- 4) Fallback to non-streaming if no content received
if not full_response.strip() and chunk_count == 0:
@@ -545,7 +553,11 @@ class LLM(BaseLLM):
"stream_options", None
) # Remove stream_options for non-streaming call
return self._handle_non_streaming_response(
- non_streaming_params, callbacks, available_functions, from_task, from_agent
+ non_streaming_params,
+ callbacks,
+ available_functions,
+ from_task,
+ from_agent,
)
# --- 5) Handle empty response with chunks
@@ -630,7 +642,13 @@ class LLM(BaseLLM):
# Log token usage if available in streaming mode
self._handle_streaming_callbacks(callbacks, usage_info, last_chunk)
# Emit completion event and return response
- self._handle_emit_call_events(response=full_response, call_type=LLMCallType.LLM_CALL, from_task=from_task, from_agent=from_agent, messages=params["messages"])
+ self._handle_emit_call_events(
+ response=full_response,
+ call_type=LLMCallType.LLM_CALL,
+ from_task=from_task,
+ from_agent=from_agent,
+ messages=params["messages"],
+ )
return full_response
# --- 9) Handle tool calls if present
@@ -642,7 +660,13 @@ class LLM(BaseLLM):
self._handle_streaming_callbacks(callbacks, usage_info, last_chunk)
# --- 11) Emit completion event and return response
- self._handle_emit_call_events(response=full_response, call_type=LLMCallType.LLM_CALL, from_task=from_task, from_agent=from_agent, messages=params["messages"])
+ self._handle_emit_call_events(
+ response=full_response,
+ call_type=LLMCallType.LLM_CALL,
+ from_task=from_task,
+ from_agent=from_agent,
+ messages=params["messages"],
+ )
return full_response
except ContextWindowExceededError as e:
@@ -654,14 +678,22 @@ class LLM(BaseLLM):
logging.error(f"Error in streaming response: {str(e)}")
if full_response.strip():
logging.warning(f"Returning partial response despite error: {str(e)}")
- self._handle_emit_call_events(response=full_response, call_type=LLMCallType.LLM_CALL, from_task=from_task, from_agent=from_agent, messages=params["messages"])
+ self._handle_emit_call_events(
+ response=full_response,
+ call_type=LLMCallType.LLM_CALL,
+ from_task=from_task,
+ from_agent=from_agent,
+ messages=params["messages"],
+ )
return full_response
# Emit failed event and re-raise the exception
assert hasattr(crewai_event_bus, "emit")
crewai_event_bus.emit(
self,
- event=LLMCallFailedEvent(error=str(e), from_task=from_task, from_agent=from_agent),
+ event=LLMCallFailedEvent(
+ error=str(e), from_task=from_task, from_agent=from_agent
+ ),
)
raise Exception(f"Failed to get streaming response: {str(e)}")
@@ -779,6 +811,7 @@ class LLM(BaseLLM):
# across the codebase. This allows CrewAgentExecutor to handle context
# length issues appropriately.
response = litellm.completion(**params)
+
except ContextWindowExceededError as e:
# Convert litellm's context window error to our own exception type
# for consistent handling in the rest of the codebase
@@ -805,7 +838,13 @@ class LLM(BaseLLM):
# --- 5) If no tool calls or no available functions, return the text response directly as long as there is a text response
if (not tool_calls or not available_functions) and text_response:
- self._handle_emit_call_events(response=text_response, call_type=LLMCallType.LLM_CALL, from_task=from_task, from_agent=from_agent, messages=params["messages"])
+ self._handle_emit_call_events(
+ response=text_response,
+ call_type=LLMCallType.LLM_CALL,
+ from_task=from_task,
+ from_agent=from_agent,
+ messages=params["messages"],
+ )
return text_response
# --- 6) If there is no text response, no available functions, but there are tool calls, return the tool calls
elif tool_calls and not available_functions and not text_response:
@@ -816,7 +855,13 @@ class LLM(BaseLLM):
if tool_result is not None:
return tool_result
# --- 8) If tool call handling didn't return a result, emit completion event and return text response
- self._handle_emit_call_events(response=text_response, call_type=LLMCallType.LLM_CALL, from_task=from_task, from_agent=from_agent, messages=params["messages"])
+ self._handle_emit_call_events(
+ response=text_response,
+ call_type=LLMCallType.LLM_CALL,
+ from_task=from_task,
+ from_agent=from_agent,
+ messages=params["messages"],
+ )
return text_response
def _handle_tool_call(
@@ -873,7 +918,9 @@ class LLM(BaseLLM):
)
# --- 3.3) Emit success event
- self._handle_emit_call_events(response=result, call_type=LLMCallType.TOOL_CALL)
+ self._handle_emit_call_events(
+ response=result, call_type=LLMCallType.TOOL_CALL
+ )
return result
except Exception as e:
# --- 3.4) Handle execution errors
@@ -891,7 +938,7 @@ class LLM(BaseLLM):
event=ToolUsageErrorEvent(
tool_name=function_name,
tool_args=function_args,
- error=f"Tool execution error: {str(e)}"
+ error=f"Tool execution error: {str(e)}",
),
)
return None
@@ -941,6 +988,7 @@ class LLM(BaseLLM):
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
+ model=self.model,
),
)
@@ -978,17 +1026,22 @@ class LLM(BaseLLM):
# whether to summarize the content or abort based on the respect_context_window flag
raise
except Exception as e:
- unsupported_stop = "Unsupported parameter" in str(e) and "'stop'" in str(e)
+ unsupported_stop = "Unsupported parameter" in str(
+ e
+ ) and "'stop'" in str(e)
if unsupported_stop:
- if "additional_drop_params" in self.additional_params and isinstance(self.additional_params["additional_drop_params"], list):
+ if (
+ "additional_drop_params" in self.additional_params
+ and isinstance(
+ self.additional_params["additional_drop_params"], list
+ )
+ ):
self.additional_params["additional_drop_params"].append("stop")
else:
self.additional_params = {"additional_drop_params": ["stop"]}
- logging.info(
- "Retrying LLM call without the unsupported 'stop'"
- )
+ logging.info("Retrying LLM call without the unsupported 'stop'")
return self.call(
messages,
@@ -1002,11 +1055,20 @@ class LLM(BaseLLM):
assert hasattr(crewai_event_bus, "emit")
crewai_event_bus.emit(
self,
- event=LLMCallFailedEvent(error=str(e), from_task=from_task, from_agent=from_agent),
+ event=LLMCallFailedEvent(
+ error=str(e), from_task=from_task, from_agent=from_agent
+ ),
)
raise
- def _handle_emit_call_events(self, response: Any, call_type: LLMCallType, from_task: Optional[Any] = None, from_agent: Optional[Any] = None, messages: str | list[dict[str, Any]] | None = None):
+ def _handle_emit_call_events(
+ self,
+ response: Any,
+ call_type: LLMCallType,
+ from_task: Optional[Any] = None,
+ from_agent: Optional[Any] = None,
+ messages: str | list[dict[str, Any]] | None = None,
+ ):
"""Handle the events for the LLM call.
Args:
@@ -1019,7 +1081,14 @@ class LLM(BaseLLM):
assert hasattr(crewai_event_bus, "emit")
crewai_event_bus.emit(
self,
- event=LLMCallCompletedEvent(messages=messages, response=response, call_type=call_type, from_task=from_task, from_agent=from_agent),
+ event=LLMCallCompletedEvent(
+ messages=messages,
+ response=response,
+ call_type=call_type,
+ from_task=from_task,
+ from_agent=from_agent,
+ model=self.model,
+ ),
)
def _format_messages_for_provider(
@@ -1074,11 +1143,13 @@ class LLM(BaseLLM):
# TODO: Remove this code after merging PR https://github.com/BerriAI/litellm/pull/10917
# Ollama doesn't supports last message to be 'assistant'
- if "ollama" in self.model.lower() and messages and messages[-1]["role"] == "assistant":
+ if (
+ "ollama" in self.model.lower()
+ and messages
+ and messages[-1]["role"] == "assistant"
+ ):
messages = messages.copy()
- messages.append(
- {"role": "user", "content": ""}
- )
+ messages.append({"role": "user", "content": ""})
return messages
# Handle Anthropic models
diff --git a/src/crewai/utilities/constants.py b/src/crewai/utilities/constants.py
index 4dbace270..ef66ff87a 100644
--- a/src/crewai/utilities/constants.py
+++ b/src/crewai/utilities/constants.py
@@ -16,3 +16,4 @@ class _NotSpecified:
# Unlike `None`, which might be a valid value from the user, `NOT_SPECIFIED` allows
# us to distinguish between "not passed at all" and "explicitly passed None" or "[]".
NOT_SPECIFIED = _NotSpecified()
+CREWAI_BASE_URL = "https://app.crewai.com/"
diff --git a/src/crewai/utilities/events/crew_events.py b/src/crewai/utilities/events/crew_events.py
index 103f3ecd3..f500ac9cc 100644
--- a/src/crewai/utilities/events/crew_events.py
+++ b/src/crewai/utilities/events/crew_events.py
@@ -47,6 +47,7 @@ class CrewKickoffCompletedEvent(CrewBaseEvent):
output: Any
type: str = "crew_kickoff_completed"
+ total_tokens: int = 0
class CrewKickoffFailedEvent(CrewBaseEvent):
diff --git a/src/crewai/utilities/events/listeners/tracing/interfaces.py b/src/crewai/utilities/events/listeners/tracing/interfaces.py
new file mode 100644
index 000000000..4516be7f8
--- /dev/null
+++ b/src/crewai/utilities/events/listeners/tracing/interfaces.py
@@ -0,0 +1,33 @@
+import json
+from datetime import datetime
+
+from crewai.cli.plus_api import PlusAPI
+from crewai.cli.authentication.token import get_auth_token
+from pydantic import BaseModel
+from .trace_batch_manager import TraceBatch
+from logging import getLogger
+
+logger = getLogger(__name__)
+
+
+class TraceSender(BaseModel):
+ """Trace sender for sending trace batches to the backend"""
+
+ def send_batch(self, batch: TraceBatch) -> bool:
+ """Print trace batch to console"""
+ try:
+ payload = batch.to_dict()
+
+ def datetime_handler(obj):
+ if isinstance(obj, datetime):
+ return obj.isoformat()
+
+ serialized_payload = json.loads(
+ json.dumps(payload, default=datetime_handler)
+ )
+
+ PlusAPI(api_key=get_auth_token()).send_trace_batch(serialized_payload)
+ return True
+ except Exception as e:
+ logger.error(f"Error sending trace batch: {e}")
+ return False
diff --git a/src/crewai/utilities/events/listeners/tracing/trace_batch_manager.py b/src/crewai/utilities/events/listeners/tracing/trace_batch_manager.py
new file mode 100644
index 000000000..d014c65d2
--- /dev/null
+++ b/src/crewai/utilities/events/listeners/tracing/trace_batch_manager.py
@@ -0,0 +1,252 @@
+import uuid
+from datetime import datetime, timezone
+from typing import Dict, List, Any, Optional
+from dataclasses import dataclass, field
+
+from crewai.utilities.constants import CREWAI_BASE_URL
+from crewai.cli.authentication.token import get_auth_token
+
+from crewai.cli.version import get_crewai_version
+from crewai.cli.plus_api import PlusAPI
+from rich.console import Console
+from rich.panel import Panel
+
+from crewai.utilities.events.listeners.tracing.types import TraceEvent
+from logging import getLogger
+
+logger = getLogger(__name__)
+
+
+@dataclass
+class TraceBatch:
+ """Batch of events to send to backend"""
+
+ version: str = field(default_factory=get_crewai_version)
+ batch_id: str = field(default_factory=lambda: str(uuid.uuid4()))
+ user_context: Dict[str, str] = field(default_factory=dict)
+ execution_metadata: Dict[str, Any] = field(default_factory=dict)
+ events: List[TraceEvent] = field(default_factory=list)
+
+ def to_dict(self) -> Dict[str, Any]:
+ return {
+ "version": self.version,
+ "batch_id": self.batch_id,
+ "user_context": self.user_context,
+ "execution_metadata": self.execution_metadata,
+ "events": [event.to_dict() for event in self.events],
+ }
+
+
+class TraceBatchManager:
+ """Single responsibility: Manage batches and event buffering"""
+
+ def __init__(self):
+ self.plus_api = PlusAPI(api_key=get_auth_token())
+ self.trace_batch_id: Optional[str] = None # Backend ID
+ self.current_batch: Optional[TraceBatch] = None
+ self.event_buffer: List[TraceEvent] = []
+ self.execution_start_times: Dict[str, datetime] = {}
+
+ def initialize_batch(
+ self, user_context: Dict[str, str], execution_metadata: Dict[str, Any]
+ ) -> TraceBatch:
+ """Initialize a new trace batch"""
+ self.current_batch = TraceBatch(
+ user_context=user_context, execution_metadata=execution_metadata
+ )
+ self.event_buffer.clear()
+
+ self.record_start_time("execution")
+
+ self._initialize_backend_batch(user_context, execution_metadata)
+
+ return self.current_batch
+
+ def _initialize_backend_batch(
+ self, user_context: Dict[str, str], execution_metadata: Dict[str, Any]
+ ):
+ """Send batch initialization to backend"""
+ if not self.plus_api or not self.current_batch:
+ return
+
+ try:
+ payload = {
+ "trace_id": self.current_batch.batch_id,
+ "execution_type": execution_metadata.get("execution_type", "crew"),
+ "execution_context": {
+ "crew_fingerprint": execution_metadata.get("crew_fingerprint"),
+ "crew_name": execution_metadata.get("crew_name", "Unknown Crew"),
+ "flow_name": execution_metadata.get("flow_name", "Unknown Flow"),
+ "crewai_version": self.current_batch.version,
+ "privacy_level": user_context.get("privacy_level", "standard"),
+ },
+ "execution_metadata": {
+ "expected_duration_estimate": execution_metadata.get(
+ "expected_duration_estimate", 300
+ ),
+ "agent_count": execution_metadata.get("agent_count", 0),
+ "task_count": execution_metadata.get("task_count", 0),
+ "flow_method_count": execution_metadata.get("flow_method_count", 0),
+ "execution_started_at": datetime.now(timezone.utc).isoformat(),
+ },
+ }
+
+ response = self.plus_api.initialize_trace_batch(payload)
+
+ if response.status_code == 201 or response.status_code == 200:
+ response_data = response.json()
+ self.trace_batch_id = response_data["trace_id"]
+ console = Console()
+ panel = Panel(
+ f"✅ Trace batch initialized with session ID: {self.trace_batch_id}",
+ title="Trace Batch Initialization",
+ border_style="green",
+ )
+ console.print(panel)
+ else:
+ logger.error(
+ f"❌ Failed to initialize trace batch: {response.status_code} - {response.text}"
+ )
+
+ except Exception as e:
+ logger.error(f"❌ Error initializing trace batch: {str(e)}")
+
+ def add_event(self, trace_event: TraceEvent):
+ """Add event to buffer"""
+ self.event_buffer.append(trace_event)
+
+ def _send_events_to_backend(self):
+ """Send buffered events to backend"""
+ if not self.plus_api or not self.trace_batch_id or not self.event_buffer:
+ return
+
+ try:
+ payload = {
+ "events": [event.to_dict() for event in self.event_buffer],
+ "batch_metadata": {
+ "events_count": len(self.event_buffer),
+ "batch_sequence": 1,
+ "is_final_batch": False,
+ },
+ }
+
+ if not self.trace_batch_id:
+ raise Exception("❌ Trace batch ID not found")
+
+ response = self.plus_api.send_trace_events(self.trace_batch_id, payload)
+
+ if response.status_code == 200 or response.status_code == 201:
+ self.event_buffer.clear()
+ else:
+ logger.error(
+ f"❌ Failed to send events: {response.status_code} - {response.text}"
+ )
+
+ except Exception as e:
+ logger.error(f"❌ Error sending events to backend: {str(e)}")
+
+ def finalize_batch(self) -> Optional[TraceBatch]:
+ """Finalize batch and return it for sending"""
+ if not self.current_batch:
+ return None
+
+ if self.event_buffer:
+ self._send_events_to_backend()
+
+ self._finalize_backend_batch()
+
+ self.current_batch.events = self.event_buffer.copy()
+
+ finalized_batch = self.current_batch
+
+ self.current_batch = None
+ self.event_buffer.clear()
+ self.trace_batch_id = None
+
+ self._cleanup_batch_data()
+
+ return finalized_batch
+
+ def _finalize_backend_batch(self):
+ """Send batch finalization to backend"""
+ if not self.plus_api or not self.trace_batch_id:
+ return
+
+ try:
+ total_events = len(self.current_batch.events) if self.current_batch else 0
+
+ payload = {
+ "status": "completed",
+ "duration_ms": self.calculate_duration("execution"),
+ "final_event_count": total_events,
+ }
+
+ response = self.plus_api.finalize_trace_batch(self.trace_batch_id, payload)
+
+ if response.status_code == 200:
+ console = Console()
+ panel = Panel(
+ f"✅ Trace batch finalized with session ID: {self.trace_batch_id}. View here: {CREWAI_BASE_URL}/crewai_plus/trace_batches/{self.trace_batch_id}",
+ title="Trace Batch Finalization",
+ border_style="green",
+ )
+ console.print(panel)
+
+ else:
+ logger.error(
+ f"❌ Failed to finalize trace batch: {response.status_code} - {response.text}"
+ )
+
+ except Exception as e:
+ logger.error(f"❌ Error finalizing trace batch: {str(e)}")
+ # TODO: send error to app
+
+ def _cleanup_batch_data(self):
+ """Clean up batch data after successful finalization to free memory"""
+ try:
+ if hasattr(self, "event_buffer") and self.event_buffer:
+ self.event_buffer.clear()
+
+ if hasattr(self, "current_batch") and self.current_batch:
+ if hasattr(self.current_batch, "events") and self.current_batch.events:
+ self.current_batch.events.clear()
+ self.current_batch = None
+
+ if hasattr(self, "batch_sequence"):
+ self.batch_sequence = 0
+
+ except Exception as e:
+ logger.error(f"Warning: Error during cleanup: {str(e)}")
+
+ def has_events(self) -> bool:
+ """Check if there are events in the buffer"""
+ return len(self.event_buffer) > 0
+
+ def get_event_count(self) -> int:
+ """Get number of events in buffer"""
+ return len(self.event_buffer)
+
+ def is_batch_initialized(self) -> bool:
+ """Check if batch is initialized"""
+ return self.current_batch is not None
+
+ def record_start_time(self, key: str):
+ """Record start time for duration calculation"""
+ self.execution_start_times[key] = datetime.now(timezone.utc)
+
+ def calculate_duration(self, key: str) -> int:
+ """Calculate duration in milliseconds from recorded start time"""
+ start_time = self.execution_start_times.get(key)
+ if start_time:
+ duration_ms = int(
+ (datetime.now(timezone.utc) - start_time).total_seconds() * 1000
+ )
+ del self.execution_start_times[key]
+ return duration_ms
+ return 0
+
+ def get_trace_id(self) -> Optional[str]:
+ """Get current trace ID"""
+ if self.current_batch:
+ return self.current_batch.user_context.get("trace_id")
+ return None
diff --git a/src/crewai/utilities/events/listeners/tracing/trace_listener.py b/src/crewai/utilities/events/listeners/tracing/trace_listener.py
new file mode 100644
index 000000000..de262ff5b
--- /dev/null
+++ b/src/crewai/utilities/events/listeners/tracing/trace_listener.py
@@ -0,0 +1,414 @@
+import os
+import uuid
+
+from typing import Dict, Any, Optional
+
+from crewai.utilities.events.base_event_listener import BaseEventListener
+from crewai.utilities.events.agent_events import (
+ AgentExecutionCompletedEvent,
+ AgentExecutionStartedEvent,
+ LiteAgentExecutionStartedEvent,
+ LiteAgentExecutionCompletedEvent,
+ LiteAgentExecutionErrorEvent,
+ AgentExecutionErrorEvent,
+)
+from crewai.utilities.events.listeners.tracing.types import TraceEvent
+from crewai.utilities.events.reasoning_events import (
+ AgentReasoningStartedEvent,
+ AgentReasoningCompletedEvent,
+ AgentReasoningFailedEvent,
+)
+from crewai.utilities.events.crew_events import (
+ CrewKickoffCompletedEvent,
+ CrewKickoffFailedEvent,
+ CrewKickoffStartedEvent,
+)
+from crewai.utilities.events.task_events import (
+ TaskCompletedEvent,
+ TaskFailedEvent,
+ TaskStartedEvent,
+)
+from crewai.utilities.events.tool_usage_events import (
+ ToolUsageErrorEvent,
+ ToolUsageFinishedEvent,
+ ToolUsageStartedEvent,
+)
+from crewai.utilities.events.llm_events import (
+ LLMCallCompletedEvent,
+ LLMCallFailedEvent,
+ LLMCallStartedEvent,
+)
+
+from crewai.utilities.events.flow_events import (
+ FlowCreatedEvent,
+ FlowStartedEvent,
+ FlowFinishedEvent,
+ MethodExecutionStartedEvent,
+ MethodExecutionFinishedEvent,
+ MethodExecutionFailedEvent,
+ FlowPlotEvent,
+)
+from crewai.utilities.events.llm_guardrail_events import (
+ LLMGuardrailStartedEvent,
+ LLMGuardrailCompletedEvent,
+)
+from crewai.utilities.serialization import to_serializable
+
+
+from .trace_batch_manager import TraceBatchManager
+
+from crewai.utilities.events.memory_events import (
+ MemoryQueryStartedEvent,
+ MemoryQueryCompletedEvent,
+ MemoryQueryFailedEvent,
+ MemorySaveStartedEvent,
+ MemorySaveCompletedEvent,
+ MemorySaveFailedEvent,
+)
+from .interfaces import TraceSender
+from crewai.cli.authentication.token import get_auth_token
+from crewai.cli.version import get_crewai_version
+
+
+class TraceCollectionListener(BaseEventListener):
+ """
+ Trace collection listener that orchestrates trace collection
+ """
+
+ trace_enabled: bool = False
+ complex_events = ["task_started", "llm_call_started", "llm_call_completed"]
+
+ _instance = None
+ _initialized = False
+
+ def __new__(cls, batch_manager=None, trace_sender=None):
+ if cls._instance is None:
+ cls._instance = super().__new__(cls)
+ return cls._instance
+
+ def __init__(
+ self,
+ batch_manager: Optional[TraceBatchManager] = None,
+ trace_sender: Optional[TraceSender] = None,
+ ):
+ if self._initialized:
+ return
+
+ super().__init__()
+ self.batch_manager = batch_manager or TraceBatchManager()
+ self.trace_sender = trace_sender or TraceSender()
+ self.trace_enabled = self._check_trace_enabled()
+ self._initialized = True
+
+ def _check_trace_enabled(self) -> bool:
+ """Check if tracing should be enabled"""
+ auth_token = get_auth_token()
+ if not auth_token:
+ return False
+
+ return os.getenv("CREWAI_TRACING_ENABLED", "false").lower() == "true" or bool(
+ os.getenv("CREWAI_USER_TOKEN")
+ )
+
+ def _get_user_context(self) -> Dict[str, str]:
+ """Extract user context for tracing"""
+ return {
+ "user_id": os.getenv("CREWAI_USER_ID", "anonymous"),
+ "organization_id": os.getenv("CREWAI_ORG_ID", ""),
+ "session_id": str(uuid.uuid4()),
+ "trace_id": str(uuid.uuid4()),
+ }
+
+ def setup_listeners(self, crewai_event_bus):
+ """Setup event listeners - delegates to specific handlers"""
+ if not self.trace_enabled:
+ return
+
+ self._register_flow_event_handlers(crewai_event_bus)
+ self._register_context_event_handlers(crewai_event_bus)
+ self._register_action_event_handlers(crewai_event_bus)
+
+ def _register_flow_event_handlers(self, event_bus):
+ """Register handlers for flow events"""
+
+ @event_bus.on(FlowCreatedEvent)
+ def on_flow_created(source, event):
+ pass
+
+ @event_bus.on(FlowStartedEvent)
+ def on_flow_started(source, event):
+ if not self.batch_manager.is_batch_initialized():
+ self._initialize_flow_batch(source, event)
+ self._handle_trace_event("flow_started", source, event)
+
+ @event_bus.on(MethodExecutionStartedEvent)
+ def on_method_started(source, event):
+ self._handle_trace_event("method_execution_started", source, event)
+
+ @event_bus.on(MethodExecutionFinishedEvent)
+ def on_method_finished(source, event):
+ self._handle_trace_event("method_execution_finished", source, event)
+
+ @event_bus.on(MethodExecutionFailedEvent)
+ def on_method_failed(source, event):
+ self._handle_trace_event("method_execution_failed", source, event)
+
+ @event_bus.on(FlowFinishedEvent)
+ def on_flow_finished(source, event):
+ self._handle_trace_event("flow_finished", source, event)
+ self._send_batch()
+
+ @event_bus.on(FlowPlotEvent)
+ def on_flow_plot(source, event):
+ self._handle_action_event("flow_plot", source, event)
+
+ def _register_context_event_handlers(self, event_bus):
+ """Register handlers for context events (start/end)"""
+
+ @event_bus.on(CrewKickoffStartedEvent)
+ def on_crew_started(source, event):
+ if not self.batch_manager.is_batch_initialized():
+ self._initialize_batch(source, event)
+ self._handle_trace_event("crew_kickoff_started", source, event)
+
+ @event_bus.on(CrewKickoffCompletedEvent)
+ def on_crew_completed(source, event):
+ self._handle_trace_event("crew_kickoff_completed", source, event)
+ self._send_batch()
+
+ @event_bus.on(CrewKickoffFailedEvent)
+ def on_crew_failed(source, event):
+ self._handle_trace_event("crew_kickoff_failed", source, event)
+ self._send_batch()
+
+ @event_bus.on(TaskStartedEvent)
+ def on_task_started(source, event):
+ self._handle_trace_event("task_started", source, event)
+
+ @event_bus.on(TaskCompletedEvent)
+ def on_task_completed(source, event):
+ self._handle_trace_event("task_completed", source, event)
+
+ @event_bus.on(TaskFailedEvent)
+ def on_task_failed(source, event):
+ self._handle_trace_event("task_failed", source, event)
+
+ @event_bus.on(AgentExecutionStartedEvent)
+ def on_agent_started(source, event):
+ self._handle_trace_event("agent_execution_started", source, event)
+
+ @event_bus.on(AgentExecutionCompletedEvent)
+ def on_agent_completed(source, event):
+ self._handle_trace_event("agent_execution_completed", source, event)
+
+ @event_bus.on(LiteAgentExecutionStartedEvent)
+ def on_lite_agent_started(source, event):
+ self._handle_trace_event("lite_agent_execution_started", source, event)
+
+ @event_bus.on(LiteAgentExecutionCompletedEvent)
+ def on_lite_agent_completed(source, event):
+ self._handle_trace_event("lite_agent_execution_completed", source, event)
+
+ @event_bus.on(LiteAgentExecutionErrorEvent)
+ def on_lite_agent_error(source, event):
+ self._handle_trace_event("lite_agent_execution_error", source, event)
+
+ @event_bus.on(AgentExecutionErrorEvent)
+ def on_agent_error(source, event):
+ self._handle_trace_event("agent_execution_error", source, event)
+
+ @event_bus.on(LLMGuardrailStartedEvent)
+ def on_guardrail_started(source, event):
+ self._handle_trace_event("llm_guardrail_started", source, event)
+
+ @event_bus.on(LLMGuardrailCompletedEvent)
+ def on_guardrail_completed(source, event):
+ self._handle_trace_event("llm_guardrail_completed", source, event)
+
+ def _register_action_event_handlers(self, event_bus):
+ """Register handlers for action events (LLM calls, tool usage, memory)"""
+
+ @event_bus.on(LLMCallStartedEvent)
+ def on_llm_call_started(source, event):
+ self._handle_action_event("llm_call_started", source, event)
+
+ @event_bus.on(LLMCallCompletedEvent)
+ def on_llm_call_completed(source, event):
+ self._handle_action_event("llm_call_completed", source, event)
+
+ @event_bus.on(LLMCallFailedEvent)
+ def on_llm_call_failed(source, event):
+ self._handle_action_event("llm_call_failed", source, event)
+
+ @event_bus.on(ToolUsageStartedEvent)
+ def on_tool_started(source, event):
+ self._handle_action_event("tool_usage_started", source, event)
+
+ @event_bus.on(ToolUsageFinishedEvent)
+ def on_tool_finished(source, event):
+ self._handle_action_event("tool_usage_finished", source, event)
+
+ @event_bus.on(ToolUsageErrorEvent)
+ def on_tool_error(source, event):
+ self._handle_action_event("tool_usage_error", source, event)
+
+ @event_bus.on(MemoryQueryStartedEvent)
+ def on_memory_query_started(source, event):
+ self._handle_action_event("memory_query_started", source, event)
+
+ @event_bus.on(MemoryQueryCompletedEvent)
+ def on_memory_query_completed(source, event):
+ self._handle_action_event("memory_query_completed", source, event)
+
+ @event_bus.on(MemoryQueryFailedEvent)
+ def on_memory_query_failed(source, event):
+ self._handle_action_event("memory_query_failed", source, event)
+
+ @event_bus.on(MemorySaveStartedEvent)
+ def on_memory_save_started(source, event):
+ self._handle_action_event("memory_save_started", source, event)
+
+ @event_bus.on(MemorySaveCompletedEvent)
+ def on_memory_save_completed(source, event):
+ self._handle_action_event("memory_save_completed", source, event)
+
+ @event_bus.on(MemorySaveFailedEvent)
+ def on_memory_save_failed(source, event):
+ self._handle_action_event("memory_save_failed", source, event)
+
+ @event_bus.on(AgentReasoningStartedEvent)
+ def on_agent_reasoning_started(source, event):
+ self._handle_action_event("agent_reasoning_started", source, event)
+
+ @event_bus.on(AgentReasoningCompletedEvent)
+ def on_agent_reasoning_completed(source, event):
+ self._handle_action_event("agent_reasoning_completed", source, event)
+
+ @event_bus.on(AgentReasoningFailedEvent)
+ def on_agent_reasoning_failed(source, event):
+ self._handle_action_event("agent_reasoning_failed", source, event)
+
+ def _initialize_batch(self, source: Any, event: Any):
+ """Initialize trace batch"""
+ user_context = self._get_user_context()
+ execution_metadata = {
+ "crew_name": getattr(event, "crew_name", "Unknown Crew"),
+ "execution_start": event.timestamp if hasattr(event, "timestamp") else None,
+ "crewai_version": get_crewai_version(),
+ }
+
+ self.batch_manager.initialize_batch(user_context, execution_metadata)
+
+ def _initialize_flow_batch(self, source: Any, event: Any):
+ """Initialize trace batch for Flow execution"""
+ user_context = self._get_user_context()
+ execution_metadata = {
+ "flow_name": getattr(source, "__class__.__name__", "Unknown Flow"),
+ "execution_start": event.timestamp if hasattr(event, "timestamp") else None,
+ "crewai_version": get_crewai_version(),
+ "execution_type": "flow",
+ }
+
+ self.batch_manager.initialize_batch(user_context, execution_metadata)
+
+ def _handle_trace_event(self, event_type: str, source: Any, event: Any):
+ """Generic handler for context end events"""
+
+ trace_event = self._create_trace_event(event_type, source, event)
+
+ self.batch_manager.add_event(trace_event)
+
+ def _handle_action_event(self, event_type: str, source: Any, event: Any):
+ """Generic handler for action events (LLM calls, tool usage)"""
+
+ if not self.batch_manager.is_batch_initialized():
+ user_context = self._get_user_context()
+ execution_metadata = {
+ "crew_name": getattr(source, "name", "Unknown Crew"),
+ "crewai_version": get_crewai_version(),
+ }
+ self.batch_manager.initialize_batch(user_context, execution_metadata)
+
+ trace_event = self._create_trace_event(event_type, source, event)
+ self.batch_manager.add_event(trace_event)
+
+ def _send_batch(self):
+ """Send finalized batch using the configured sender"""
+ batch = self.batch_manager.finalize_batch()
+ if batch:
+ success = self.trace_sender.send_batch(batch)
+ if not success:
+ print("⚠️ Failed to send trace batch")
+
+ def _create_trace_event(
+ self, event_type: str, source: Any, event: Any
+ ) -> TraceEvent:
+ """Create a trace event"""
+ trace_event = TraceEvent(
+ type=event_type,
+ )
+
+ trace_event.event_data = self._build_event_data(event_type, event, source)
+ return trace_event
+
+ def _build_event_data(
+ self, event_type: str, event: Any, source: Any
+ ) -> Dict[str, Any]:
+ """Build event data"""
+ if event_type not in self.complex_events:
+ return self._safe_serialize_to_dict(event)
+ elif event_type == "task_started":
+ return {
+ "task_description": event.task.description,
+ "task_name": event.task.name,
+ "context": event.context,
+ "agent": source.agent.role,
+ }
+ elif event_type == "llm_call_started":
+ return {
+ **self._safe_serialize_to_dict(event),
+ "messages": self._truncate_messages(event.messages),
+ }
+ elif event_type == "llm_call_completed":
+ return {
+ **self._safe_serialize_to_dict(event),
+ "messages": self._truncate_messages(event.messages),
+ }
+ else:
+ return {
+ "event_type": event_type,
+ "event": self._safe_serialize_to_dict(event),
+ "source": source,
+ }
+
+ # TODO: move to utils
+ def _safe_serialize_to_dict(
+ self, obj, exclude: set[str] | None = None
+ ) -> Dict[str, Any]:
+ """Safely serialize an object to a dictionary for event data."""
+ try:
+ serialized = to_serializable(obj, exclude)
+ if isinstance(serialized, dict):
+ return serialized
+ else:
+ return {"serialized_data": serialized}
+ except Exception as e:
+ return {"serialization_error": str(e), "object_type": type(obj).__name__}
+
+ # TODO: move to utils
+ def _truncate_messages(self, messages, max_content_length=200, max_messages=5):
+ """Truncate message content and limit number of messages"""
+ if not messages or not isinstance(messages, list):
+ return messages
+
+ # Limit number of messages
+ limited_messages = messages[:max_messages]
+
+ # Truncate each message content
+ for msg in limited_messages:
+ if isinstance(msg, dict) and "content" in msg:
+ content = msg["content"]
+ if len(content) > max_content_length:
+ msg["content"] = content[:max_content_length] + "..."
+
+ return limited_messages
diff --git a/src/crewai/utilities/events/listeners/tracing/types.py b/src/crewai/utilities/events/listeners/tracing/types.py
new file mode 100644
index 000000000..7c9955384
--- /dev/null
+++ b/src/crewai/utilities/events/listeners/tracing/types.py
@@ -0,0 +1,19 @@
+from dataclasses import dataclass, field, asdict
+from datetime import datetime, timezone
+from typing import Dict, Any
+import uuid
+
+
+@dataclass
+class TraceEvent:
+ """Individual trace event payload"""
+
+ event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
+ timestamp: str = field(
+ default_factory=lambda: datetime.now(timezone.utc).isoformat()
+ )
+ type: str = ""
+ event_data: Dict[str, Any] = field(default_factory=dict)
+
+ def to_dict(self) -> Dict[str, Any]:
+ return asdict(self)
diff --git a/src/crewai/utilities/events/llm_events.py b/src/crewai/utilities/events/llm_events.py
index 74101a041..b0518c1a0 100644
--- a/src/crewai/utilities/events/llm_events.py
+++ b/src/crewai/utilities/events/llm_events.py
@@ -5,6 +5,7 @@ from pydantic import BaseModel
from crewai.utilities.events.base_events import BaseEvent
+
class LLMEventBase(BaseEvent):
task_name: Optional[str] = None
task_id: Optional[str] = None
@@ -32,6 +33,7 @@ class LLMEventBase(BaseEvent):
self.task_id = task.id
self.task_name = task.name
+
class LLMCallType(Enum):
"""Type of LLM call being made"""
@@ -48,6 +50,7 @@ class LLMCallStartedEvent(LLMEventBase):
"""
type: str = "llm_call_started"
+ model: Optional[str] = None
messages: Optional[Union[str, List[Dict[str, Any]]]] = None
tools: Optional[List[dict[str, Any]]] = None
callbacks: Optional[List[Any]] = None
@@ -61,6 +64,8 @@ class LLMCallCompletedEvent(LLMEventBase):
messages: str | list[dict[str, Any]] | None = None
response: Any
call_type: LLMCallType
+ model: Optional[str] = None
+
class LLMCallFailedEvent(LLMEventBase):
"""Event emitted when a LLM call fails"""
diff --git a/tests/cassettes/TestTraceListenerSetup.test_batch_manager_finalizes_batch_clears_buffer.yaml b/tests/cassettes/TestTraceListenerSetup.test_batch_manager_finalizes_batch_clears_buffer.yaml
new file mode 100644
index 000000000..7ff3d14f8
--- /dev/null
+++ b/tests/cassettes/TestTraceListenerSetup.test_batch_manager_finalizes_batch_clears_buffer.yaml
@@ -0,0 +1,470 @@
+interactions:
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '825'
+ content-type:
+ - application/json
+ cookie:
+ - __cf_bm=ePO5hy0kEoADCuKcboFy1iS1qckCE5KCpifQaXnlomM-1754508545-1.0.1.1-ieWfjcdIxQIXGfaMizvmgTvZPRFehqDXliegaOT7EO.kt7KSSFGmNDcC35_D9hOhE.fJ5K302uX0snQF3nLaapds2dqgGbNcsyFPOKNvAdI;
+ _cfuvid=NaXWifUGChHp6Ap1mvfMrNzmO4HdzddrqXkSR9T.hYo-1754508545647-0.0.1.1-604800000
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '600.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFLBbpwwEL3zFSOflwqykE25RZWiRuq9hzZCEzOAE+NxbbPbJtp/rwyb
+ hU1bqRck5s17fm9mXhMAoRpRgZA9BjlYnX7K6dq/jNkXaV9uyx/9vfx617Ed9mP+uRObyODHJ5Lh
+ jfVB8mA1BcVmhqUjDBRV811ZlNlNWVxPwMAN6UjrbEgLTgdlVHqVXRVptkvzmxO7ZyXJiwq+JQAA
+ r9M3+jQN/RQVZJu3ykDeY0eiOjcBCMc6VgR6r3xAE8RmASWbQGayfg+GDyDRQKf2BAhdtA1o/IEc
+ wHdzpwxquJ3+K+hJa4YDO92sBR21o8cYyoxarwA0hgPGoUxRHk7I8Wxec2cdP/p3VNEqo3xfO0LP
+ Jhr1ga2Y0GMC8DANabzILazjwYY68DNNz+XlbtYTy25W6PYEBg6oV/XdabSXenVDAZX2qzELibKn
+ ZqEuO8GxUbwCklXqP938TXtOrkz3P/ILICXZQE1tHTVKXiZe2hzF0/1X23nKk2Hhye2VpDoocnET
+ DbU46vmghP/lAw11q0xHzjo1X1Vr622BZYH0cStFckx+AwAA//8DAMHQtj5jAwAA
+ headers:
+ CF-RAY:
+ - 96b0f0f0ac9e7ad9-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:07 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '653'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '667'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999830'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999827'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_3f500b79ab1a400ea9e26d0f12e890bb
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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"}'
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '797'
+ content-type:
+ - application/json
+ cookie:
+ - __cf_bm=f59gEPi_nA3TTxtjbKaSQpvkTwezaAqOvqfxiGzRnVQ-1754508546-1.0.1.1-JrSaytxVIQSVE00I.vyGj7d4HJbbMV6R9fWPJbkDKu0Y8ueMRzTwTUnfz0YzP5nsZX5oxoE6WlmFxOuz0rRuq9YhZZsO_TbaFBOFk1jGK9U;
+ _cfuvid=3D66v3.J_RcVoYy9dlF.jHwq1zTIm842xynZxzSy1Wc-1754508546352-0.0.1.1-604800000
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '200.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFLBatwwEL37K6Y622V3Y8eJb6FQ2kIPoS0EmmAm8tirRNYISc52Cfvv
+ RfZm7bQp9GLwvHlP783McwIgVCMqEHKLQfZWZx/WdB42Nw9Xtux3dPMNr89/fPm+f7zct9dfRRoZ
+ fP9AMryw3kvuraag2EywdISBouq6LPJidVHk5Qj03JCOtM6GLOesV0Zlm9Umz1Zltr44sresJHlR
+ wc8EAOB5/EafpqFfooJV+lLpyXvsSFSnJgDhWMeKQO+VD2iCSGdQsglkRuufwfAOJBro1BMBQhdt
+ Axq/Iwdwaz4qgxquxv8KPpHWnMKOnW7eLSUdtYPHGMsMWi8ANIYDxrGMYe6OyOFkX3NnHd/7P6ii
+ VUb5be0IPZto1Qe2YkQPCcDdOKbhVXJhHfc21IEfaXxuXZSTnpi3s0SPYOCAelEvN+kbenVDAZX2
+ i0ELiXJLzUydt4JDo3gBJIvUf7t5S3tKrkz3P/IzICXZQE1tHTVKvk48tzmKx/uvttOUR8PCk3tS
+ kuqgyMVNNNTioKeTEn7vA/V1q0xHzjo13VVr67Mcixzp8kyK5JD8BgAA//8DAB06pnJlAwAA
+ headers:
+ CF-RAY:
+ - 96b0f0f54d6aeb2c-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:08 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '809'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '823'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999827'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999827'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_806f7071fb664da48953f5b216b56d9a
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"trace_id": "eb9e0ee1-15ed-4044-b84b-f17e493a1e28", "execution_type":
+ "crew", "execution_context": {"crew_fingerprint": null, "crew_name": "crew",
+ "flow_name": "Unknown Flow", "crewai_version": "0.152.0", "privacy_level": "standard"},
+ "execution_metadata": {"expected_duration_estimate": 300, "agent_count": 0,
+ "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-08-06T19:30:52.210701+00:00"}}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '413'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing/batches
+ response:
+ body:
+ string: "\n\n
\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:52 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - bec0cf39-af9c-4955-b600-607187a7b10b
+ x-runtime:
+ - '0.005352'
+ status:
+ code: 404
+ message: Not Found
+- request:
+ body: '{"version": "0.152.0", "batch_id": "eb9e0ee1-15ed-4044-b84b-f17e493a1e28",
+ "user_context": {"user_id": "anonymous", "organization_id": "", "session_id":
+ "e7e7a716-e64b-490b-96db-5c5367042114", "trace_id": "54e95e1f-cd41-4ece-9e5e-21984d635e6a"},
+ "execution_metadata": {"crew_name": "crew", "execution_start": "2025-08-06T19:30:52.209750+00:00",
+ "crewai_version": "0.152.0"}, "events": [{"event_id": "98b2a833-63fc-457c-a2e0-6ce228a8214c",
+ "timestamp": "2025-08-06T19:30:52.328066+00:00", "type": "crew_kickoff_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.209750+00:00", "type": "crew_kickoff_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "inputs": null}}, {"event_id": "4abf563c-d35f-4a09-867d-75c1c54b3fed",
+ "timestamp": "2025-08-06T19:30:52.328113+00:00", "type": "crew_kickoff_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.209750+00:00", "type": "crew_kickoff_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "inputs": null}}, {"event_id": "60bdc932-6b56-4f1d-bcc2-5b3b57c8dc94",
+ "timestamp": "2025-08-06T19:30:52.330079+00:00", "type": "task_started", "event_data":
+ {"task_description": "Say hello to the world", "task_name": null, "context":
+ "", "agent": "Test Agent"}}, {"event_id": "97761b9f-d132-47e7-8857-5fdda8c80b65",
+ "timestamp": "2025-08-06T19:30:52.330089+00:00", "type": "task_started", "event_data":
+ {"task_description": "Say hello to the world", "task_name": null, "context":
+ "", "agent": "Test Agent"}}, {"event_id": "cdaa47c1-448f-476e-9761-14a25f26c481",
+ "timestamp": "2025-08-06T19:30:52.330477+00:00", "type": "agent_execution_started",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionStartedEvent"}}, {"event_id": "7aa43738-3903-44cf-8416-d47542469537",
+ "timestamp": "2025-08-06T19:30:52.330612+00:00", "type": "agent_execution_started",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionStartedEvent"}}, {"event_id": "6eb42795-be95-4f1c-b70f-385c59483e43",
+ "timestamp": "2025-08-06T19:30:52.330751+00:00", "type": "llm_call_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.330725+00:00", "type": "llm_call_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "1bfe4b49-ba6a-464d-9b6a-ca2eb8e965d8", "agent_id":
+ "5d6dbe70-71fc-42e2-ba0d-61b460542dad", "agent_role": "Test Agent", "model":
+ "gpt-4o-mini", "messages": [{"role": "system", "content": "You are Test Agent.
+ Test backstory\nYour personal goal is: Test goal\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\n..."}, {"role": "user", "content": "\nCurrent Task:
+ Say hello to the world\n\nThis is the expected criteria for your final answer:
+ hello world\nyou MUST return the actual complete content as the final answer,
+ not a summary.\n\nBegin! This is ..."}], "tools": null, "callbacks": [""], "available_functions": null}}, {"event_id": "679e3211-ef91-45c0-9d4a-e5118e653dbd",
+ "timestamp": "2025-08-06T19:30:52.330798+00:00", "type": "llm_call_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.330725+00:00", "type": "llm_call_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "1bfe4b49-ba6a-464d-9b6a-ca2eb8e965d8", "agent_id":
+ "5d6dbe70-71fc-42e2-ba0d-61b460542dad", "agent_role": "Test Agent", "model":
+ "gpt-4o-mini", "messages": [{"role": "system", "content": "You are Test Agent.
+ Test backstory\nYour personal goal is: Test goal\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\n..."}, {"role": "user", "content": "\nCurrent Task:
+ Say hello to the world\n\nThis is the expected criteria for your final answer:
+ hello world\nyou MUST return the actual complete content as the final answer,
+ not a summary.\n\nBegin! This is ..."}], "tools": null, "callbacks": [""], "available_functions": null}}, {"event_id": "911c67ea-125b-4adf-87a5-4a9265575f93",
+ "timestamp": "2025-08-06T19:30:52.335757+00:00", "type": "llm_call_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.335728+00:00", "type": "llm_call_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "1bfe4b49-ba6a-464d-9b6a-ca2eb8e965d8", "agent_id":
+ "5d6dbe70-71fc-42e2-ba0d-61b460542dad", "agent_role": "Test Agent", "messages":
+ [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal
+ goal is: Test goal\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\n..."},
+ {"role": "user", "content": "\nCurrent Task: Say hello to the world\n\nThis
+ is the expected criteria for your final answer: hello world\nyou MUST return
+ the actual complete content as the final answer, not a summary.\n\nBegin! This
+ is ..."}], "response": "I now can give a great answer \nFinal Answer: hello
+ world", "call_type": "", "response_cost":
+ 3.255e-05, "model": "gpt-4o-mini"}}, {"event_id": "1c93586f-82b9-4999-adda-78c8010b59f6",
+ "timestamp": "2025-08-06T19:30:52.335800+00:00", "type": "llm_call_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.335728+00:00", "type": "llm_call_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "1bfe4b49-ba6a-464d-9b6a-ca2eb8e965d8", "agent_id":
+ "5d6dbe70-71fc-42e2-ba0d-61b460542dad", "agent_role": "Test Agent", "messages":
+ [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal
+ goal is: Test goal\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\n..."},
+ {"role": "user", "content": "\nCurrent Task: Say hello to the world\n\nThis
+ is the expected criteria for your final answer: hello world\nyou MUST return
+ the actual complete content as the final answer, not a summary.\n\nBegin! This
+ is ..."}], "response": "I now can give a great answer \nFinal Answer: hello
+ world", "call_type": "", "response_cost":
+ 3.255e-05, "model": "gpt-4o-mini"}}, {"event_id": "eb9d53af-ce39-4241-ab93-e40545a1ee78",
+ "timestamp": "2025-08-06T19:30:52.335904+00:00", "type": "agent_execution_completed",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionCompletedEvent"}}, {"event_id": "dc71f5f8-5762-4e44-ac60-aa20b033c9f9",
+ "timestamp": "2025-08-06T19:30:52.335989+00:00", "type": "agent_execution_completed",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionCompletedEvent"}}, {"event_id": "84da8fb8-9247-4718-bc85-a69033c9261f",
+ "timestamp": "2025-08-06T19:30:52.336082+00:00", "type": "task_completed", "event_data":
+ {"serialization_error": "Circular reference detected (id repeated)", "object_type":
+ "TaskCompletedEvent"}}, {"event_id": "c1a23877-2b87-40be-98a1-a3b2630c8657",
+ "timestamp": "2025-08-06T19:30:52.336107+00:00", "type": "task_completed", "event_data":
+ {"serialization_error": "Circular reference detected (id repeated)", "object_type":
+ "TaskCompletedEvent"}}, {"event_id": "c77587d7-68d6-4600-b98a-74fe58af41fc",
+ "timestamp": "2025-08-06T19:30:52.337164+00:00", "type": "crew_kickoff_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.337145+00:00", "type": "crew_kickoff_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "output": {"description": "Say hello to the
+ world", "name": null, "expected_output": "hello world", "summary": "Say hello
+ to the world...", "raw": "hello world", "pydantic": null, "json_dict": null,
+ "agent": "Test Agent", "output_format": "raw"}, "total_tokens": 170}}]}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '8300'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing
+ response:
+ body:
+ string: "\n\n\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:52 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - 78674bcb-6c8a-4eaf-8577-5cb27cac4089
+ x-runtime:
+ - '0.006009'
+ status:
+ code: 404
+ message: Not Found
+version: 1
diff --git a/tests/cassettes/TestTraceListenerSetup.test_events_collection_batch_manager.yaml b/tests/cassettes/TestTraceListenerSetup.test_events_collection_batch_manager.yaml
new file mode 100644
index 000000000..9966f0d57
--- /dev/null
+++ b/tests/cassettes/TestTraceListenerSetup.test_events_collection_batch_manager.yaml
@@ -0,0 +1,470 @@
+interactions:
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '825'
+ content-type:
+ - application/json
+ cookie:
+ - __cf_bm=ePO5hy0kEoADCuKcboFy1iS1qckCE5KCpifQaXnlomM-1754508545-1.0.1.1-ieWfjcdIxQIXGfaMizvmgTvZPRFehqDXliegaOT7EO.kt7KSSFGmNDcC35_D9hOhE.fJ5K302uX0snQF3nLaapds2dqgGbNcsyFPOKNvAdI;
+ _cfuvid=NaXWifUGChHp6Ap1mvfMrNzmO4HdzddrqXkSR9T.hYo-1754508545647-0.0.1.1-604800000
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '600.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFJNa9wwEL37Vww6x8Wbtbu7voXQQC+lh0Jb2mAm0thWI0tCkuOWsP+9
+ SN6svf2AXAyeN+/pvZl5zgCYFKwGxnsMfLAqv93Q22mP+O7z9OlwUIW4LT7S+LX68nj3YWJXkWEe
+ fhAPL6w33AxWUZBGzzB3hIGi6mZXlVWxr6oiAYMRpCKtsyEvTT5ILfPr4rrMi12+2Z/YvZGcPKvh
+ WwYA8Jy+0acW9JPVkLRSZSDvsSNWn5sAmDMqVhh6L31AHdjVAnKjA+lk/T1oMwFHDZ18IkDoom1A
+ 7SdyAN/1ndSo4Cb919CTUgYm45RYCzpqR48xlB6VWgGotQkYh5Ki3J+Q49m8Mp115sH/QWWt1NL3
+ jSP0RkejPhjLEnrMAO7TkMaL3Mw6M9jQBPNI6blNtZv12LKbFbo9gcEEVKv67jTaS71GUECp/GrM
+ jCPvSSzUZSc4CmlWQLZK/bebf2nPyaXuXiO/AJyTDSQa60hIfpl4aXMUT/d/becpJ8PMk3uSnJog
+ ycVNCGpxVPNBMf/LBxqaVuqOnHVyvqrWNtsSqxLpsOUsO2a/AQAA//8DAD59q5pjAwAA
+ headers:
+ CF-RAY:
+ - 96b0f1059ae17ad9-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:10 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '521'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '537'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999827'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999827'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_c94c2a416aee4c93bae1f801c8ae3e72
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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"}'
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '797'
+ content-type:
+ - application/json
+ cookie:
+ - __cf_bm=f59gEPi_nA3TTxtjbKaSQpvkTwezaAqOvqfxiGzRnVQ-1754508546-1.0.1.1-JrSaytxVIQSVE00I.vyGj7d4HJbbMV6R9fWPJbkDKu0Y8ueMRzTwTUnfz0YzP5nsZX5oxoE6WlmFxOuz0rRuq9YhZZsO_TbaFBOFk1jGK9U;
+ _cfuvid=3D66v3.J_RcVoYy9dlF.jHwq1zTIm842xynZxzSy1Wc-1754508546352-0.0.1.1-604800000
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '200.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFJNa9wwEL37Vww6x2U/7GzqW2j6BYVSaE5tMBN5bKuVNYok7zaE/e9F
+ 2u3a2ybQi8Hz5j29NzNPGYBQjahAyB6DHKzO3yzpcnf74f3nh6+rx/bT9saa2+u343D55eGmFBeR
+ wfc/SIY/rFeSB6spKDYHWDrCQFF1uSmLcnFVlosEDNyQjrTOhrzgfFBG5avFqsgXm3x5dWT3rCR5
+ UcG3DADgKX2jT9PQL1FB0kqVgbzHjkR1agIQjnWsCPRe+YAmiIsJlGwCmWT9IxjegUQDndoSIHTR
+ NqDxO3IA3807ZVDDdfqvoCetGXbsdDMXdNSOHmMoM2o9A9AYDhiHkqLcHZH9ybzmzjq+939RRauM
+ 8n3tCD2baNQHtiKh+wzgLg1pPMstrOPBhjrwT0rPLcvNQU9Mu5mh6yMYOKCe1TfH0Z7r1Q0FVNrP
+ xiwkyp6aiTrtBMdG8QzIZqn/dfOc9iG5Mt3/yE+AlGQDNbV11Ch5nnhqcxRP96W205STYeHJbZWk
+ OihycRMNtTjqw0EJ/+gDDXWrTEfOOnW4qtbW6wLLAun1Wopsn/0GAAD//wMASJr3q2MDAAA=
+ headers:
+ CF-RAY:
+ - 96b0f109ae7aeb2c-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:11 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '499'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '511'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999830'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999827'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_dece4be9f37c4d64b324ab36d1ed9cf4
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"trace_id": "ff5ac8a9-dec2-4b73-8928-3dd06d12051f", "execution_type":
+ "crew", "execution_context": {"crew_fingerprint": null, "crew_name": "crew",
+ "flow_name": "Unknown Flow", "crewai_version": "0.152.0", "privacy_level": "standard"},
+ "execution_metadata": {"expected_duration_estimate": 300, "agent_count": 0,
+ "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-08-06T19:30:51.727534+00:00"}}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '413'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing/batches
+ response:
+ body:
+ string: "\n\n\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:51 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - 0b6a5ff5-789e-4c0d-a10b-316fecc0e905
+ x-runtime:
+ - '0.005528'
+ status:
+ code: 404
+ message: Not Found
+- request:
+ body: '{"version": "0.152.0", "batch_id": "ff5ac8a9-dec2-4b73-8928-3dd06d12051f",
+ "user_context": {"user_id": "anonymous", "organization_id": "", "session_id":
+ "aabc00e7-d423-4385-8b83-0468c03ae47b", "trace_id": "0a0586da-135c-4080-a352-dbe47bb2ac86"},
+ "execution_metadata": {"crew_name": "crew", "execution_start": "2025-08-06T19:30:51.726805+00:00",
+ "crewai_version": "0.152.0"}, "events": [{"event_id": "211eb90d-fb76-4ee5-bee7-62cc2f1d9aa8",
+ "timestamp": "2025-08-06T19:30:51.842887+00:00", "type": "crew_kickoff_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:51.726805+00:00", "type": "crew_kickoff_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "inputs": null}}, {"event_id": "713e4dbd-887f-4481-a6c8-554b637848e2",
+ "timestamp": "2025-08-06T19:30:51.842982+00:00", "type": "crew_kickoff_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:51.726805+00:00", "type": "crew_kickoff_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "inputs": null}}, {"event_id": "b920108c-c6fe-40d7-baa3-29c23d76a8e1",
+ "timestamp": "2025-08-06T19:30:51.844489+00:00", "type": "task_started", "event_data":
+ {"task_description": "Say hello to the world", "task_name": null, "context":
+ "", "agent": "Test Agent"}}, {"event_id": "96180117-d060-49ab-8327-712f230653f2",
+ "timestamp": "2025-08-06T19:30:51.844512+00:00", "type": "task_started", "event_data":
+ {"task_description": "Say hello to the world", "task_name": null, "context":
+ "", "agent": "Test Agent"}}, {"event_id": "82baa39d-d1ae-44f8-8f35-40646fdec793",
+ "timestamp": "2025-08-06T19:30:51.845195+00:00", "type": "agent_execution_started",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionStartedEvent"}}, {"event_id": "c34d2e12-6671-4593-a45d-8742704f6ace",
+ "timestamp": "2025-08-06T19:30:51.845868+00:00", "type": "agent_execution_started",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionStartedEvent"}}, {"event_id": "87d12818-f0b4-46d0-8ecc-e46afaf8eddb",
+ "timestamp": "2025-08-06T19:30:51.846100+00:00", "type": "llm_call_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:51.846006+00:00", "type": "llm_call_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "7f026c34-8c77-4710-8ecb-9d4c830b9eb4", "agent_id":
+ "bd02dc4e-982e-481c-9358-2b4a7ac73831", "agent_role": "Test Agent", "model":
+ "gpt-4o-mini", "messages": [{"role": "system", "content": "You are Test Agent.
+ Test backstory\nYour personal goal is: Test goal\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\n..."}, {"role": "user", "content": "\nCurrent Task:
+ Say hello to the world\n\nThis is the expected criteria for your final answer:
+ hello world\nyou MUST return the actual complete content as the final answer,
+ not a summary.\n\nBegin! This is ..."}], "tools": null, "callbacks": [""], "available_functions": null}}, {"event_id": "bbfd4480-87aa-4a56-b988-2dcc9e142c20",
+ "timestamp": "2025-08-06T19:30:51.846155+00:00", "type": "llm_call_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:51.846006+00:00", "type": "llm_call_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "7f026c34-8c77-4710-8ecb-9d4c830b9eb4", "agent_id":
+ "bd02dc4e-982e-481c-9358-2b4a7ac73831", "agent_role": "Test Agent", "model":
+ "gpt-4o-mini", "messages": [{"role": "system", "content": "You are Test Agent.
+ Test backstory\nYour personal goal is: Test goal\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\n..."}, {"role": "user", "content": "\nCurrent Task:
+ Say hello to the world\n\nThis is the expected criteria for your final answer:
+ hello world\nyou MUST return the actual complete content as the final answer,
+ not a summary.\n\nBegin! This is ..."}], "tools": null, "callbacks": [""], "available_functions": null}}, {"event_id": "25a17ec7-b2ee-4eeb-bdf5-27efffed961c",
+ "timestamp": "2025-08-06T19:30:52.018207+00:00", "type": "llm_call_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.017914+00:00", "type": "llm_call_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "7f026c34-8c77-4710-8ecb-9d4c830b9eb4", "agent_id":
+ "bd02dc4e-982e-481c-9358-2b4a7ac73831", "agent_role": "Test Agent", "messages":
+ [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal
+ goal is: Test goal\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\n..."},
+ {"role": "user", "content": "\nCurrent Task: Say hello to the world\n\nThis
+ is the expected criteria for your final answer: hello world\nyou MUST return
+ the actual complete content as the final answer, not a summary.\n\nBegin! This
+ is ..."}], "response": "I now can give a great answer \nFinal Answer: hello
+ world", "call_type": "", "response_cost":
+ 3.135e-05, "model": "gpt-4o-mini"}}, {"event_id": "0ccb9b70-c5ad-4f7f-b3ee-ecfd62c2d7cc",
+ "timestamp": "2025-08-06T19:30:52.018273+00:00", "type": "llm_call_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.017914+00:00", "type": "llm_call_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "7f026c34-8c77-4710-8ecb-9d4c830b9eb4", "agent_id":
+ "bd02dc4e-982e-481c-9358-2b4a7ac73831", "agent_role": "Test Agent", "messages":
+ [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal
+ goal is: Test goal\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\n..."},
+ {"role": "user", "content": "\nCurrent Task: Say hello to the world\n\nThis
+ is the expected criteria for your final answer: hello world\nyou MUST return
+ the actual complete content as the final answer, not a summary.\n\nBegin! This
+ is ..."}], "response": "I now can give a great answer \nFinal Answer: hello
+ world", "call_type": "", "response_cost":
+ 3.135e-05, "model": "gpt-4o-mini"}}, {"event_id": "d7f4440b-8f9f-4e29-a946-6d10f4bdfc3c",
+ "timestamp": "2025-08-06T19:30:52.018559+00:00", "type": "agent_execution_completed",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionCompletedEvent"}}, {"event_id": "072195c3-54df-4cba-9068-b9a25bbb8d7c",
+ "timestamp": "2025-08-06T19:30:52.018669+00:00", "type": "agent_execution_completed",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionCompletedEvent"}}, {"event_id": "0b6f9e85-32c9-4c62-9049-6890953e2143",
+ "timestamp": "2025-08-06T19:30:52.018838+00:00", "type": "task_completed", "event_data":
+ {"serialization_error": "Circular reference detected (id repeated)", "object_type":
+ "TaskCompletedEvent"}}, {"event_id": "5ff20fcb-ec10-40ac-bb90-9568aa4eb1de",
+ "timestamp": "2025-08-06T19:30:52.018867+00:00", "type": "task_completed", "event_data":
+ {"serialization_error": "Circular reference detected (id repeated)", "object_type":
+ "TaskCompletedEvent"}}, {"event_id": "c5a36300-3911-4d75-a660-d133a7a4be94",
+ "timestamp": "2025-08-06T19:30:52.020135+00:00", "type": "crew_kickoff_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.020115+00:00", "type": "crew_kickoff_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "output": {"description": "Say hello to the
+ world", "name": null, "expected_output": "hello world", "summary": "Say hello
+ to the world...", "raw": "hello world", "pydantic": null, "json_dict": null,
+ "agent": "Test Agent", "output_format": "raw"}, "total_tokens": 170}}]}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '8300'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing
+ response:
+ body:
+ string: "\n\n\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:52 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - 9edcdee4-f720-431e-9d6d-2dbc1a7bb8fe
+ x-runtime:
+ - '0.005504'
+ status:
+ code: 404
+ message: Not Found
+version: 1
diff --git a/tests/cassettes/TestTraceListenerSetup.test_trace_listener_collects_crew_events.yaml b/tests/cassettes/TestTraceListenerSetup.test_trace_listener_collects_crew_events.yaml
new file mode 100644
index 000000000..e9995e608
--- /dev/null
+++ b/tests/cassettes/TestTraceListenerSetup.test_trace_listener_collects_crew_events.yaml
@@ -0,0 +1,450 @@
+interactions:
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '825'
+ content-type:
+ - application/json
+ cookie:
+ - __cf_bm=ePO5hy0kEoADCuKcboFy1iS1qckCE5KCpifQaXnlomM-1754508545-1.0.1.1-ieWfjcdIxQIXGfaMizvmgTvZPRFehqDXliegaOT7EO.kt7KSSFGmNDcC35_D9hOhE.fJ5K302uX0snQF3nLaapds2dqgGbNcsyFPOKNvAdI;
+ _cfuvid=NaXWifUGChHp6Ap1mvfMrNzmO4HdzddrqXkSR9T.hYo-1754508545647-0.0.1.1-604800000
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '600.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFLBbpwwEL3zFSOfl2oJ0N1ySypV6aG3ntpGaGIGcGI8lm2yjaL998qw
+ Wdi2kXJBYt685/dm5iUBEKoRFQjZY5CD1ennjD6ON/5Hrr4dysxu8/72++NNd/twXT4PYhMZfP9A
+ MryyPkgerKag2MywdISBomq2K4tyuy+L/QQM3JCOtM6GtOB0UEalV9urIt3u0mx/YvesJHlRwc8E
+ AOBl+kafpqHfooLt5rUykPfYkajOTQDCsY4Vgd4rH9AEsVlAySaQmax/BcMHkGigU08ECF20DWj8
+ gRzAL/NFGdRwPf1X0JPWDAd2ulkLOmpHjzGUGbVeAWgMB4xDmaLcnZDj2bzmzjq+939RRauM8n3t
+ CD2baNQHtmJCjwnA3TSk8SK3sI4HG+rAjzQ9l5W7WU8su1mh+QkMHFCv6rvTaC/16oYCKu1XYxYS
+ ZU/NQl12gmOjeAUkq9T/uvmf9pxcme498gsgJdlATW0dNUpeJl7aHMXTfavtPOXJsPDknpSkOihy
+ cRMNtTjq+aCEf/aBhrpVpiNnnZqvqrV1XmBZIH3KpUiOyR8AAAD//wMAErrW9WMDAAA=
+ headers:
+ CF-RAY:
+ - 96b0f0fb5c067ad9-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:09 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '628'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '657'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999827'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999827'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_a0daca00035c423daf0e9df208720180
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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"}'
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '797'
+ content-type:
+ - application/json
+ cookie:
+ - __cf_bm=f59gEPi_nA3TTxtjbKaSQpvkTwezaAqOvqfxiGzRnVQ-1754508546-1.0.1.1-JrSaytxVIQSVE00I.vyGj7d4HJbbMV6R9fWPJbkDKu0Y8ueMRzTwTUnfz0YzP5nsZX5oxoE6WlmFxOuz0rRuq9YhZZsO_TbaFBOFk1jGK9U;
+ _cfuvid=3D66v3.J_RcVoYy9dlF.jHwq1zTIm842xynZxzSy1Wc-1754508546352-0.0.1.1-604800000
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '200.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFLLjtswDLz7Kwid4yLZ2HXiW7FA0R567KXtwmAk2tZWlgSJTlos8u+F
+ nGzs9AH0YsAczmiG5EsGILQSNQjZI8vBm/xxQ2+Pnz89n8Lj7gvvP3BArNb94cClqsQqMdzhmSS/
+ st5IN3hDrJ29wDIQMiXVTVUW5XpXFvsJGJwik2id57xw+aCtzh/WD0W+rvLN7srunZYURQ1fMwCA
+ l+mbfFpFP0QN69VrZaAYsSNR35oARHAmVQTGqCOjZbGaQeksk52sfwTrTiDRQqePBAhdsg1o44kC
+ wDf7Xls08G76r6EnYxycXDBqKRioHSOmUHY0ZgGgtY4xDWWK8nRFzjfzxnU+uEP8jSpabXXsm0AY
+ nU1GIzsvJvScATxNQxrvcgsf3OC5Yfedpuc2ZXXRE/NuFuj2CrJjNIt6dR3tvV6jiFGbuBizkCh7
+ UjN13gmOSrsFkC1S/+nmb9qX5Np2/yM/A1KSZ1KND6S0vE88twVKp/uvttuUJ8MiUjhqSQ1rCmkT
+ iloczeWgRPwZmYam1baj4IO+XFXrm22BZYG030qRnbNfAAAA//8DAOX6h6tjAwAA
+ headers:
+ CF-RAY:
+ - 96b0f101793aeb2c-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:09 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '541'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '557'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999827'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999827'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_df70f95325b14817a692f23cf9cca880
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"trace_id": "2487456d-e03a-4eae-92a1-e9779e8f06a1", "execution_type":
+ "crew", "execution_context": {"crew_fingerprint": null, "crew_name": "Unknown
+ Crew", "flow_name": "Unknown Flow", "crewai_version": "0.152.0", "privacy_level":
+ "standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count":
+ 0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-08-06T19:30:52.475039+00:00"}}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '421'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing/batches
+ response:
+ body:
+ string: "\n\n\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:52 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - 3640ddcd-56a3-48cc-9a5a-110ebe34ac80
+ x-runtime:
+ - '0.007004'
+ status:
+ code: 404
+ message: Not Found
+- request:
+ body: '{"version": "0.152.0", "batch_id": "2487456d-e03a-4eae-92a1-e9779e8f06a1",
+ "user_context": {"user_id": "anonymous", "organization_id": "", "session_id":
+ "57ab4cf7-915a-4d4e-b01d-3791f418dd39", "trace_id": "c780f111-40df-4c4b-ae88-b09c0f7e3276"},
+ "execution_metadata": {"crew_name": "Unknown Crew", "crewai_version": "0.152.0"},
+ "events": [{"event_id": "55b93497-f7f7-4c2e-baf9-eeec358cf90f", "timestamp":
+ "2025-08-06T19:30:52.588780+00:00", "type": "llm_call_started", "event_data":
+ {"timestamp": "2025-08-06T19:30:52.473897+00:00", "type": "llm_call_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "020f12f8-4bcf-4e49-9bf9-d8fee70eaf6a", "agent_id":
+ "126a2971-a630-405c-a0d9-72d2c46e8074", "agent_role": "Test Agent", "model":
+ "gpt-4o-mini", "messages": [{"role": "system", "content": "You are Test Agent.
+ Test backstory\nYour personal goal is: Test goal\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\n..."}, {"role": "user", "content": "\nCurrent Task:
+ Say hello to the world\n\nThis is the expected criteria for your final answer:
+ hello world\nyou MUST return the actual complete content as the final answer,
+ not a summary.\n\nBegin! This is ..."}], "tools": null, "callbacks": [""], "available_functions": null}}, {"event_id": "967e03f5-fc65-477f-a1ba-4614acbd0527",
+ "timestamp": "2025-08-06T19:30:52.588932+00:00", "type": "llm_call_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.473897+00:00", "type": "llm_call_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "020f12f8-4bcf-4e49-9bf9-d8fee70eaf6a", "agent_id":
+ "126a2971-a630-405c-a0d9-72d2c46e8074", "agent_role": "Test Agent", "model":
+ "gpt-4o-mini", "messages": [{"role": "system", "content": "You are Test Agent.
+ Test backstory\nYour personal goal is: Test goal\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\n..."}, {"role": "user", "content": "\nCurrent Task:
+ Say hello to the world\n\nThis is the expected criteria for your final answer:
+ hello world\nyou MUST return the actual complete content as the final answer,
+ not a summary.\n\nBegin! This is ..."}], "tools": null, "callbacks": [""], "available_functions": null}}, {"event_id": "f67f33c0-f9ac-450e-987e-bb48880b9b69",
+ "timestamp": "2025-08-06T19:30:52.597813+00:00", "type": "llm_call_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.597748+00:00", "type": "llm_call_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "020f12f8-4bcf-4e49-9bf9-d8fee70eaf6a", "agent_id":
+ "126a2971-a630-405c-a0d9-72d2c46e8074", "agent_role": "Test Agent", "messages":
+ [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal
+ goal is: Test goal\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\n..."},
+ {"role": "user", "content": "\nCurrent Task: Say hello to the world\n\nThis
+ is the expected criteria for your final answer: hello world\nyou MUST return
+ the actual complete content as the final answer, not a summary.\n\nBegin! This
+ is ..."}], "response": "I now can give a great answer \nFinal Answer: hello
+ world", "call_type": "", "response_cost":
+ 3.135e-05, "model": "gpt-4o-mini"}}, {"event_id": "0868106f-9491-4214-9674-4f9c5621875b",
+ "timestamp": "2025-08-06T19:30:52.597885+00:00", "type": "llm_call_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.597748+00:00", "type": "llm_call_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "020f12f8-4bcf-4e49-9bf9-d8fee70eaf6a", "agent_id":
+ "126a2971-a630-405c-a0d9-72d2c46e8074", "agent_role": "Test Agent", "messages":
+ [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal
+ goal is: Test goal\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\n..."},
+ {"role": "user", "content": "\nCurrent Task: Say hello to the world\n\nThis
+ is the expected criteria for your final answer: hello world\nyou MUST return
+ the actual complete content as the final answer, not a summary.\n\nBegin! This
+ is ..."}], "response": "I now can give a great answer \nFinal Answer: hello
+ world", "call_type": "", "response_cost":
+ 3.135e-05, "model": "gpt-4o-mini"}}, {"event_id": "4b68fa53-1829-4201-bfa2-9364dbd8fc51",
+ "timestamp": "2025-08-06T19:30:52.598054+00:00", "type": "agent_execution_completed",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionCompletedEvent"}}, {"event_id": "264a2a9a-c234-45f2-8021-5e4b1b7c4c98",
+ "timestamp": "2025-08-06T19:30:52.598224+00:00", "type": "agent_execution_completed",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionCompletedEvent"}}, {"event_id": "b8b7f244-e2c0-4cac-9411-0efab7b9936a",
+ "timestamp": "2025-08-06T19:30:52.598372+00:00", "type": "task_completed", "event_data":
+ {"serialization_error": "Circular reference detected (id repeated)", "object_type":
+ "TaskCompletedEvent"}}, {"event_id": "c15489a3-31af-48bb-8f32-cb3c3f46f7b9",
+ "timestamp": "2025-08-06T19:30:52.598416+00:00", "type": "task_completed", "event_data":
+ {"serialization_error": "Circular reference detected (id repeated)", "object_type":
+ "TaskCompletedEvent"}}, {"event_id": "6806c6bd-1399-4261-aa23-5b6166c2ab31",
+ "timestamp": "2025-08-06T19:30:52.601018+00:00", "type": "crew_kickoff_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.600994+00:00", "type": "crew_kickoff_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "output": {"description": "Say hello to the
+ world", "name": null, "expected_output": "hello world", "summary": "Say hello
+ to the world...", "raw": "hello world", "pydantic": null, "json_dict": null,
+ "agent": "Test Agent", "output_format": "raw"}, "total_tokens": 170}}]}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '6503'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing
+ response:
+ body:
+ string: "\n\n\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:52 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - 50885aa8-a8e2-4a5a-87f7-6d0e2f2f80f9
+ x-runtime:
+ - '0.006464'
+ status:
+ code: 404
+ message: Not Found
+version: 1
diff --git a/tests/cassettes/TestTraceListenerSetup.test_trace_listener_disabled_when_env_false.yaml b/tests/cassettes/TestTraceListenerSetup.test_trace_listener_disabled_when_env_false.yaml
new file mode 100644
index 000000000..5154b193a
--- /dev/null
+++ b/tests/cassettes/TestTraceListenerSetup.test_trace_listener_disabled_when_env_false.yaml
@@ -0,0 +1,433 @@
+interactions:
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '825'
+ content-type:
+ - application/json
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '600.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFJdb9QwEHzPr1j8nKDkmvTavBUQnw8goQoJqKKts8kZHK9lOy1Q3X9H
+ Tq6XFIrES6Ts7IxndvcuARCqFTUIucMgB6uz5wWduvz8g3nx/tnny3d4MRabX6flW9O9qj6KNDL4
+ +hvJcM96KnmwmoJiM8PSEQaKqsW2Kqv8rCqrCRi4JR1pvQ1ZydmgjMo2+abM8m1WnB3YO1aSvKjh
+ SwIAcDd9o0/T0g9RQ57eVwbyHnsS9bEJQDjWsSLQe+UDmiDSBZRsApnJ+hswfAsSDfTqhgChj7YB
+ jb8lB/DVvFQGNVxM/zW8Jq05hU/sdPtkLemoGz3GWGbUegWgMRwwjmUKc3VA9kf7mnvr+Nr/QRWd
+ MsrvGkfo2USrPrAVE7pPAK6mMY0PkgvreLChCfydpueKajvriWU7a/QABg6oV/XtJn1Er2kpoNJ+
+ NWghUe6oXajLVnBsFa+AZJX6bzePac/Jlen/R34BpCQbqG2so1bJh4mXNkfxeP/VdpzyZFh4cjdK
+ UhMUubiJljoc9XxSwv/0gYamU6YnZ52a76qzzUmJVYl0fiJFsk9+AwAA//8DABLzfQllAwAA
+ headers:
+ CF-RAY:
+ - 96b0f0e62d177ad9-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:05 GMT
+ Server:
+ - cloudflare
+ Set-Cookie:
+ - __cf_bm=ePO5hy0kEoADCuKcboFy1iS1qckCE5KCpifQaXnlomM-1754508545-1.0.1.1-ieWfjcdIxQIXGfaMizvmgTvZPRFehqDXliegaOT7EO.kt7KSSFGmNDcC35_D9hOhE.fJ5K302uX0snQF3nLaapds2dqgGbNcsyFPOKNvAdI;
+ path=/; expires=Wed, 06-Aug-25 19:59:05 GMT; domain=.api.openai.com; HttpOnly;
+ Secure; SameSite=None
+ - _cfuvid=NaXWifUGChHp6Ap1mvfMrNzmO4HdzddrqXkSR9T.hYo-1754508545647-0.0.1.1-604800000;
+ path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '526'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '568'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999827'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999827'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_0e70b38c85e144d289fbdf89082cf16e
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour
+ personal goal is: Test goal\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: Say hello to
+ the world\n\nThis is the expected criteria for your final answer: hello world\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"}'
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '797'
+ content-type:
+ - application/json
+ host:
+ - api.openai.com
+ user-agent:
+ - OpenAI/Python 1.93.0
+ x-stainless-arch:
+ - arm64
+ x-stainless-async:
+ - 'false'
+ x-stainless-lang:
+ - python
+ x-stainless-os:
+ - MacOS
+ x-stainless-package-version:
+ - 1.93.0
+ x-stainless-raw-response:
+ - 'true'
+ x-stainless-read-timeout:
+ - '200.0'
+ x-stainless-retry-count:
+ - '0'
+ x-stainless-runtime:
+ - CPython
+ x-stainless-runtime-version:
+ - 3.12.9
+ method: POST
+ uri: https://api.openai.com/v1/chat/completions
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAAAwAAAP//jFJdj9MwEHzPr1j8nKC0TejRN4TE3YmTQIAEEpyiPWeTujheYztX0Kn/
+ HTnpNbkPJF4iZWdnPLO7dwmAULXYgJBbDLKzOnu7oFdu//nLr8v8Ij//1jdXq93Xjx8+7a7OzXuR
+ Rgbf7EiGe9ZLyZ3VFBSbEZaOMFBUXazLoszPyqIcgI5r0pHW2pAVnHXKqGyZL4ssX2eLsyN7y0qS
+ Fxv4ngAA3A3f6NPU9FtsIE/vKx15jy2JzakJQDjWsSLQe+UDmiDSCZRsApnB+iUY3oNEA626JUBo
+ o21A4/fkAH6Yd8qghjfD/wYuSGtOYc9O1y/mko6a3mOMZXqtZwAawwHjWIYw10fkcLKvubWOb/wj
+ qmiUUX5bOULPJlr1ga0Y0EMCcD2MqX+QXFjHnQ1V4J80PLco16OemLYzR49g4IB6Vl8v02f0qpoC
+ Ku1ngxYS5ZbqiTptBfta8QxIZqmfunlOe0yuTPs/8hMgJdlAdWUd1Uo+TDy1OYrH+6+205QHw8KT
+ u1WSqqDIxU3U1GCvx5MS/o8P1FWNMi0569R4V42tVgWWBdLrlRTJIfkLAAD//wMAE4F9LmUDAAA=
+ headers:
+ CF-RAY:
+ - 96b0f0eadf69eb2c-SJC
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 06 Aug 2025 19:29:06 GMT
+ Server:
+ - cloudflare
+ Set-Cookie:
+ - __cf_bm=f59gEPi_nA3TTxtjbKaSQpvkTwezaAqOvqfxiGzRnVQ-1754508546-1.0.1.1-JrSaytxVIQSVE00I.vyGj7d4HJbbMV6R9fWPJbkDKu0Y8ueMRzTwTUnfz0YzP5nsZX5oxoE6WlmFxOuz0rRuq9YhZZsO_TbaFBOFk1jGK9U;
+ path=/; expires=Wed, 06-Aug-25 19:59:06 GMT; domain=.api.openai.com; HttpOnly;
+ Secure; SameSite=None
+ - _cfuvid=3D66v3.J_RcVoYy9dlF.jHwq1zTIm842xynZxzSy1Wc-1754508546352-0.0.1.1-604800000;
+ path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ 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:
+ - '504'
+ openai-project:
+ - proj_xitITlrFeen7zjNSzML82h9x
+ openai-version:
+ - '2020-10-01'
+ x-envoy-upstream-service-time:
+ - '527'
+ x-ratelimit-limit-project-tokens:
+ - '150000000'
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-project-tokens:
+ - '149999827'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999830'
+ x-ratelimit-reset-project-tokens:
+ - 0s
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_32abf5c6f27e42579bc84b0bfcc9c4b4
+ status:
+ code: 200
+ message: OK
+- request:
+ body: '{"trace_id": "e3677f76-4763-4f55-94b2-f38707f353c3", "execution_type":
+ "crew", "execution_context": {"crew_fingerprint": null, "crew_name": "crew",
+ "flow_name": "Unknown Flow", "crewai_version": "0.152.0", "privacy_level": "standard"},
+ "execution_metadata": {"expected_duration_estimate": 300, "agent_count": 0,
+ "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-08-06T19:30:52.778875+00:00"}}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '413'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing/batches
+ response:
+ body:
+ string: "\n\n\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:52 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - 8c9c1556-d30f-4736-b62c-5a41150e859f
+ x-runtime:
+ - '0.005329'
+ status:
+ code: 404
+ message: Not Found
+- request:
+ body: '{"version": "0.152.0", "batch_id": "e3677f76-4763-4f55-94b2-f38707f353c3",
+ "user_context": {"user_id": "anonymous", "organization_id": "", "session_id":
+ "eb96086e-c3b3-4757-a118-328be61c9aad", "trace_id": "90245ff6-bd46-4e0e-83da-b12edd241b0e"},
+ "execution_metadata": {"crew_name": "crew", "execution_start": "2025-08-06T19:30:52.777333+00:00",
+ "crewai_version": "0.152.0"}, "events": [{"event_id": "d5c81b9a-b8a9-4638-ab50-aa91792b95c8",
+ "timestamp": "2025-08-06T19:30:52.909777+00:00", "type": "crew_kickoff_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.777333+00:00", "type": "crew_kickoff_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "inputs": null}}, {"event_id": "a5bd314d-f9eb-471f-b3c4-e176e6ec62f9",
+ "timestamp": "2025-08-06T19:30:52.911914+00:00", "type": "task_started", "event_data":
+ {"task_description": "Say hello to the world", "task_name": null, "context":
+ "", "agent": "Test Agent"}}, {"event_id": "ce0e41d9-90a9-4585-8dc3-c04ee02232bc",
+ "timestamp": "2025-08-06T19:30:52.912403+00:00", "type": "agent_execution_started",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionStartedEvent"}}, {"event_id": "d7c3546e-fe60-4c8a-9e4a-a510fa631a8b",
+ "timestamp": "2025-08-06T19:30:52.912693+00:00", "type": "llm_call_started",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.912657+00:00", "type": "llm_call_started",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "e4abe414-b25d-44ea-8a0d-4998d7e55ed3", "agent_id":
+ "91a39492-d0c8-4994-b8b4-acdd256a2e96", "agent_role": "Test Agent", "model":
+ "gpt-4o-mini", "messages": [{"role": "system", "content": "You are Test Agent.
+ Test backstory\nYour personal goal is: Test goal\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\n..."}, {"role": "user", "content": "\nCurrent Task:
+ Say hello to the world\n\nThis is the expected criteria for your final answer:
+ hello world\nyou MUST return the actual complete content as the final answer,
+ not a summary.\n\nBegin! This is ..."}], "tools": null, "callbacks": [""], "available_functions": null}}, {"event_id": "6ed9e994-3e66-4653-97e0-a9e8e8c1d978",
+ "timestamp": "2025-08-06T19:30:52.919664+00:00", "type": "llm_call_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.919623+00:00", "type": "llm_call_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "task_name": null, "task_id": "e4abe414-b25d-44ea-8a0d-4998d7e55ed3", "agent_id":
+ "91a39492-d0c8-4994-b8b4-acdd256a2e96", "agent_role": "Test Agent", "messages":
+ [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal
+ goal is: Test goal\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\n..."},
+ {"role": "user", "content": "\nCurrent Task: Say hello to the world\n\nThis
+ is the expected criteria for your final answer: hello world\nyou MUST return
+ the actual complete content as the final answer, not a summary.\n\nBegin! This
+ is ..."}], "response": "I now can give a great answer \nFinal Answer: Hello,
+ World!", "call_type": "", "response_cost":
+ 3.255e-05, "model": "gpt-4o-mini"}}, {"event_id": "2f03d7fe-2faf-4d6b-a9e1-d3cb9e87ef10",
+ "timestamp": "2025-08-06T19:30:52.919798+00:00", "type": "agent_execution_completed",
+ "event_data": {"serialization_error": "Circular reference detected (id repeated)",
+ "object_type": "AgentExecutionCompletedEvent"}}, {"event_id": "c2ed7fa8-0361-406f-8a0f-4cf0f580dbee",
+ "timestamp": "2025-08-06T19:30:52.919953+00:00", "type": "task_completed", "event_data":
+ {"serialization_error": "Circular reference detected (id repeated)", "object_type":
+ "TaskCompletedEvent"}}, {"event_id": "727d1ea2-4f7b-4d12-b491-42a27f3c3123",
+ "timestamp": "2025-08-06T19:30:52.921547+00:00", "type": "crew_kickoff_completed",
+ "event_data": {"timestamp": "2025-08-06T19:30:52.921522+00:00", "type": "crew_kickoff_completed",
+ "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
+ "crew_name": "crew", "crew": null, "output": {"description": "Say hello to the
+ world", "name": null, "expected_output": "hello world", "summary": "Say hello
+ to the world...", "raw": "Hello, World!", "pydantic": null, "json_dict": null,
+ "agent": "Test Agent", "output_format": "raw"}, "total_tokens": 172}}]}'
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate, zstd
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '4656'
+ Content-Type:
+ - application/json
+ User-Agent:
+ - CrewAI-CLI/0.152.0
+ X-Crewai-Organization-Id:
+ - d3a3d10c-35db-423f-a7a4-c026030ba64d
+ X-Crewai-Version:
+ - 0.152.0
+ method: POST
+ uri: https://app.crewai.com/crewai_plus/api/v1/tracing
+ response:
+ body:
+ string: "\n\n\n The page you were looking
+ for doesn't exist (404)\n \n
+ \ \n\n\n\n \n \n
\n
The
+ page you were looking for doesn't exist.
\n
You may have mistyped
+ the address or the page may have moved.
\n
\n
If you are
+ the application owner check the logs for more information.
\n
\n\n\n"
+ headers:
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '1722'
+ Content-Type:
+ - text/html; charset=UTF-8
+ Date:
+ - Wed, 06 Aug 2025 19:30:53 GMT
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ x-request-id:
+ - 3b16d4bb-ba79-4a32-a776-26bbdf8d0a68
+ x-runtime:
+ - '0.005566'
+ status:
+ code: 404
+ message: Not Found
+version: 1
diff --git a/tests/cassettes/test_gemma3[gemini-gemma-3-12b-it].yaml b/tests/cassettes/test_gemma3[gemini-gemma-3-12b-it].yaml
deleted file mode 100644
index c6ee3390e..000000000
--- a/tests/cassettes/test_gemma3[gemini-gemma-3-12b-it].yaml
+++ /dev/null
@@ -1,59 +0,0 @@
-interactions:
-- request:
- body: '{"contents": [{"role": "user", "parts": [{"text": "What is the capital
- of France?"}]}], "generationConfig": {"stop_sequences": []}}'
- headers:
- accept:
- - '*/*'
- accept-encoding:
- - gzip, deflate
- connection:
- - keep-alive
- content-length:
- - '131'
- content-type:
- - application/json
- host:
- - generativelanguage.googleapis.com
- user-agent:
- - litellm/1.60.2
- method: POST
- uri: https://generativelanguage.googleapis.com/v1beta/models/gemma-3-12b-it:generateContent
- response:
- body:
- string: !!binary |
- H4sIAAAAAAAC/2WRTWvDMAyG7/kVwpdBSEvX7jB23QfsMFa2MAZbD2qipGaOFWwFWkr/+5ykaVPq
- gGP0SvLrR/sIQGVoc52jkFcP8BMiAPtubzW2QlaCMIRCsEYn59x+7UfnkCK0bYtUuiHIsNaCBriA
- F4c2I9Ae4niJTvs4nv7a/nuVGw9oPIOEIoOuJC+QadmBtkNlsAoIpeF1aJgFZ+SgYAfBUQIF+o1m
- m0CJXhxbrnZJV5E1RhpHUzUyeTidV8n5aY4Ntb4rzskM6YchQRXaar/5IPRs27TP9H2pTqq2OW1D
- eBYNF3StVeOxpDcSDJDxhFLVjqtaUv4j+8hNB/m+7zUayYW8mB914QD0QrqbJVdd/VO4U5vxqEZT
- DE9EE+h2Y3r+TtUIg1yYGjB0/1V0BNIz+iLndQ+jpKrCyWJyO19PtKjoEP0DlZtdIF8CAAA=
- headers:
- Alt-Svc:
- - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
- Content-Encoding:
- - gzip
- Content-Type:
- - application/json; charset=UTF-8
- Date:
- - Tue, 22 Apr 2025 14:25:39 GMT
- Server:
- - scaffolding on HTTPServer2
- Server-Timing:
- - gfet4t7; dur=3835
- Transfer-Encoding:
- - chunked
- Vary:
- - Origin
- - X-Origin
- - Referer
- X-Content-Type-Options:
- - nosniff
- X-Frame-Options:
- - SAMEORIGIN
- X-XSS-Protection:
- - '0'
- status:
- code: 200
- message: OK
-version: 1
diff --git a/tests/cassettes/test_gemma3[gemini-gemma-3-1b-it].yaml b/tests/cassettes/test_gemma3[gemini-gemma-3-1b-it].yaml
deleted file mode 100644
index f9306cdfc..000000000
--- a/tests/cassettes/test_gemma3[gemini-gemma-3-1b-it].yaml
+++ /dev/null
@@ -1,60 +0,0 @@
-interactions:
-- request:
- body: '{"contents": [{"role": "user", "parts": [{"text": "What is the capital
- of France?"}]}], "generationConfig": {"stop_sequences": []}}'
- headers:
- accept:
- - '*/*'
- accept-encoding:
- - gzip, deflate
- connection:
- - keep-alive
- content-length:
- - '131'
- content-type:
- - application/json
- host:
- - generativelanguage.googleapis.com
- user-agent:
- - litellm/1.60.2
- method: POST
- uri: https://generativelanguage.googleapis.com/v1beta/models/gemma-3-1b-it:generateContent
- response:
- body:
- string: !!binary |
- H4sIAAAAAAAC/2VRy07DQAy85yusPUZtBSoIxJWHxAFRQYSQKAc3cVqL7DrKuqKlqsRv8Ht8CZuk
- aVOxh314xuP1eBMBmBRdxhkqeXMFbyECsGn2GhOn5DQAXSgES6z0wG3XpncPFKVVnWSSBUGKJSsW
- IDncVehSAvYQxxOs2MfxCKZu6u719/vHA0Iq1ooDyz6UTqlUDi9doELDr1M1aMbiinXcSQ9gtlTg
- VqOGJc855VAztAZWvMInZ1SsoaJU5o6/KANxNDK9X2/39/fBoddKCqobsRLyO/q2I5icHfvFE6EX
- V9Oek8eJ2aPsMlqF8EnUFWikzdLjnB5IMbiOe29NWYktNZEPcteybFy/bLV6MzqCxxc7XCXYcASd
- nQ/+qfqbUJOL/ux6Yw0tYsG6buZ2+5qYng169KnOhuZ8j3aGtB69UOW5NWNO1uJwPDydDVlNtI3+
- AD6XWQdvAgAA
- headers:
- Alt-Svc:
- - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
- Content-Encoding:
- - gzip
- Content-Type:
- - application/json; charset=UTF-8
- Date:
- - Tue, 22 Apr 2025 14:25:32 GMT
- Server:
- - scaffolding on HTTPServer2
- Server-Timing:
- - gfet4t7; dur=1535
- Transfer-Encoding:
- - chunked
- Vary:
- - Origin
- - X-Origin
- - Referer
- X-Content-Type-Options:
- - nosniff
- X-Frame-Options:
- - SAMEORIGIN
- X-XSS-Protection:
- - '0'
- status:
- code: 200
- message: OK
-version: 1
diff --git a/tests/cassettes/test_gemma3[gemini-gemma-3-27b-it].yaml b/tests/cassettes/test_gemma3[gemini-gemma-3-27b-it].yaml
index 0215b0a40..496a3a055 100644
--- a/tests/cassettes/test_gemma3[gemini-gemma-3-27b-it].yaml
+++ b/tests/cassettes/test_gemma3[gemini-gemma-3-27b-it].yaml
@@ -6,7 +6,7 @@ interactions:
accept:
- '*/*'
accept-encoding:
- - gzip, deflate
+ - gzip, deflate, zstd
connection:
- keep-alive
content-length:
@@ -16,19 +16,19 @@ interactions:
host:
- generativelanguage.googleapis.com
user-agent:
- - litellm/1.60.2
+ - litellm/1.74.9
method: POST
uri: https://generativelanguage.googleapis.com/v1beta/models/gemma-3-27b-it:generateContent
response:
body:
string: !!binary |
- H4sIAAAAAAAC/2VRXUvDMBR976+45EUo3RDnUHwTnSA4HFpEcHuI7e16aZqU5NZNxv67abtuHTbQ
- hHPu5zm7AEAkUqeUSkYn7uDLIwC79t9wRjNq9kQPebCSlk+x3bcbvH0I47ZJEnGOkMiKWCowGTxZ
- qRMEchCGC2nJheEYlnqpn/nCQaHNRkNmLJDvSwkoP1kpbeFAUYHAvtiMsgwVxGaDNmqRF1P/WIR5
- 7bAuI/ApLXxvE0gRYkumrHL0hIMNKtXcxA4y6XIyOoKkJkcau8ykVlxbHDczNUcM1tof36voJIY1
- CptNS5Oi6sP3fYDISJPL31A6o5uw9/h1IY4s6RS3Hr4M+gZtaVE7ucY5svS2yKP4orJ+F45NgfrB
- 1K0tt12tgYln9PX0wLPxFpxR00n0r6p79D1JDc0d+O5XlIr4tzV29hmLgQx8NlQvQ3uvgoMgnUYf
- aB11YqyxLOVoMrq6+R4Ri2Af/AEDrXcbkQIAAA==
+ H4sIAAAAAAAC/21RwWrbQBC96yuGvRSMFYp7aOktxAnY1MQ0ahtIfJhII3vwalfZGdUpxv/elRQ5
+ CkQL2uXNm7f75h0TAJOjK7hAJTHf4SEiAMfu39a8U3IaCwMUwRqDvnH77zg6R4rSS9tksh1BjjUr
+ WvAl3AR0OQELTCZrDCyTyQU8uke30E8Ce+cPDkofgOO9nIONL6sw7AUs7wk0il1zWZKFzB8oTDvk
+ h2/+BoJVI9RUU4gtHXwZcigIssC+qncUCwIHsrbdWQVKlB17N4W8YWFHfWfeWG0CXbRvapcZ2Tqd
+ z5vp2zCCt9Q6rXxBdqCfBoIp2bHsfhKKdy3tLrtdm3OVXUEvEf6cDBd00qYR3NKKFGMseB6+qUP0
+ opnfk7vyTRfLt17LqI8j/rAyapJ5lGQ7zm4Ua3SAlvVfl9v1fWZGLvWd8uCy2zfJq99+BL8pCPde
+ t1RVmH5JZ1+fUtZOzgSS2juhRdEylvgnw+VTMU/T5bPKmos7nV3+Mskp+Q+x/LCbmwIAAA==
headers:
Alt-Svc:
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
@@ -37,11 +37,11 @@ interactions:
Content-Type:
- application/json; charset=UTF-8
Date:
- - Tue, 22 Apr 2025 14:25:41 GMT
+ - Wed, 06 Aug 2025 18:55:33 GMT
Server:
- scaffolding on HTTPServer2
Server-Timing:
- - gfet4t7; dur=2447
+ - gfet4t7; dur=1529
Transfer-Encoding:
- chunked
Vary:
diff --git a/tests/cassettes/test_gemma3[gemini-gemma-3-4b-it].yaml b/tests/cassettes/test_gemma3[gemini-gemma-3-4b-it].yaml
deleted file mode 100644
index d5680b6d2..000000000
--- a/tests/cassettes/test_gemma3[gemini-gemma-3-4b-it].yaml
+++ /dev/null
@@ -1,60 +0,0 @@
-interactions:
-- request:
- body: '{"contents": [{"role": "user", "parts": [{"text": "What is the capital
- of France?"}]}], "generationConfig": {"stop_sequences": []}}'
- headers:
- accept:
- - '*/*'
- accept-encoding:
- - gzip, deflate
- connection:
- - keep-alive
- content-length:
- - '131'
- content-type:
- - application/json
- host:
- - generativelanguage.googleapis.com
- user-agent:
- - litellm/1.60.2
- method: POST
- uri: https://generativelanguage.googleapis.com/v1beta/models/gemma-3-4b-it:generateContent
- response:
- body:
- string: !!binary |
- H4sIAAAAAAAC/2WRzUrDQBDH73mKYY+lLUKLiBcPfoAHsWhQwXqYJtNk6WYn7E6woRQ8+wR68t18
- Ah/BbWraFPeQXeb/z3z8ZhUBqARtqlMU8uoUnkMEYNV8NxpbIStBaEMhWKKTvXd7Vp13sAgtNz+p
- OCdIsNSCBngOVw5tQqA99HoTdNr3ekOY2qm9lu+3Tw8ImeFZ8CahKDmYs4NQrA9z9Llm24cMvTi2
- XNQQ2oakMlI5GsLP18d7k+mRK5NCzRUYvSAQhoXl12CuJdc2g4IdAc64Emg6OFOdzte790t/P69j
- Q5thCk7JtPZ1a1BzbbXP7wg9243tPr6dqJ2qbUrLED6K2gJNalV5zOiGBAN53PFVpeOilJgXZM+5
- asifbHN19nQgj1pdOFA+kMbH/X9Z/UWoqU13f53VhhHRaKmb3V0+xaqDQQ6aajE090v0B2TL6IGc
- 11sYGRUFDkaD8WygRUXr6BcxmBLccwIAAA==
- headers:
- Alt-Svc:
- - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
- Content-Encoding:
- - gzip
- Content-Type:
- - application/json; charset=UTF-8
- Date:
- - Tue, 22 Apr 2025 14:25:35 GMT
- Server:
- - scaffolding on HTTPServer2
- Server-Timing:
- - gfet4t7; dur=2349
- Transfer-Encoding:
- - chunked
- Vary:
- - Origin
- - X-Origin
- - Referer
- X-Content-Type-Options:
- - nosniff
- X-Frame-Options:
- - SAMEORIGIN
- X-XSS-Protection:
- - '0'
- status:
- code: 200
- message: OK
-version: 1
diff --git a/tests/llm_test.py b/tests/llm_test.py
index fb78c4774..20f1d8108 100644
--- a/tests/llm_test.py
+++ b/tests/llm_test.py
@@ -282,9 +282,6 @@ def test_gemini_models(model):
@pytest.mark.parametrize(
"model",
[
- "gemini/gemma-3-1b-it",
- "gemini/gemma-3-4b-it",
- "gemini/gemma-3-12b-it",
"gemini/gemma-3-27b-it",
],
)
@@ -377,6 +374,7 @@ def get_weather_tool_schema():
},
}
+
def test_context_window_exceeded_error_handling():
"""Test that litellm.ContextWindowExceededError is converted to LLMContextLengthExceededException."""
from litellm.exceptions import ContextWindowExceededError
@@ -392,7 +390,7 @@ def test_context_window_exceeded_error_handling():
mock_completion.side_effect = ContextWindowExceededError(
"This model's maximum context length is 8192 tokens. However, your messages resulted in 10000 tokens.",
model="gpt-4",
- llm_provider="openai"
+ llm_provider="openai",
)
with pytest.raises(LLMContextLengthExceededException) as excinfo:
@@ -407,7 +405,7 @@ def test_context_window_exceeded_error_handling():
mock_completion.side_effect = ContextWindowExceededError(
"This model's maximum context length is 8192 tokens. However, your messages resulted in 10000 tokens.",
model="gpt-4",
- llm_provider="openai"
+ llm_provider="openai",
)
with pytest.raises(LLMContextLengthExceededException) as excinfo:
@@ -598,6 +596,7 @@ def test_handle_streaming_tool_calls(get_weather_tool_schema, mock_emit):
expected_final_chunk_result=expected_final_chunk_result,
)
+
@pytest.mark.vcr(filter_headers=["authorization"])
def test_handle_streaming_tool_calls_with_error(get_weather_tool_schema, mock_emit):
def get_weather_error(location):
@@ -609,9 +608,7 @@ def test_handle_streaming_tool_calls_with_error(get_weather_tool_schema, mock_em
{"role": "user", "content": "What is the weather in New York?"},
],
tools=[get_weather_tool_schema],
- available_functions={
- "get_weather": get_weather_error
- },
+ available_functions={"get_weather": get_weather_error},
)
assert response == ""
expected_final_chunk_result = '{"location":"New York, NY"}'
@@ -676,8 +673,11 @@ def test_llm_call_when_stop_is_unsupported(caplog):
assert isinstance(result, str)
assert "Paris" in result
+
@pytest.mark.vcr(filter_headers=["authorization"])
-def test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provided(caplog):
+def test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provided(
+ caplog,
+):
llm = LLM(model="o1-mini", stop=["stop"], additional_drop_params=["another_param"])
with caplog.at_level(logging.INFO):
result = llm.call("What is the capital of France?")
@@ -690,6 +690,7 @@ def test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provid
def ollama_llm():
return LLM(model="ollama/llama3.2:3b")
+
def test_ollama_appends_dummy_user_message_when_last_is_assistant(ollama_llm):
original_messages = [
{"role": "user", "content": "Hi there"},
diff --git a/tests/tracing/test_tracing.py b/tests/tracing/test_tracing.py
new file mode 100644
index 000000000..03fc3ddb5
--- /dev/null
+++ b/tests/tracing/test_tracing.py
@@ -0,0 +1,313 @@
+import os
+import pytest
+from unittest.mock import patch, MagicMock
+
+# Remove the module-level patch
+from crewai import Agent, Task, Crew
+from crewai.utilities.events.listeners.tracing.trace_listener import (
+ TraceCollectionListener,
+)
+from crewai.utilities.events.listeners.tracing.trace_batch_manager import (
+ TraceBatchManager,
+)
+from crewai.utilities.events.listeners.tracing.types import TraceEvent
+
+
+class TestTraceListenerSetup:
+ """Test TraceListener is properly setup and collecting events"""
+
+ @pytest.fixture(autouse=True)
+ def mock_auth_token(self):
+ """Mock authentication token for all tests in this class"""
+ # Need to patch all the places where get_auth_token is imported/used
+ with (
+ patch(
+ "crewai.cli.authentication.token.get_auth_token",
+ return_value="mock_token_12345",
+ ),
+ patch(
+ "crewai.utilities.events.listeners.tracing.trace_listener.get_auth_token",
+ return_value="mock_token_12345",
+ ),
+ patch(
+ "crewai.utilities.events.listeners.tracing.trace_batch_manager.get_auth_token",
+ return_value="mock_token_12345",
+ ),
+ patch(
+ "crewai.utilities.events.listeners.tracing.interfaces.get_auth_token",
+ return_value="mock_token_12345",
+ ),
+ ):
+ yield
+
+ @pytest.fixture(autouse=True)
+ def clear_event_bus(self):
+ """Clear event bus listeners before and after each test"""
+ from crewai.utilities.events import crewai_event_bus
+
+ # Store original handlers
+ original_handlers = crewai_event_bus._handlers.copy()
+
+ # Clear for test
+ crewai_event_bus._handlers.clear()
+
+ yield
+
+ # Restore original state
+ crewai_event_bus._handlers.clear()
+ crewai_event_bus._handlers.update(original_handlers)
+
+ @pytest.fixture(autouse=True)
+ def reset_tracing_singletons(self):
+ """Reset tracing singleton instances between tests"""
+ # Reset TraceCollectionListener singleton
+ if hasattr(TraceCollectionListener, "_instance"):
+ TraceCollectionListener._instance = None
+ TraceCollectionListener._initialized = False
+
+ yield
+
+ # Clean up after test
+ if hasattr(TraceCollectionListener, "_instance"):
+ TraceCollectionListener._instance = None
+ TraceCollectionListener._initialized = False
+
+ @pytest.fixture(autouse=True)
+ def mock_plus_api_calls(self):
+ """Mock all PlusAPI HTTP calls to avoid network requests"""
+ with (
+ patch("requests.post") as mock_post,
+ patch("requests.get") as mock_get,
+ patch("requests.put") as mock_put,
+ patch("requests.delete") as mock_delete,
+ patch.object(TraceBatchManager, "initialize_batch", return_value=None),
+ patch.object(
+ TraceBatchManager, "_finalize_backend_batch", return_value=True
+ ),
+ patch.object(TraceBatchManager, "_cleanup_batch_data", return_value=True),
+ ):
+ mock_response = MagicMock()
+ mock_response.status_code = 200
+ mock_response.json.return_value = {
+ "id": "mock_trace_batch_id",
+ "status": "success",
+ "message": "Batch created successfully",
+ }
+ mock_response.raise_for_status.return_value = None
+
+ mock_post.return_value = mock_response
+ mock_get.return_value = mock_response
+ mock_put.return_value = mock_response
+ mock_delete.return_value = mock_response
+
+ yield {
+ "post": mock_post,
+ "get": mock_get,
+ "put": mock_put,
+ "delete": mock_delete,
+ }
+
+ @pytest.mark.vcr(filter_headers=["authorization"])
+ def test_trace_listener_collects_crew_events(self):
+ """Test that trace listener properly collects events from crew execution"""
+
+ with patch.dict(os.environ, {"CREWAI_TRACING_ENABLED": "true"}):
+ agent = Agent(
+ role="Test Agent",
+ goal="Test goal",
+ backstory="Test backstory",
+ llm="gpt-4o-mini",
+ )
+ task = Task(
+ description="Say hello to the world",
+ expected_output="hello world",
+ agent=agent,
+ )
+ crew = Crew(agents=[agent], tasks=[task], verbose=True)
+
+ trace_listener = TraceCollectionListener()
+ from crewai.utilities.events import crewai_event_bus
+
+ trace_listener.setup_listeners(crewai_event_bus)
+
+ with patch.object(
+ trace_listener.batch_manager,
+ "initialize_batch",
+ return_value=None,
+ ) as initialize_mock:
+ crew.kickoff()
+
+ assert initialize_mock.call_count >= 1
+
+ call_args = initialize_mock.call_args_list[0]
+ assert len(call_args[0]) == 2 # user_context, execution_metadata
+ _, execution_metadata = call_args[0]
+ assert isinstance(execution_metadata, dict)
+ assert "crew_name" in execution_metadata
+
+ @pytest.mark.vcr(filter_headers=["authorization"])
+ def test_batch_manager_finalizes_batch_clears_buffer(self):
+ """Test that batch manager properly finalizes batch and clears buffer"""
+
+ with patch.dict(os.environ, {"CREWAI_TRACING_ENABLED": "true"}):
+ agent = Agent(
+ role="Test Agent",
+ goal="Test goal",
+ backstory="Test backstory",
+ llm="gpt-4o-mini",
+ )
+
+ task = Task(
+ description="Say hello to the world",
+ expected_output="hello world",
+ agent=agent,
+ )
+
+ crew = Crew(agents=[agent], tasks=[task], verbose=True)
+
+ from crewai.utilities.events import crewai_event_bus
+
+ trace_listener = None
+ for handler_list in crewai_event_bus._handlers.values():
+ for handler in handler_list:
+ if hasattr(handler, "__self__") and isinstance(
+ handler.__self__, TraceCollectionListener
+ ):
+ trace_listener = handler.__self__
+ break
+ if trace_listener:
+ break
+
+ if not trace_listener:
+ pytest.skip(
+ "No trace listener found - tracing may not be properly enabled"
+ )
+
+ with patch.object(
+ trace_listener.batch_manager,
+ "finalize_batch",
+ wraps=trace_listener.batch_manager.finalize_batch,
+ ) as finalize_mock:
+ crew.kickoff()
+
+ assert finalize_mock.call_count >= 1
+
+ @pytest.mark.vcr(filter_headers=["authorization"])
+ def test_events_collection_batch_manager(self, mock_plus_api_calls):
+ """Test that trace listener properly collects events from crew execution"""
+
+ with patch.dict(os.environ, {"CREWAI_TRACING_ENABLED": "true"}):
+ agent = Agent(
+ role="Test Agent",
+ goal="Test goal",
+ backstory="Test backstory",
+ llm="gpt-4o-mini",
+ )
+ task = Task(
+ description="Say hello to the world",
+ expected_output="hello world",
+ agent=agent,
+ )
+ crew = Crew(agents=[agent], tasks=[task], verbose=True)
+
+ from crewai.utilities.events import crewai_event_bus
+
+ # Create and setup trace listener explicitly
+ trace_listener = TraceCollectionListener()
+ trace_listener.setup_listeners(crewai_event_bus)
+
+ with patch.object(
+ trace_listener.batch_manager,
+ "add_event",
+ wraps=trace_listener.batch_manager.add_event,
+ ) as add_event_mock:
+ crew.kickoff()
+
+ assert add_event_mock.call_count >= 2
+
+ completion_events = [
+ call.args[0]
+ for call in add_event_mock.call_args_list
+ if call.args[0].type == "crew_kickoff_completed"
+ ]
+ assert len(completion_events) >= 1
+
+ # Verify the first completion event has proper structure
+ completion_event = completion_events[0]
+ assert "crew_name" in completion_event.event_data
+ assert completion_event.event_data["crew_name"] == "crew"
+
+ # Verify all events have proper structure
+ for call in add_event_mock.call_args_list:
+ event = call.args[0]
+ assert isinstance(event, TraceEvent)
+ assert hasattr(event, "event_data")
+ assert hasattr(event, "type")
+
+ @pytest.mark.vcr(filter_headers=["authorization"])
+ def test_trace_listener_disabled_when_env_false(self):
+ """Test that trace listener doesn't make HTTP calls when tracing is disabled"""
+
+ with patch.dict(os.environ, {"CREWAI_TRACING_ENABLED": "false"}):
+ agent = Agent(
+ role="Test Agent",
+ goal="Test goal",
+ backstory="Test backstory",
+ llm="gpt-4o-mini",
+ )
+ task = Task(
+ description="Say hello to the world",
+ expected_output="hello world",
+ agent=agent,
+ )
+
+ crew = Crew(agents=[agent], tasks=[task], verbose=True)
+ result = crew.kickoff()
+ assert result is not None
+
+ from crewai.utilities.events import crewai_event_bus
+
+ trace_handlers = []
+ for handlers in crewai_event_bus._handlers.values():
+ for handler in handlers:
+ if hasattr(handler, "__self__") and isinstance(
+ handler.__self__, TraceCollectionListener
+ ):
+ trace_handlers.append(handler)
+ elif hasattr(handler, "__name__") and any(
+ trace_name in handler.__name__
+ for trace_name in [
+ "on_crew_started",
+ "on_crew_completed",
+ "on_flow_started",
+ ]
+ ):
+ trace_handlers.append(handler)
+
+ assert len(trace_handlers) == 0, (
+ f"Found {len(trace_handlers)} trace handlers when tracing should be disabled"
+ )
+
+ def test_trace_listener_setup_correctly(self):
+ """Test that trace listener is set up correctly when enabled"""
+
+ with patch.dict(os.environ, {"CREWAI_TRACING_ENABLED": "true"}):
+ trace_listener = TraceCollectionListener()
+
+ assert trace_listener.trace_enabled is True
+ assert trace_listener.batch_manager is not None
+ assert trace_listener.trace_sender is not None
+
+ # Helper method to ensure cleanup
+ def teardown_method(self):
+ """Cleanup after each test method"""
+ from crewai.utilities.events import crewai_event_bus
+
+ crewai_event_bus._handlers.clear()
+
+ @classmethod
+ def teardown_class(cls):
+ """Final cleanup after all tests in this class"""
+ from crewai.utilities.events import crewai_event_bus
+
+ crewai_event_bus._handlers.clear()