From aa6e1a411382afd5be3e6059f3f14125ade82c62 Mon Sep 17 00:00:00 2001 From: fenilfaldu Date: Tue, 20 May 2025 05:00:10 +0530 Subject: [PATCH 1/7] Added span name customization based on default tags --- agentops/instrumentation/crewai/instrumentation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py index 0ecfb8d06..2df9cdb1c 100644 --- a/agentops/instrumentation/crewai/instrumentation.py +++ b/agentops/instrumentation/crewai/instrumentation.py @@ -14,6 +14,7 @@ from agentops.instrumentation.crewai.version import __version__ from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes, MessageAttributes from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute +from agentops import get_client # Initialize logger @@ -380,14 +381,21 @@ def wrap_agent_execute_task( def wrap_task_execute( tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs ): - task_name = instance.description if hasattr(instance, "description") else "task" + # Get the span name from default tags if available + config = get_client().config + span_name = "crewai.task" + if config.default_tags and len(config.default_tags) > 0: + # Get the first tag from the set + first_tag = next(iter(config.default_tags)) + span_name = f"{first_tag}.task" with tracer.start_as_current_span( - f"{task_name}.task", kind=SpanKind.CLIENT, attributes={ SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, + SpanAttributes.OPERATION_NAME: span_name, }, + name=span_name, ) as span: try: span.set_attribute(TELEMETRY_SDK_NAME, "agentops") From 1686e2ea0dfe3edb73a9debeebda247a992b9eb0 Mon Sep 17 00:00:00 2001 From: fenilfaldu Date: Wed, 21 May 2025 01:45:03 +0530 Subject: [PATCH 2/7] added the tags/default tags in the root span --- .../instrumentation/crewai/instrumentation.py | 20 +++++++++---------- agentops/semconv/span_attributes.py | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py index 2df9cdb1c..3b514e742 100644 --- a/agentops/instrumentation/crewai/instrumentation.py +++ b/agentops/instrumentation/crewai/instrumentation.py @@ -381,21 +381,21 @@ def wrap_agent_execute_task( def wrap_task_execute( tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs ): - # Get the span name from default tags if available + task_name = instance.description if hasattr(instance, "description") else "task" + config = get_client().config - span_name = "crewai.task" + attributes = { + SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, + } + if config.default_tags and len(config.default_tags) > 0: - # Get the first tag from the set - first_tag = next(iter(config.default_tags)) - span_name = f"{first_tag}.task" + tag_list = list(config.default_tags) + attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] = tag_list with tracer.start_as_current_span( + f"{task_name}.task", kind=SpanKind.CLIENT, - attributes={ - SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, - SpanAttributes.OPERATION_NAME: span_name, - }, - name=span_name, + attributes=attributes, ) as span: try: span.set_attribute(TELEMETRY_SDK_NAME, "agentops") diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index 79f0285a9..775c8b17f 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -86,6 +86,7 @@ class SpanAttributes: AGENTOPS_ENTITY_INPUT = "agentops.entity.input" AGENTOPS_SPAN_KIND = "agentops.span.kind" AGENTOPS_ENTITY_NAME = "agentops.entity.name" + AGENTOPS_SPAN_TAGS = "tags" # Operation attributes OPERATION_NAME = "operation.name" From e1759df6af41ddc99516b78159c5f7a882ad271c Mon Sep 17 00:00:00 2001 From: fenilfaldu Date: Wed, 21 May 2025 02:20:49 +0530 Subject: [PATCH 3/7] added the test of tags in the root span --- tests/unit/test_session_legacy.py | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/unit/test_session_legacy.py b/tests/unit/test_session_legacy.py index 610fc42eb..1e319299d 100644 --- a/tests/unit/test_session_legacy.py +++ b/tests/unit/test_session_legacy.py @@ -144,3 +144,56 @@ def test_crewai_kwargs_force_flush(): # Explicitly ensure the core isn't already shut down for the test assert TracingCore.get_instance()._initialized, "TracingCore should still be initialized" + + +def test_crewai_task_instrumentation(instrumentation): + """ + Test the CrewAI task instrumentation focusing on span attributes and tags. + This test verifies that task spans are properly created with correct attributes + and tags without requiring a session. + """ + import agentops + from opentelemetry.trace import SpanKind + from agentops.sdk.core import TracingCore + from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues + from opentelemetry import trace + + # Initialize AgentOps with API key and default tags + agentops.init( + api_key="test-api-key", + ) + agentops.start_session(tags=["test", "crewai-integration"]) + # Get the tracer + tracer = trace.get_tracer(__name__) + + # Create a mock task instance + class MockTask: + def __init__(self): + self.description = "Test Task Description" + self.agent = "Test Agent" + self.tools = ["tool1", "tool2"] + + task = MockTask() + + # Start a span for the task + with tracer.start_as_current_span( + f"{task.description}.task", + kind=SpanKind.CLIENT, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, + SpanAttributes.AGENTOPS_SPAN_TAGS: ["crewai", "task-test"] + } + ) as span: + # Verify span attributes + assert span.attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == AgentOpsSpanKindValues.TASK.value + assert "crewai" in span.attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] + assert "task-test" in span.attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] + + # Verify span name + assert span.name == f"{task.description}.task" + + # Verify span kind + assert span.kind == SpanKind.CLIENT + + agentops.end_session(end_state="Success", end_state_reason="Test Finished", is_auto_end=True) + From f1092f17edc955b99510851f56acd960fff21037 Mon Sep 17 00:00:00 2001 From: fenilfaldu Date: Wed, 21 May 2025 02:22:30 +0530 Subject: [PATCH 4/7] ruff check :) --- tests/unit/test_session_legacy.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_session_legacy.py b/tests/unit/test_session_legacy.py index 1e319299d..087a0c815 100644 --- a/tests/unit/test_session_legacy.py +++ b/tests/unit/test_session_legacy.py @@ -154,7 +154,6 @@ def test_crewai_task_instrumentation(instrumentation): """ import agentops from opentelemetry.trace import SpanKind - from agentops.sdk.core import TracingCore from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues from opentelemetry import trace @@ -181,19 +180,18 @@ def __init__(self): kind=SpanKind.CLIENT, attributes={ SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, - SpanAttributes.AGENTOPS_SPAN_TAGS: ["crewai", "task-test"] - } + SpanAttributes.AGENTOPS_SPAN_TAGS: ["crewai", "task-test"], + }, ) as span: # Verify span attributes assert span.attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == AgentOpsSpanKindValues.TASK.value assert "crewai" in span.attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] assert "task-test" in span.attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] - + # Verify span name assert span.name == f"{task.description}.task" - + # Verify span kind assert span.kind == SpanKind.CLIENT agentops.end_session(end_state="Success", end_state_reason="Test Finished", is_auto_end=True) - From befeb6996e6a0fb99cd7cbc2e89fd5c1330c3868 Mon Sep 17 00:00:00 2001 From: fenilfaldu Date: Mon, 26 May 2025 23:20:36 +0530 Subject: [PATCH 5/7] code refactored --- .../instrumentation/crewai/instrumentation.py | 18 ++++++++++++++---- agentops/semconv/span_attributes.py | 2 +- tests/unit/test_session_legacy.py | 8 ++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py index cf48d6037..7d0400f0c 100644 --- a/agentops/instrumentation/crewai/instrumentation.py +++ b/agentops/instrumentation/crewai/instrumentation.py @@ -13,6 +13,7 @@ from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT from agentops.instrumentation.crewai.version import __version__ from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes, MessageAttributes +from agentops.semconv.core import CoreAttributes from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute from agentops import get_client @@ -160,12 +161,20 @@ def wrap_kickoff( logger.debug( f"CrewAI: Starting workflow instrumentation for Crew with {len(getattr(instance, 'agents', []))} agents" ) + + config = get_client().config + attributes = { + SpanAttributes.LLM_SYSTEM: "crewai", + } + + if config.default_tags and len(config.default_tags) > 0: + tag_list = list(config.default_tags) + attributes[CoreAttributes.TAGS]=tag_list + with tracer.start_as_current_span( "crewai.workflow", kind=SpanKind.INTERNAL, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - }, + attributes=attributes, ) as span: try: span.set_attribute(TELEMETRY_SDK_NAME, "agentops") @@ -389,7 +398,8 @@ def wrap_task_execute( if config.default_tags and len(config.default_tags) > 0: tag_list = list(config.default_tags) - attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] = tag_list + attributes[CoreAttributes.TAGS]=tag_list + with tracer.start_as_current_span( f"{task_name}.task", diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index 775c8b17f..8ac87fb63 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -86,7 +86,7 @@ class SpanAttributes: AGENTOPS_ENTITY_INPUT = "agentops.entity.input" AGENTOPS_SPAN_KIND = "agentops.span.kind" AGENTOPS_ENTITY_NAME = "agentops.entity.name" - AGENTOPS_SPAN_TAGS = "tags" + # Operation attributes OPERATION_NAME = "operation.name" diff --git a/tests/unit/test_session_legacy.py b/tests/unit/test_session_legacy.py index 087a0c815..59b9c96a2 100644 --- a/tests/unit/test_session_legacy.py +++ b/tests/unit/test_session_legacy.py @@ -156,7 +156,7 @@ def test_crewai_task_instrumentation(instrumentation): from opentelemetry.trace import SpanKind from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues from opentelemetry import trace - + from agentops.semconv.core import CoreAttributes # Initialize AgentOps with API key and default tags agentops.init( api_key="test-api-key", @@ -180,13 +180,13 @@ def __init__(self): kind=SpanKind.CLIENT, attributes={ SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, - SpanAttributes.AGENTOPS_SPAN_TAGS: ["crewai", "task-test"], + CoreAttributes.TAGS: ["crewai", "task-test"], }, ) as span: # Verify span attributes assert span.attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == AgentOpsSpanKindValues.TASK.value - assert "crewai" in span.attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] - assert "task-test" in span.attributes[SpanAttributes.AGENTOPS_SPAN_TAGS] + assert "crewai" in span.attributes[CoreAttributes.TAGS] + assert "task-test" in span.attributes[CoreAttributes.TAGS] # Verify span name assert span.name == f"{task.description}.task" From d7afe928f9f2b76a76685831d46b7624dcbf5184 Mon Sep 17 00:00:00 2001 From: fenilfaldu Date: Mon, 26 May 2025 23:22:35 +0530 Subject: [PATCH 6/7] ruff checks --- agentops/instrumentation/crewai/instrumentation.py | 5 ++--- agentops/semconv/span_attributes.py | 1 - tests/unit/test_session_legacy.py | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py index 7d0400f0c..0c48d19a3 100644 --- a/agentops/instrumentation/crewai/instrumentation.py +++ b/agentops/instrumentation/crewai/instrumentation.py @@ -169,7 +169,7 @@ def wrap_kickoff( if config.default_tags and len(config.default_tags) > 0: tag_list = list(config.default_tags) - attributes[CoreAttributes.TAGS]=tag_list + attributes[CoreAttributes.TAGS] = tag_list with tracer.start_as_current_span( "crewai.workflow", @@ -398,8 +398,7 @@ def wrap_task_execute( if config.default_tags and len(config.default_tags) > 0: tag_list = list(config.default_tags) - attributes[CoreAttributes.TAGS]=tag_list - + attributes[CoreAttributes.TAGS] = tag_list with tracer.start_as_current_span( f"{task_name}.task", diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index 8ac87fb63..79f0285a9 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -87,7 +87,6 @@ class SpanAttributes: AGENTOPS_SPAN_KIND = "agentops.span.kind" AGENTOPS_ENTITY_NAME = "agentops.entity.name" - # Operation attributes OPERATION_NAME = "operation.name" OPERATION_VERSION = "operation.version" diff --git a/tests/unit/test_session_legacy.py b/tests/unit/test_session_legacy.py index 59b9c96a2..509efa055 100644 --- a/tests/unit/test_session_legacy.py +++ b/tests/unit/test_session_legacy.py @@ -157,6 +157,7 @@ def test_crewai_task_instrumentation(instrumentation): from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues from opentelemetry import trace from agentops.semconv.core import CoreAttributes + # Initialize AgentOps with API key and default tags agentops.init( api_key="test-api-key", From e1469522b442dbef3943fb74e5a3c9a3d756bde8 Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Wed, 28 May 2025 02:33:51 +0530 Subject: [PATCH 7/7] add `TODO` for converting tags to list --- agentops/instrumentation/crewai/instrumentation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py index 0c48d19a3..b091a701c 100644 --- a/agentops/instrumentation/crewai/instrumentation.py +++ b/agentops/instrumentation/crewai/instrumentation.py @@ -398,6 +398,8 @@ def wrap_task_execute( if config.default_tags and len(config.default_tags) > 0: tag_list = list(config.default_tags) + # TODO: This should be a set to prevent duplicates, but we need to ensure + # that the tags are not modified in place, so we convert to list first. attributes[CoreAttributes.TAGS] = tag_list with tracer.start_as_current_span(