fix: ensure instrumentation flags

This commit is contained in:
Greyson LaLonde
2025-11-15 20:48:40 -05:00
committed by GitHub
parent d7bdac12a2
commit b546982690
24 changed files with 4035 additions and 1560 deletions

View File

@@ -493,5 +493,206 @@ def config_reset():
config_command.reset_all_settings()
@crewai.group()
def env():
"""Environment variable commands."""
@env.command("view")
def env_view():
"""View tracing-related environment variables."""
import os
from pathlib import Path
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
console = Console()
# Check for .env file
env_file = Path(".env")
env_file_exists = env_file.exists()
# Create table for environment variables
table = Table(show_header=True, header_style="bold cyan", expand=True)
table.add_column("Environment Variable", style="cyan", width=30)
table.add_column("Value", style="white", width=20)
table.add_column("Source", style="yellow", width=20)
# Check CREWAI_TRACING_ENABLED
crewai_tracing = os.getenv("CREWAI_TRACING_ENABLED", "")
if crewai_tracing:
table.add_row(
"CREWAI_TRACING_ENABLED",
crewai_tracing,
"Environment/Shell",
)
else:
table.add_row(
"CREWAI_TRACING_ENABLED",
"[dim]Not set[/dim]",
"[dim]—[/dim]",
)
# Check other related env vars
crewai_testing = os.getenv("CREWAI_TESTING", "")
if crewai_testing:
table.add_row("CREWAI_TESTING", crewai_testing, "Environment/Shell")
crewai_user_id = os.getenv("CREWAI_USER_ID", "")
if crewai_user_id:
table.add_row("CREWAI_USER_ID", crewai_user_id, "Environment/Shell")
crewai_org_id = os.getenv("CREWAI_ORG_ID", "")
if crewai_org_id:
table.add_row("CREWAI_ORG_ID", crewai_org_id, "Environment/Shell")
# Check if .env file exists
table.add_row(
".env file",
"✅ Found" if env_file_exists else "❌ Not found",
str(env_file.resolve()) if env_file_exists else "N/A",
)
panel = Panel(
table,
title="Tracing Environment Variables",
border_style="blue",
padding=(1, 2),
)
console.print("\n")
console.print(panel)
# Show helpful message
if env_file_exists:
console.print(
"\n[dim]💡 Tip: To enable tracing via .env, add: CREWAI_TRACING_ENABLED=true[/dim]"
)
else:
console.print(
"\n[dim]💡 Tip: Create a .env file in your project root and add: CREWAI_TRACING_ENABLED=true[/dim]"
)
console.print()
@crewai.group()
def traces():
"""Trace collection management commands."""
@traces.command("enable")
def traces_enable():
"""Enable trace collection for crew/flow executions."""
from rich.console import Console
from rich.panel import Panel
from crewai.events.listeners.tracing.utils import (
_load_user_data,
_save_user_data,
)
console = Console()
# Update user data to enable traces
user_data = _load_user_data()
user_data["trace_consent"] = True
user_data["first_execution_done"] = True
_save_user_data(user_data)
panel = Panel(
"✅ Trace collection has been enabled!\n\n"
"Your crew/flow executions will now send traces to CrewAI+.\n"
"Use 'crewai traces disable' to turn off trace collection.",
title="Traces Enabled",
border_style="green",
padding=(1, 2),
)
console.print(panel)
@traces.command("disable")
def traces_disable():
"""Disable trace collection for crew/flow executions."""
from rich.console import Console
from rich.panel import Panel
from crewai.events.listeners.tracing.utils import (
_load_user_data,
_save_user_data,
)
console = Console()
# Update user data to disable traces
user_data = _load_user_data()
user_data["trace_consent"] = False
user_data["first_execution_done"] = True
_save_user_data(user_data)
panel = Panel(
"❌ Trace collection has been disabled!\n\n"
"Your crew/flow executions will no longer send traces.\n"
"Use 'crewai traces enable' to turn trace collection back on.",
title="Traces Disabled",
border_style="red",
padding=(1, 2),
)
console.print(panel)
@traces.command("status")
def traces_status():
"""Show current trace collection status."""
import os
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from crewai.events.listeners.tracing.utils import (
_load_user_data,
is_tracing_enabled,
)
console = Console()
user_data = _load_user_data()
table = Table(show_header=False, box=None)
table.add_column("Setting", style="cyan")
table.add_column("Value", style="white")
# Check environment variable
env_enabled = os.getenv("CREWAI_TRACING_ENABLED", "false")
table.add_row("CREWAI_TRACING_ENABLED", env_enabled)
# Check user consent
trace_consent = user_data.get("trace_consent")
if trace_consent is True:
consent_status = "✅ Enabled (user consented)"
elif trace_consent is False:
consent_status = "❌ Disabled (user declined)"
else:
consent_status = "⚪ Not set (first-time user)"
table.add_row("User Consent", consent_status)
# Check overall status
if is_tracing_enabled():
overall_status = "✅ ENABLED"
border_style = "green"
else:
overall_status = "❌ DISABLED"
border_style = "red"
table.add_row("Overall Status", overall_status)
panel = Panel(
table,
title="Trace Collection Status",
border_style=border_style,
padding=(1, 2),
)
console.print(panel)
if __name__ == "__main__":
crewai()

