diff --git a/docs/edge/ar/telemetry.mdx b/docs/edge/ar/telemetry.mdx index d90114446..d6ad1b350 100644 --- a/docs/edge/ar/telemetry.mdx +++ b/docs/edge/ar/telemetry.mdx @@ -62,6 +62,7 @@ os.environ['OTEL_SDK_DISABLED'] = 'true' | نعم | محاولة نشر الطاقم باستخدام CLI الخاص بـ CrewAI | تشمل: حقيقة إجراء النشر ومعرّف الطاقم، وما إذا كان يحاول سحب السجلات، وما إذا بدأ النشر من أمر CLI أو من واجهة التشغيل TUI. لا تُسجَّل محتويات المشروع أو الطاقم. لا توجد بيانات شخصية. | | نعم | بيئة التنفيذ | تشمل: مساعد البرمجة بالذكاء الاصطناعي الذي يشغّل العملية إن وُجد (واحد من قائمة ثابتة مثل `claude_code` أو `codex` أو `cursor` أو `unknown`)، ومكان تشغيل العملية (واحد من قائمة ثابتة مثل `ci` أو `container` أو `serverless` أو `interactive`)، و`project_id` من ملف `pyproject.toml` عند ضبطه. يتحقق الاكتشاف فقط مما إذا كانت متغيرات البيئة المعروفة مضبوطة، ولا يقرأ قيمها أبدًا. لا بيانات شخصية. | | نعم | إشارات دورة حياة التدفق | تشمل: بدء التدفق، وما إذا اكتمل أو فشل، وما إذا فشلت إحدى دواله، وما إذا توقّف مؤقتًا لطلب إدخال أو ملاحظات بشرية، وما إذا كان البدء استئنافًا لتشغيل سابق، وما إذا فشلت دورة محادثة، ومدة تشغيل التدفق، وما إذا كان التدفق من التدفقات التي يشغّلها CrewAI داخليًا أم من كتابتك. يُسجَّل اسم التدفق (يجب ألا يتضمن معلومات شخصية)، كما هو الحال بالفعل عند إنشاء التدفق وتنفيذه. لا تُسجَّل أبدًا أسماء الدوال أو رسائل الأخطاء أو حالة التدفق. لا بيانات شخصية. | +| نعم | إشارة مشاركة التتبع | تشمل: نجاح مشاركة دفعة من عمليات التتبع مع CrewAI AMP، وما إذا تمت المشاركة بشكل مجهول (قبل إنشاء حساب) أو مرتبطة بحسابك. ومثل كل span، تحمل أيضًا سمات بيئة التنفيذ الموضحة أعلاه (`project_id` عند تكوينه، ومساعد البرمجة، وبيئة التشغيل). يصف هذا الصف بيانات القياس عن بُعد الخاصة بالمشاركة فقط — وليس محتويات التتبع أو الوصول الذي تمنحه روابط التتبع المشتركة. لا تُسجَّل محتويات التتبع أو المدخلات أو المخرجات في هذه الإشارة. قبل مشاركة التتبعات، راجع الأسرار والبيانات الشخصية وإعدادات التنقيح والاحتفاظ في AMP. | | لا | بيانات الوكيل الموسّعة | تشمل: وصف الهدف، نص الخلفية، معرّف ملف موجهات i18n. يجب على المستخدمين التأكد من عدم تضمين معلومات شخصية في حقول النص. | | لا | معلومات المهمة التفصيلية | تشمل: وصف المهمة، وصف المخرجات المتوقعة، مراجع السياق. يجب على المستخدمين التأكد من عدم تضمين معلومات شخصية في هذه الحقول. | | لا | معلومات البيئة | تشمل: المنصة، الإصدار، النظام، الإصدار، وعدد وحدات المعالجة المركزية. مثال: 'Windows 10'، 'x86_64'. لا بيانات شخصية. | diff --git a/docs/edge/en/telemetry.mdx b/docs/edge/en/telemetry.mdx index 7d5261c49..f63c5651b 100644 --- a/docs/edge/en/telemetry.mdx +++ b/docs/edge/en/telemetry.mdx @@ -62,6 +62,7 @@ own tracer provider, which is independent of the one described here. | Yes | Crew Deployment attempt using crewAI CLI | Includes: The fact a deploy is being made and crew id, whether it's trying to pull logs, and whether the deploy was started from a CLI command or from the run TUI. No project or crew contents. No personal data. | | Yes | Execution Environment | Includes: which AI coding assistant is running the process, if any (one of a fixed list such as `claude_code`, `codex`, `cursor`, or `unknown`), where the process runs (one of a fixed list such as `ci`, `container`, `serverless`, `interactive`), and the `project_id` from your `pyproject.toml` when one is configured. Detection reads only whether known environment variables are set, never their values. No personal data. | | Yes | Flow Lifecycle Signals | Includes: that a flow started, whether it completed or failed, whether one of its methods failed, whether it paused for human input or feedback, whether the start was a resumed run, whether a conversation turn failed, how long the flow ran, and whether the flow is one CrewAI runs internally or one you wrote. The flow name is recorded (should not include personal info), as it already is for flow creation and execution. Method names, error messages and flow state are never recorded. No personal data. | +| Yes | Trace Sharing Signal | Includes: that a batch of traces was successfully shared with CrewAI AMP, and whether it was shared anonymously (before you have an account) or linked to your account. Like every span, it also carries the Execution Environment attributes described above (`project_id` when configured, the coding assistant, and the runtime). This row describes sharing telemetry only — not the trace contents or access granted by shared trace links. Trace contents, inputs, and outputs are never recorded on this signal. Before sharing traces, review secrets, personal data, and AMP redaction and retention settings. | | No | Agent's Expanded Data | Includes: goal description, backstory text, i18n prompt file identifier. Users should ensure no personal info is included in text fields. | | No | Detailed Task Information | Includes: task description, expected output description, context references. Users should ensure no personal info is included in these fields. | | No | Environment Information | Includes: platform, release, system, version, and CPU count. Example: 'Windows 10', 'x86_64'. No personal data. | diff --git a/docs/edge/ko/telemetry.mdx b/docs/edge/ko/telemetry.mdx index 8c72d7846..f1131c6b6 100644 --- a/docs/edge/ko/telemetry.mdx +++ b/docs/edge/ko/telemetry.mdx @@ -59,7 +59,8 @@ provider로 등록하지 않습니다. 이를 통해 양방향이 분리됩니 | 예 | LLM 속성 | LLM의 이름, model_name, 모델, top_k, temperature 및 클래스명이 포함됩니다. 모두 기술적이고 비개인 정보입니다. | | 예 | crewAI CLI를 통한 Crew 배포 시도 | 포함 항목: 배포가 시도되고 있다는 사실과 crew id, 로그를 가져오려고 하는지 여부, 그리고 배포가 CLI 명령에서 시작되었는지 실행 TUI에서 시작되었는지 여부. 프로젝트나 crew의 내용은 기록되지 않습니다. 개인 정보 없음. | | 예 | 실행 환경 | 포함: 프로세스를 실행 중인 AI 코딩 어시스턴트(있는 경우, `claude_code`, `codex`, `cursor`, `unknown` 등 고정 목록 중 하나), 프로세스가 실행되는 위치(`ci`, `container`, `serverless`, `interactive` 등 고정 목록 중 하나), 그리고 `pyproject.toml`에 설정된 경우 `project_id`. 감지는 알려진 환경 변수의 설정 여부만 확인하며 값은 읽지 않음. 개인 데이터 없음. | -| 예 | Flow 라이프사이클 신호 | 포함 항목: flow의 시작, 완료 또는 실패 여부, 해당 메서드의 실패 여부, 사람의 입력이나 피드백을 위해 일시 중지되었는지 여부, 해당 시작이 재개된 실행인지 여부, 대화 턴의 실패 여부, flow 실행 시간, 그리고 해당 flow가 CrewAI가 내부적으로 실행하는 것인지 사용자가 작성한 것인지 여부. flow 이름은 기록되며(개인 정보 포함 불가), 이는 flow 생성 및 실행에서 이미 그러합니다. 메서드 이름, 오류 메시지, flow 상태는 절대 기록하지 않습니다. 개인 정보 없음. | +| 예 | Flow 라이프사이클 신호 | 포함 항목: flow의 시작, 완료 또는 실패 여부, 해당 메서드의 실패 여부, 사람의 입력이나 피드백을 위해 일시 중지되었는지 여부, 해당 시작이 재개된 실행인지 여부, 대화 턴의 실패 여부, flow 실행 시간, 그리고 해당 flow가 CrewAI가 내부적으로 실행하는 것인지 사용자가 작성한 것인지 여부. flow 이름은 기록되며(개인 정보를 포함해서는 안 됨), 이는 flow 생성 및 실행에서 이미 그러합니다. 메서드 이름, 오류 메시지, flow 상태는 절대 기록하지 않습니다. 개인 정보 없음. | +| 예 | 트레이스 공유 신호 | 포함 항목: 트레이스 배치가 CrewAI AMP에 성공적으로 공유되었는지 여부와, 익명으로(계정 생성 전) 공유되었는지 또는 계정에 연결되어 공유되었는지 여부. 모든 span과 마찬가지로 위에서 설명한 실행 환경 속성(구성된 경우 `project_id`, 코딩 어시스턴트, 런타임)도 함께 기록됩니다. 이 행은 공유 텔레메트리만 설명하며 — 트레이스 내용이나 공유된 트레이스 링크로 부여되는 접근 권한은 설명하지 않습니다. 트레이스 내용, 입력, 출력은 이 신호에는 기록되지 않습니다. 트레이스를 공유하기 전에 비밀 정보, 개인 데이터, AMP 편집 및 보존 설정을 검토하세요. | | 아니오 | 에이전트 확장 데이터 | 목표 설명, 배경 이야기 텍스트, i18n 프롬프트 파일 식별자가 포함됩니다. 사용자들은 텍스트 필드에 개인 정보가 포함되지 않도록 해야 합니다. | | 아니오 | 상세 작업 정보 | 작업 설명, 예상 출력 설명, 컨텍스트 참조가 포함됩니다. 사용자들은 이러한 필드에 개인 정보가 포함되지 않도록 해야 합니다. | | 아니오 | 환경 정보 | 플랫폼, 릴리즈, 시스템, 버전, CPU 개수가 포함됩니다. 예: 'Windows 10', 'x86_64'. 개인 정보 없음. | diff --git a/docs/edge/pt-BR/telemetry.mdx b/docs/edge/pt-BR/telemetry.mdx index 6e4025b51..c7156e20a 100644 --- a/docs/edge/pt-BR/telemetry.mdx +++ b/docs/edge/pt-BR/telemetry.mdx @@ -59,9 +59,10 @@ por meio do próprio tracer provider, que é independente do descrito aqui. | Sim | Dados de Execução de Testes | Inclui: chave e ID aleatórias do crew, número de iterações, nome do modelo usado, score de qualidade (float), tempo de execução (em segundos). Tudo não pessoal. | | Sim | Dados do Ciclo de Vida da Tarefa | Inclui: horários de criação, início/fim de execução, identificadores de crew e tarefa. Armazenado como spans com timestamps. Sem dados pessoais. | | Sim | Atributos do LLM | Inclui: nome, model_name, model, top_k, temperatura e nome da classe do LLM. Todos técnicos, sem dados pessoais. | -| Sim | Tentativa de Deploy do Crew pelo CLI do crewAI | Inclui: O fato de um deploy estar sendo realizado e o crew id, se está tentando buscar logs, e se o deploy foi iniciado por um comando do CLI ou pela TUI de execução. Nenhum conteúdo do projeto ou do crew. Nenhum dado pessoal. | +| Sim | Tentativa de Deploy do Crew pelo CLI do crewAI | Inclui: O fato de um deploy estar sendo realizado e o crew id, se está tentando buscar logs, e se o deploy foi iniciado por um comando do CLI ou pela TUI de execução. Não inclui conteúdo do projeto ou do crew nem dados pessoais. | | Sim | Ambiente de Execução | Inclui: qual assistente de código com IA está executando o processo, se houver (um de uma lista fixa como `claude_code`, `codex`, `cursor` ou `unknown`), onde o processo é executado (um de uma lista fixa como `ci`, `container`, `serverless`, `interactive`) e o `project_id` do seu `pyproject.toml` quando houver um configurado. A detecção lê apenas se variáveis de ambiente conhecidas estão definidas, nunca seus valores. Sem dados pessoais. | -| Sim | Sinais de Ciclo de Vida do Flow | Inclui: que um flow iniciou, se foi concluído ou falhou, se um de seus métodos falhou, se pausou para entrada ou feedback humano, se o início foi uma execução retomada, se um turno de conversa falhou, por quanto tempo o flow executou e se o flow é um que o CrewAI executa internamente ou um que você escreveu. O nome do flow é registrado (não deve incluir info pessoal), como já ocorre na criação e execução do flow. Nomes de métodos, mensagens de erro e estado do flow nunca são registrados. Sem dados pessoais. | +| Sim | Sinais de Ciclo de Vida do Flow | Inclui: que um flow iniciou, se foi concluído ou falhou, se um de seus métodos falhou, se pausou para entrada ou feedback humano, se o início foi uma execução retomada, se um turno de conversa falhou, por quanto tempo o flow executou e se o flow é um que o CrewAI executa internamente ou um que você escreveu. O nome do flow é registrado (não deve incluir informações pessoais), como já ocorre na criação e execução do flow. Nomes de métodos, mensagens de erro e estado do flow nunca são registrados. Sem dados pessoais. | +| Sim | Sinal de Compartilhamento de Trace | Inclui: que um lote de traces foi compartilhado com sucesso com o CrewAI AMP, e se foi compartilhado anonimamente (antes de você ter uma conta) ou vinculado à sua conta. Como todo span, também carrega os atributos de Ambiente de Execução descritos acima (`project_id` quando configurado, o assistente de programação e o runtime). Esta linha descreve apenas a telemetria do compartilhamento — não o conteúdo dos traces nem o acesso concedido por links de traces compartilhados. O conteúdo dos traces, entradas e saídas nunca são registrados neste sinal. Antes de compartilhar traces, revise segredos, dados pessoais e as configurações de redação e retenção do AMP. | | Não | Dados Expandidos do Agente | Inclui: descrição do objetivo, texto da história, identificador de arquivo i18n prompt. Usuários devem garantir que não haja info pessoal nesses campos de texto. | | Não | Informações Detalhadas da Tarefa | Inclui: descrição da tarefa, descrição do resultado esperado, referências de contexto. Usuários devem garantir que não haja info pessoal nessas áreas. | | Não | Informações de Ambiente | Inclui: plataforma, release, sistema, versão e quantidade de CPUs. Exemplo: 'Windows 10', 'x86_64'. Sem dados pessoais. | diff --git a/lib/crewai/src/crewai/events/listeners/tracing/trace_batch_manager.py b/lib/crewai/src/crewai/events/listeners/tracing/trace_batch_manager.py index 6f3abd399..b2846acb4 100644 --- a/lib/crewai/src/crewai/events/listeners/tracing/trace_batch_manager.py +++ b/lib/crewai/src/crewai/events/listeners/tracing/trace_batch_manager.py @@ -29,6 +29,7 @@ from crewai.events.listeners.tracing.utils import ( should_auto_collect_first_time_traces, ) from crewai.plus_api import PlusAPI +from crewai.telemetry.telemetry import Telemetry from crewai.version import get_crewai_version @@ -78,6 +79,7 @@ class TraceBatchManager: self.backend_initialized: bool = False self.trace_url: str | None = None self.ephemeral_trace_url: str | None = None + self._telemetry: Telemetry = Telemetry() try: self.plus_api = PlusAPI( api_key=get_auth_token(), @@ -408,6 +410,14 @@ class TraceBatchManager: if response.status_code == 200: self._batch_finalized = True + # Emitted on finalize, not init: a batch that initializes but + # fails to send never lands in AMP. Records only that a batch + # arrived, never its contents. + self._telemetry.feature_usage_span( + "tracing:ephemeral_sent" + if is_ephemeral + else "tracing:authenticated_sent" + ) access_code = response.json().get("access_code", None) console = Console() settings = Settings() diff --git a/lib/crewai/tests/tracing/test_tracing.py b/lib/crewai/tests/tracing/test_tracing.py index 11c25c222..bead4638f 100644 --- a/lib/crewai/tests/tracing/test_tracing.py +++ b/lib/crewai/tests/tracing/test_tracing.py @@ -16,6 +16,7 @@ from crewai.events.listeners.tracing.trace_listener import ( ) from crewai.events.listeners.tracing.types import TraceEvent from crewai.flow.flow import Flow, start +from crewai.telemetry.telemetry import Telemetry from tests.utils import wait_for_event_handlers @@ -1781,3 +1782,175 @@ class TestTraceBatchManagerDuplicateInitMerge: assert meta.get("execution_type") == "flow" assert meta.get("flow_name") == "ResearchFlow" assert meta.get("crew_name") == "Unknown Crew" + + +class TestTraceBatchSentTelemetry: + """A finalized trace batch reports that it reached AMP. + + The signal is what links an OSS project to its AMP traces, so it must fire + on exactly the runs that landed and on no others. + """ + + def _make_manager(self, *, ephemeral: bool) -> TraceBatchManager: + with patch( + "crewai.events.listeners.tracing.trace_batch_manager.get_auth_token", + return_value="mock_token", + ): + bm = TraceBatchManager() + bm.trace_batch_id = "batch-id" + bm.is_current_batch_ephemeral = ephemeral + bm._telemetry = MagicMock() + return bm + + def _finalize(self, bm: TraceBatchManager, *, status_code: int = 200) -> bool: + endpoint = ( + "finalize_ephemeral_trace_batch" + if bm.is_current_batch_ephemeral + else "finalize_trace_batch" + ) + with ( + patch.object( + bm.plus_api, + endpoint, + return_value=MagicMock( + status_code=status_code, + json=MagicMock(return_value={"access_code": "TRACE-test"}), + text="error body", + ), + ), + patch.object(bm, "_mark_batch_as_failed"), + patch("crewai.events.listeners.tracing.trace_batch_manager.Console.print"), + ): + return bm._finalize_backend_batch() + + def _features(self, bm: TraceBatchManager) -> list[str]: + return [c.args[0] for c in bm._telemetry.feature_usage_span.call_args_list] + + def test_ephemeral_batch_reports_ephemeral_sent(self) -> None: + bm = self._make_manager(ephemeral=True) + assert self._finalize(bm) is True + assert self._features(bm) == ["tracing:ephemeral_sent"] + + def test_authenticated_batch_reports_authenticated_sent(self) -> None: + bm = self._make_manager(ephemeral=False) + assert self._finalize(bm) is True + assert self._features(bm) == ["tracing:authenticated_sent"] + + def test_rejected_batch_reports_nothing(self) -> None: + """A 500 means the batch never landed, so it must not be counted.""" + bm = self._make_manager(ephemeral=True) + assert self._finalize(bm, status_code=500) is False + assert self._features(bm) == [] + + def test_batch_without_id_reports_nothing(self) -> None: + bm = self._make_manager(ephemeral=True) + bm.trace_batch_id = None + assert self._finalize(bm) is True + assert self._features(bm) == [] + + def test_already_finalized_batch_reports_nothing(self) -> None: + """Finalization is idempotent; the signal must not double-count.""" + bm = self._make_manager(ephemeral=True) + bm._batch_finalized = True + assert self._finalize(bm) is True + assert self._features(bm) == [] + + def test_finalization_raising_reports_nothing(self) -> None: + bm = self._make_manager(ephemeral=True) + with ( + patch.object( + bm.plus_api, + "finalize_ephemeral_trace_batch", + side_effect=RuntimeError("network down"), + ), + patch.object(bm, "_mark_batch_as_failed"), + ): + assert bm._finalize_backend_batch() is False + assert self._features(bm) == [] + + def test_batch_that_fell_back_to_ephemeral_reports_ephemeral(self) -> None: + """A 401 at init flips the batch to ephemeral; finalize must agree.""" + bm = self._make_manager(ephemeral=False) + bm.current_batch = TraceBatch( + user_context={"privacy_level": "standard"}, + execution_metadata={"execution_type": "crew"}, + ) + with ( + patch.object( + bm.plus_api, + "initialize_trace_batch", + return_value=MagicMock(status_code=401), + ), + patch.object( + bm.plus_api, + "initialize_ephemeral_trace_batch", + return_value=MagicMock( + status_code=200, + json=MagicMock(return_value={"ephemeral_trace_id": "eph-id"}), + ), + ), + ): + bm._initialize_backend_batch({}, {}, skip_context_check=True) + + assert bm.is_current_batch_ephemeral is True + assert self._finalize(bm) is True + assert self._features(bm) == ["tracing:ephemeral_sent"] + + def test_first_time_handler_path_reports_ephemeral_sent(self) -> None: + """The first-time flow finalizes through its own caller, not finalize_batch.""" + bm = self._make_manager(ephemeral=True) + bm.current_batch = TraceBatch( + user_context={"privacy_level": "standard"}, + execution_metadata={"execution_type": "crew", "crew_name": "test"}, + ) + bm.backend_initialized = True + + handler = FirstTimeTraceHandler() + handler.is_first_time = True + handler.collected_events = True + handler.batch_manager = bm + + with ( + patch.object(bm, "_send_events_to_backend", return_value=200), + patch.object( + bm.plus_api, + "finalize_ephemeral_trace_batch", + return_value=MagicMock( + status_code=200, + json=MagicMock(return_value={"access_code": "TRACE-test"}), + ), + ), + patch("crewai.events.listeners.tracing.trace_batch_manager.Console.print"), + patch.object(handler, "_show_local_trace_message"), + ): + handler._initialize_backend_and_send_events() + + assert self._features(bm) == ["tracing:ephemeral_sent"] + + def test_broken_telemetry_does_not_break_finalization(self) -> None: + """Telemetry is best-effort: a failing exporter must not fail the batch.""" + bm = self._make_manager(ephemeral=True) + bm._telemetry = Telemetry() + broken_provider = MagicMock() + broken_provider.get_tracer.side_effect = RuntimeError("exporter down") + + with ( + # `provider` exists only when telemetry initialized, so create it here + # to reach the failure this test is about. + patch.object(bm._telemetry, "provider", broken_provider, create=True), + patch.object(bm._telemetry, "_should_execute_telemetry", return_value=True), + ): + assert self._finalize(bm) is True + + # Proves the assertion above is not vacuous: the span was really attempted. + broken_provider.get_tracer.assert_called_once() + + def test_disabled_telemetry_does_not_break_finalization(self) -> None: + """Opted-out users have no provider at all; finalize must still succeed.""" + bm = self._make_manager(ephemeral=True) + bm._telemetry = Telemetry() + + with patch.object( + bm._telemetry, "_should_execute_telemetry", return_value=False + ): + assert self._finalize(bm) is True