View File

@@ -1,3 +1,5 @@
from datetime import datetime
import os
from typing import Any
from rich.console import Console
@@ -5,6 +7,7 @@ from rich.table import Table
from crewai.cli.command import BaseCommand
from crewai.cli.config import HIDDEN_SETTINGS_KEYS, READONLY_SETTINGS_KEYS, Settings
from crewai.events.listeners.tracing.utils import _load_user_data
console = Console()
@@ -39,6 +42,42 @@ class SettingsCommand(BaseCommand):
table.add_row(field_name, display_value, description)
# Add trace-related settings from user data
user_data = _load_user_data()
# CREWAI_TRACING_ENABLED environment variable
env_tracing = os.getenv("CREWAI_TRACING_ENABLED", "")
env_tracing_display = env_tracing if env_tracing else "Not set"
table.add_row(
"CREWAI_TRACING_ENABLED",
env_tracing_display,
"Environment variable to enable/disable tracing",
)
# Trace consent status
trace_consent = user_data.get("trace_consent")
if trace_consent is True:
consent_display = "✅ Enabled"
elif trace_consent is False:
consent_display = "❌ Disabled"
else:
consent_display = "Not set"
table.add_row(
"trace_consent", consent_display, "Whether trace collection is enabled"
)
# First execution timestamp
if user_data.get("first_execution_at"):
timestamp = datetime.fromtimestamp(user_data["first_execution_at"])
first_exec_display = timestamp.strftime("%Y-%m-%d %H:%M:%S")
else:
first_exec_display = "Not set"
table.add_row(
"first_execution_at",
first_exec_display,
"Timestamp of first crew/flow execution",
)
console.print(table)
def set(self, key: str, value: str) -> None:

View File

@@ -27,6 +27,8 @@ from pydantic import (
model_validator,
)
from pydantic_core import PydanticCustomError
from rich.console import Console
from rich.panel import Panel
from typing_extensions import Self
from crewai.agent import Agent
@@ -39,8 +41,8 @@ from crewai.events.listeners.tracing.trace_listener import (
TraceCollectionListener,
)
from crewai.events.listeners.tracing.utils import (
is_tracing_enabled,
should_auto_collect_first_time_traces,
set_tracing_enabled,
should_enable_tracing,
)
from crewai.events.types.crew_events import (
CrewKickoffCompletedEvent,
@@ -280,8 +282,8 @@ class Crew(FlowTrackable, BaseModel):
description="Metrics for the LLM usage during all tasks execution.",
)
tracing: bool | None = Field(
default=False,
description="Whether to enable tracing for the crew.",
default=None,
description="Whether to enable tracing for the crew. True=always enable, False=always disable, None=check environment/user settings.",
)
@field_validator("id", mode="before")
@@ -311,17 +313,16 @@ class Crew(FlowTrackable, BaseModel):
@model_validator(mode="after")
def set_private_attrs(self) -> Crew:
"""set private attributes."""
self._cache_handler = CacheHandler()
event_listener = EventListener() # type: ignore[no-untyped-call]
if (
is_tracing_enabled()
or self.tracing
or should_auto_collect_first_time_traces()
):
trace_listener = TraceCollectionListener()
trace_listener.setup_listeners(crewai_event_bus)
# Determine and set tracing state once for this execution
tracing_enabled = should_enable_tracing(override=self.tracing)
set_tracing_enabled(tracing_enabled)
# Always setup trace listener - actual execution control is via contextvar
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)
@@ -1171,6 +1172,10 @@ class Crew(FlowTrackable, BaseModel):
total_tokens=self.token_usage.total_tokens,
),
)
# Finalization is handled by trace listener (always initialized)
# The batch manager checks contextvar to determine if tracing is enabled
return CrewOutput(
raw=final_task_output.raw,
pydantic=final_task_output.pydantic,
@@ -1651,3 +1656,32 @@ class Crew(FlowTrackable, BaseModel):
and able_to_inject
):
self.tasks[0].allow_crewai_trigger_context = True
def _show_tracing_disabled_message(self) -> None:
"""Show a message when tracing is disabled."""
from crewai.events.listeners.tracing.utils import has_user_declined_tracing
console = Console()
if has_user_declined_tracing():
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Crew code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
else:
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Crew code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
panel = Panel(
message,
title="Tracing Status",
border_style="blue",
padding=(1, 2),
)
console.print(panel)

View File

@@ -10,6 +10,7 @@ import atexit
from collections.abc import Callable, Generator
from concurrent.futures import Future, ThreadPoolExecutor
from contextlib import contextmanager
import contextvars
import threading
from typing import Any, Final, ParamSpec, TypeVar
@@ -288,8 +289,9 @@ class CrewAIEventsBus:
if event_type is LLMStreamChunkEvent:
self._call_handlers(source, event, level_sync)
else:
ctx = contextvars.copy_context()
future = self._sync_executor.submit(
self._call_handlers, source, event, level_sync
ctx.run, self._call_handlers, source, event, level_sync
)
await asyncio.get_running_loop().run_in_executor(
None, future.result
@@ -346,8 +348,9 @@ class CrewAIEventsBus:
if event_type is LLMStreamChunkEvent:
self._call_handlers(source, event, sync_handlers)
else:
ctx = contextvars.copy_context()
sync_future = self._sync_executor.submit(
self._call_handlers, source, event, sync_handlers
ctx.run, self._call_handlers, source, event, sync_handlers
)
if not async_handlers:
return sync_future

View File

@@ -1,5 +1,4 @@
import logging
from pathlib import Path
import uuid
import webbrowser
@@ -17,47 +16,6 @@ from crewai.events.listeners.tracing.utils import (
logger = logging.getLogger(__name__)
def _update_or_create_env_file():
"""Update or create .env file with CREWAI_TRACING_ENABLED=true."""
env_path = Path(".env")
env_content = ""
variable_name = "CREWAI_TRACING_ENABLED"
variable_value = "true"
# Read existing content if file exists
if env_path.exists():
with open(env_path, "r") as f:
env_content = f.read()
# Check if CREWAI_TRACING_ENABLED is already set
lines = env_content.splitlines()
variable_exists = False
updated_lines = []
for line in lines:
if line.strip().startswith(f"{variable_name}="):
# Update existing variable
updated_lines.append(f"{variable_name}={variable_value}")
variable_exists = True
else:
updated_lines.append(line)
# Add variable if it doesn't exist
if not variable_exists:
if updated_lines and not updated_lines[-1].strip():
# If last line is empty, replace it
updated_lines[-1] = f"{variable_name}={variable_value}"
else:
# Add new line and then the variable
updated_lines.append(f"{variable_name}={variable_value}")
# Write updated content
with open(env_path, "w") as f:
f.write("\n".join(updated_lines))
if updated_lines: # Add final newline if there's content
f.write("\n")
class FirstTimeTraceHandler:
"""Handles the first-time user trace collection and display flow."""
@@ -96,20 +54,16 @@ class FirstTimeTraceHandler:
if user_wants_traces:
self._initialize_backend_and_send_events()
# Enable tracing for future runs by updating .env file
try:
_update_or_create_env_file()
except Exception: # noqa: S110
pass
if self.ephemeral_url:
self._display_ephemeral_trace_link()
else:
self._show_tracing_declined_message()
mark_first_execution_completed()
mark_first_execution_completed(user_consented=user_wants_traces)
except Exception as e:
self._gracefully_fail(f"Error in trace handling: {e}")
mark_first_execution_completed()
mark_first_execution_completed(user_consented=False)
def _initialize_backend_and_send_events(self):
"""Initialize backend batch and send collected events."""
@@ -182,8 +136,13 @@ This trace shows:
• Tool usage and results
• LLM calls and responses
✅ Tracing has been enabled for future runs! (CREWAI_TRACING_ENABLED=true added to .env)
You can also add tracing=True to your Crew(tracing=True) / Flow(tracing=True) for more control.
✅ Tracing has been enabled for future runs!
Your preference has been saved. Future Crew/Flow executions will automatically collect traces.
To disable tracing later, do any one of these:
• Set tracing=False in your Crew/Flow code
• Set CREWAI_TRACING_ENABLED=false in your project's .env file
• Run: crewai traces disable
📝 Note: This link will expire in 24 hours.
""".strip()
@@ -199,6 +158,32 @@ You can also add tracing=True to your Crew(tracing=True) / Flow(tracing=True) fo
console.print(panel)
console.print()
def _show_tracing_declined_message(self):
"""Show message when user declines tracing."""
console = Console()
panel_content = """
Info: Tracing has been disabled.
Your preference has been saved. Future Crew/Flow executions will not collect traces.
To enable tracing later, do any one of these:
• Set tracing=True in your Crew/Flow code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable
""".strip()
panel = Panel(
panel_content,
title="Tracing Preference Saved",
border_style="blue",
padding=(1, 2),
)
console.print("\n")
console.print(panel)
console.print()
def _gracefully_fail(self, error_message: str):
"""Handle errors gracefully without disrupting user experience."""
console = Console()
@@ -218,8 +203,14 @@ Unfortunately, we couldn't upload them to the server right now, but here's what
• Execution duration: {self.batch_manager.calculate_duration("execution")}ms
• Batch ID: {self.batch_manager.trace_batch_id}
Tracing has been enabled for future runs! (CREWAI_TRACING_ENABLED=true added to .env)
Tracing has been enabled for future runs!
Your preference has been saved. Future Crew/Flow executions will automatically collect traces.
The traces include agent decisions, task execution, and tool usage.
To disable tracing later, do any one of these:
• Set tracing=False in your Crew/Flow code
• Set CREWAI_TRACING_ENABLED=false in your project's .env file
• Run: crewai traces disable
""".strip()
panel = Panel(

View File

@@ -12,7 +12,10 @@ from crewai.cli.authentication.token import AuthError, get_auth_token
from crewai.cli.plus_api import PlusAPI
from crewai.cli.version import get_crewai_version
from crewai.events.listeners.tracing.types import TraceEvent
from crewai.events.listeners.tracing.utils import should_auto_collect_first_time_traces
from crewai.events.listeners.tracing.utils import (
is_tracing_enabled_in_context,
should_auto_collect_first_time_traces,
)
from crewai.utilities.constants import CREWAI_BASE_URL
@@ -107,6 +110,9 @@ class TraceBatchManager:
):
"""Send batch initialization to backend"""
if not is_tracing_enabled_in_context():
return
if not self.plus_api or not self.current_batch:
return
@@ -243,7 +249,8 @@ class TraceBatchManager:
def finalize_batch(self) -> TraceBatch | None:
"""Finalize batch and return it for sending"""
if not self.current_batch:
if not self.current_batch or not is_tracing_enabled_in_context():
return None
all_handlers_completed = self.wait_for_pending_events()

View File

@@ -10,13 +10,14 @@ from crewai.cli.authentication.token import AuthError, get_auth_token
from crewai.cli.version import get_crewai_version
from crewai.events.base_event_listener import BaseEventListener
from crewai.events.event_bus import CrewAIEventsBus
from crewai.events.utils.console_formatter import ConsoleFormatter
from crewai.events.listeners.tracing.first_time_trace_handler import (
FirstTimeTraceHandler,
)
from crewai.events.listeners.tracing.trace_batch_manager import TraceBatchManager
from crewai.events.listeners.tracing.types import TraceEvent
from crewai.events.listeners.tracing.utils import safe_serialize_to_dict
from crewai.events.listeners.tracing.utils import (
safe_serialize_to_dict,
)
from crewai.events.types.agent_events import (
AgentExecutionCompletedEvent,
AgentExecutionErrorEvent,
@@ -80,6 +81,7 @@ from crewai.events.types.tool_usage_events import (
ToolUsageFinishedEvent,
ToolUsageStartedEvent,
)
from crewai.events.utils.console_formatter import ConsoleFormatter
class TraceCollectionListener(BaseEventListener):
@@ -627,3 +629,35 @@ class TraceCollectionListener(BaseEventListener):
"event": safe_serialize_to_dict(event),
"source": source,
}
def _show_tracing_disabled_message(self) -> None:
"""Show a message when tracing is disabled."""
from rich.console import Console
from rich.panel import Panel
from crewai.events.listeners.tracing.utils import has_user_declined_tracing
console = Console()
if has_user_declined_tracing():
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Crew/Flow code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
else:
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Crew/Flow code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
panel = Panel(
message,
title="Tracing Status",
border_style="blue",
padding=(1, 2),
)
console.print(panel)

View File

@@ -1,3 +1,4 @@
from contextvars import ContextVar, Token
from datetime import datetime
import getpass
import hashlib
@@ -8,7 +9,7 @@ from pathlib import Path
import platform
import re
import subprocess
from typing import Any
from typing import Any, cast
import uuid
import click
@@ -23,7 +24,120 @@ from crewai.utilities.serialization import to_serializable
logger = logging.getLogger(__name__)
_tracing_enabled: ContextVar[bool | None] = ContextVar("_tracing_enabled", default=None)
def should_enable_tracing(*, override: bool | None = None) -> bool:
"""Determine if tracing should be enabled.
This is the single source of truth for tracing enablement.
Priority order:
1. Explicit override (e.g., Crew.tracing=True/False)
2. Environment variable CREWAI_TRACING_ENABLED
3. User consent from user_data
Args:
override: Explicit override for tracing (True=always enable, False=always disable, None=check other settings)
Returns:
True if tracing should be enabled, False otherwise.
"""
if override is True:
return True
if override is False:
return False
env_value = os.getenv("CREWAI_TRACING_ENABLED", "").lower()
if env_value in ("true", "1"):
return True
data = _load_user_data()
if data.get("trace_consent", False) is not False:
return True
return False
def set_tracing_enabled(enabled: bool) -> object:
"""Set tracing enabled state for current execution context.
Args:
enabled: Whether tracing should be enabled
Returns:
A token that can be used with reset_tracing_enabled to restore previous value.
"""
return _tracing_enabled.set(enabled)
def reset_tracing_enabled(token: Token[bool | None]) -> None:
"""Reset tracing enabled state to previous value.
Args:
token: Token returned from set_tracing_enabled
"""
_tracing_enabled.reset(token)
def is_tracing_enabled_in_context() -> bool:
"""Check if tracing is enabled in current execution context.
Returns:
True if tracing is enabled in context, False otherwise.
Returns False if context has not been set.
"""
enabled = _tracing_enabled.get()
return enabled if enabled is not None else False
def _user_data_file() -> Path:
base = Path(db_storage_path())
base.mkdir(parents=True, exist_ok=True)
return base / ".crewai_user.json"
def _load_user_data() -> dict[str, Any]:
p = _user_data_file()
if p.exists():
try:
return cast(dict[str, Any], json.loads(p.read_text()))
except (json.JSONDecodeError, OSError, PermissionError) as e:
logger.warning(f"Failed to load user data: {e}")
return {}
def _save_user_data(data: dict[str, Any]) -> None:
try:
p = _user_data_file()
p.write_text(json.dumps(data, indent=2))
except (OSError, PermissionError) as e:
logger.warning(f"Failed to save user data: {e}")
def has_user_declined_tracing() -> bool:
"""Check if user has explicitly declined trace collection.
Returns:
True if user previously declined tracing, False otherwise.
"""
data = _load_user_data()
if data.get("first_execution_done", False):
return data.get("trace_consent", False) is False
return False
def is_tracing_enabled() -> bool:
"""Check if tracing should be enabled.
Returns:
True if tracing is enabled and not disabled, False otherwise.
"""
# If user has explicitly declined tracing, never enable it
if has_user_declined_tracing():
return False
return os.getenv("CREWAI_TRACING_ENABLED", "false").lower() == "true"
@@ -213,36 +327,12 @@ def _get_generic_system_id() -> str | None:
return None
def _user_data_file() -> Path:
base = Path(db_storage_path())
base.mkdir(parents=True, exist_ok=True)
return base / ".crewai_user.json"
def _load_user_data() -> dict:
p = _user_data_file()
if p.exists():
try:
return json.loads(p.read_text())
except (json.JSONDecodeError, OSError, PermissionError) as e:
logger.warning(f"Failed to load user data: {e}")
return {}
def _save_user_data(data: dict) -> None:
try:
p = _user_data_file()
p.write_text(json.dumps(data, indent=2))
except (OSError, PermissionError) as e:
logger.warning(f"Failed to save user data: {e}")
def get_user_id() -> str:
"""Stable, anonymized user identifier with caching."""
data = _load_user_data()
if "user_id" in data:
return data["user_id"]
return cast(str, data["user_id"])
try:
username = getpass.getuser()
@@ -263,8 +353,12 @@ def is_first_execution() -> bool:
return not data.get("first_execution_done", False)
def mark_first_execution_done() -> None:
"""Mark that the first execution has been completed."""
def mark_first_execution_done(user_consented: bool = False) -> None:
"""Mark that the first execution has been completed.
Args:
user_consented: Whether the user consented to trace collection.
"""
data = _load_user_data()
if data.get("first_execution_done", False):
return
@@ -275,12 +369,13 @@ def mark_first_execution_done() -> None:
"first_execution_at": datetime.now().timestamp(),
"user_id": get_user_id(),
"machine_id": _get_machine_id(),
"trace_consent": user_consented,
}
)
_save_user_data(data)
def safe_serialize_to_dict(obj, exclude: set[str] | None = None) -> dict[str, Any]:
def safe_serialize_to_dict(obj: Any, exclude: set[str] | None = None) -> dict[str, Any]:
"""Safely serialize an object to a dictionary for event data."""
try:
serialized = to_serializable(obj, exclude)
@@ -291,7 +386,9 @@ def safe_serialize_to_dict(obj, exclude: set[str] | None = None) -> dict[str, An
return {"serialization_error": str(e), "object_type": type(obj).__name__}
def truncate_messages(messages, max_content_length=500, max_messages=5):
def truncate_messages(
messages: list[dict[str, Any]], max_content_length: int = 500, max_messages: int = 5
) -> list[dict[str, Any]]:
"""Truncate message content and limit number of messages"""
if not messages or not isinstance(messages, list):
return messages
@@ -308,9 +405,22 @@ def truncate_messages(messages, max_content_length=500, max_messages=5):
def should_auto_collect_first_time_traces() -> bool:
"""True if we should auto-collect traces for first-time user."""
"""True if we should auto-collect traces for first-time user.
Returns:
True if first-time user AND telemetry not disabled AND tracing not explicitly enabled, False otherwise.
"""
if _is_test_environment():
return False
# If user has previously declined, never auto-collect
if has_user_declined_tracing():
return False
if is_tracing_enabled_in_context():
return False
return is_first_execution()
@@ -355,7 +465,7 @@ def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool:
result = [False]
def get_input():
def get_input() -> None:
try:
response = input().strip().lower()
result[0] = response in ["y", "yes"]
@@ -377,6 +487,10 @@ def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool:
return False
def mark_first_execution_completed() -> None:
"""Mark first execution as completed (called after trace prompt)."""
mark_first_execution_done()
def mark_first_execution_completed(user_consented: bool = False) -> None:
"""Mark first execution as completed (called after trace prompt).
Args:
user_consented: Whether the user consented to trace collection.
"""
mark_first_execution_done(user_consented=user_consented)

View File

@@ -1,3 +1,4 @@
import threading
from typing import Any, ClassVar
from rich.console import Console
@@ -27,6 +28,7 @@ class ConsoleFormatter:
_pending_a2a_turn_number: int | None = None
_a2a_turn_branches: ClassVar[dict[int, Tree]] = {}
_current_a2a_agent_name: str | None = None
crew_completion_printed: ClassVar[threading.Event] = threading.Event()
def __init__(self, verbose: bool = False):
self.console = Console(width=None)
@@ -47,13 +49,44 @@ class ConsoleFormatter:
padding=(1, 2),
)
def _show_tracing_disabled_message_if_needed(self) -> None:
"""Show tracing disabled message if tracing is not enabled."""
from crewai.events.listeners.tracing.utils import (
has_user_declined_tracing,
is_tracing_enabled_in_context,
)
if not is_tracing_enabled_in_context():
if has_user_declined_tracing():
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Crew/Flow code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
else:
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Crew/Flow code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
panel = Panel(
message,
title="Tracing Status",
border_style="blue",
padding=(1, 2),
)
self.console.print(panel)
def create_status_content(
self,
title: str,
name: str,
status_style: str = "blue",
tool_args: dict[str, Any] | str = "",
**fields,
**fields: Any,
) -> Text:
"""Create standardized status content with consistent formatting."""
content = Text()
@@ -92,7 +125,7 @@ class ConsoleFormatter:
"""Add a node to the tree with consistent styling."""
return parent.add(Text(text, style=style))
def print(self, *args, **kwargs) -> None:
def print(self, *args: Any, **kwargs: Any) -> None:
"""Custom print that replaces consecutive Tree renders.
* If the argument is a single ``Tree`` instance, we either start a
@@ -208,11 +241,20 @@ class ConsoleFormatter:
self.print_panel(content, title, style)
if status in ["completed", "failed"]:
self.crew_completion_printed.set()
# Show tracing disabled message after crew completion
self._show_tracing_disabled_message_if_needed()
def create_crew_tree(self, crew_name: str, source_id: str) -> Tree | None:
"""Create and initialize a new crew tree with initial status."""
if not self.verbose:
return None
# Reset the crew completion event for this new crew execution
ConsoleFormatter.crew_completion_printed.clear()
tree = Tree(
Text("🚀 Crew: ", style="cyan bold") + Text(crew_name, style="cyan")
)
@@ -497,7 +539,7 @@ class ConsoleFormatter:
return method_branch
def get_llm_tree(self, tool_name: str):
def get_llm_tree(self, tool_name: str) -> Tree:
text = Text()
text.append(f"🔧 Using {tool_name} from LLM available_function", style="yellow")
@@ -512,7 +554,7 @@ class ConsoleFormatter:
self,
tool_name: str,
tool_args: dict[str, Any] | str,
):
) -> None:
# Create status content for the tool usage
content = self.create_status_content(
"Tool Usage Started", tool_name, Status="In Progress", tool_args=tool_args
@@ -528,7 +570,7 @@ class ConsoleFormatter:
def handle_llm_tool_usage_finished(
self,
tool_name: str,
):
) -> None:
tree = self.get_llm_tree(tool_name)
self.add_tree_node(tree, "✅ Tool Usage Completed", "green")
self.print(tree)
@@ -538,7 +580,7 @@ class ConsoleFormatter:
self,
tool_name: str,
error: str,
):
) -> None:
tree = self.get_llm_tree(tool_name)
self.add_tree_node(tree, "❌ Tool Usage Failed", "red")
self.print(tree)
@@ -1558,7 +1600,7 @@ class ConsoleFormatter:
if branch_to_use is None and tree_to_use is not None:
branch_to_use = tree_to_use
def add_panel():
def add_panel() -> None:
memory_text = str(memory_content)
if len(memory_text) > 500:
memory_text = memory_text[:497] + "..."

View File

@@ -26,14 +26,17 @@ from uuid import uuid4
from opentelemetry import baggage
from opentelemetry.context import attach, detach
from pydantic import BaseModel, Field, ValidationError
from rich.console import Console
from rich.panel import Panel
from crewai.events.event_bus import crewai_event_bus
from crewai.events.listeners.tracing.trace_listener import (
TraceCollectionListener,
)
from crewai.events.listeners.tracing.utils import (
is_tracing_enabled,
should_auto_collect_first_time_traces,
has_user_declined_tracing,
set_tracing_enabled,
should_enable_tracing,
)
from crewai.events.types.flow_events import (
FlowCreatedEvent,
@@ -452,7 +455,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
_router_paths: ClassVar[dict[FlowMethodName, list[FlowMethodName]]] = {}
initial_state: type[T] | T | None = None
name: str | None = None
tracing: bool | None = False
tracing: bool | None = None
def __class_getitem__(cls: type[Flow[T]], item: type[T]) -> type[Flow[T]]:
class _FlowGeneric(cls): # type: ignore
@@ -464,13 +467,14 @@ class Flow(Generic[T], metaclass=FlowMeta):
def __init__(
self,
persistence: FlowPersistence | None = None,
tracing: bool | None = False,
tracing: bool | None = None,
**kwargs: Any,
) -> None:
"""Initialize a new Flow instance.
Args:
persistence: Optional persistence backend for storing flow states
tracing: Whether to enable tracing. True=always enable, False=always disable, None=check environment/user settings
**kwargs: Additional state values to initialize or override
"""
# Initialize basic instance attributes
@@ -488,13 +492,11 @@ class Flow(Generic[T], metaclass=FlowMeta):
# Initialize state with initial values
self._state = self._create_initial_state()
self.tracing = tracing
if (
is_tracing_enabled()
or self.tracing
or should_auto_collect_first_time_traces()
):
trace_listener = TraceCollectionListener()
trace_listener.setup_listeners(crewai_event_bus)
tracing_enabled = should_enable_tracing(override=self.tracing)
set_tracing_enabled(tracing_enabled)
trace_listener = TraceCollectionListener()
trace_listener.setup_listeners(crewai_event_bus)
# Apply any additional kwargs
if kwargs:
self._initialize_state(kwargs)
@@ -936,18 +938,13 @@ class Flow(Generic[T], metaclass=FlowMeta):
)
self._event_futures.clear()
if (
is_tracing_enabled()
or self.tracing
or should_auto_collect_first_time_traces()
):
trace_listener = TraceCollectionListener()
if trace_listener.batch_manager.batch_owner_type == "flow":
if trace_listener.first_time_handler.is_first_time:
trace_listener.first_time_handler.mark_events_collected()
trace_listener.first_time_handler.handle_execution_completion()
else:
trace_listener.batch_manager.finalize_batch()
trace_listener = TraceCollectionListener()
if trace_listener.batch_manager.batch_owner_type == "flow":
if trace_listener.first_time_handler.is_first_time:
trace_listener.first_time_handler.mark_events_collected()
trace_listener.first_time_handler.handle_execution_completion()
else:
trace_listener.batch_manager.finalize_batch()
return final_output
finally:
@@ -1381,3 +1378,32 @@ class Flow(Generic[T], metaclass=FlowMeta):
)
structure = build_flow_structure(self)
return render_interactive(structure, filename=filename, show=show)
@staticmethod
def _show_tracing_disabled_message() -> None:
"""Show a message when tracing is disabled."""
console = Console()
if has_user_declined_tracing():
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Flow code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
else:
message = """Info: Tracing is disabled.
To enable tracing, do any one of these:
• Set tracing=True in your Flow code
• Set CREWAI_TRACING_ENABLED=true in your project's .env file
• Run: crewai traces enable"""
panel = Panel(
message,
title="Tracing Status",
border_style="blue",
padding=(1, 2),
)
console.print(panel)