From 7f967db60d0982b016346668a734f6e958b5e4b6 Mon Sep 17 00:00:00 2001 From: Dwij Patel Date: Thu, 17 Apr 2025 03:23:37 +0530 Subject: [PATCH 1/4] Refactor CrewAI instrumentation to enhance span attribute management --- .../crewai/crewai_span_attributes.py | 216 +++++++++++------- .../instrumentation/crewai/instrumentation.py | 76 ++++-- 2 files changed, 197 insertions(+), 95 deletions(-) diff --git a/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py b/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py index a367d7f76..a68c2267b 100644 --- a/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py +++ b/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py @@ -1,22 +1,46 @@ -from opentelemetry.trace import Span +"""OpenTelemetry instrumentation for CrewAI.""" + import json +import logging +from typing import Any +from opentelemetry.trace import Span + +from agentops.semconv.span_attributes import SpanAttributes +from agentops.semconv.agent import AgentAttributes +from agentops.semconv.workflow import WorkflowAttributes + +# Initialize logger for logging potential issues and operations +logger = logging.getLogger(__name__) +def _parse_tools(tools): + """Parse tools into a JSON string with name and description.""" + result = [] + for tool in tools: + res = {} + if hasattr(tool, "name") and tool.name is not None: + res["name"] = tool.name + if hasattr(tool, "description") and tool.description is not None: + res["description"] = tool.description + if res: + result.append(res) + return json.dumps(result) -def set_span_attribute(span: Span, name, value): - if value is not None: - if value != "": - span.set_attribute(name, value) - return +def set_span_attribute(span: Span, key: str, value: Any) -> None: + """Set a single attribute on a span.""" + if value is not None and value != "": + span.set_attribute(key, value) class CrewAISpanAttributes: + """Manages span attributes for CrewAI instrumentation.""" + def __init__(self, span: Span, instance) -> None: self.span = span self.instance = instance - self.crew = {"tasks": [], "agents": [], "llms": []} self.process_instance() def process_instance(self): + """Process the instance based on its type.""" instance_type = self.instance.__class__.__name__ method_mapping = { "Crew": self._process_crew, @@ -29,26 +53,7 @@ def process_instance(self): method() def _process_crew(self): - self._populate_crew_attributes() - for key, value in self.crew.items(): - self._set_attribute(f"crewai.crew.{key}", value) - - def _process_agent(self): - agent_data = self._populate_agent_attributes() - for key, value in agent_data.items(): - self._set_attribute(f"crewai.agent.{key}", value) - - def _process_task(self): - task_data = self._populate_task_attributes() - for key, value in task_data.items(): - self._set_attribute(f"crewai.task.{key}", value) - - def _process_llm(self): - llm_data = self._populate_llm_attributes() - for key, value in llm_data.items(): - self._set_attribute(f"crewai.llm.{key}", value) - - def _populate_crew_attributes(self): + """Process a Crew instance.""" for key, value in self.instance.__dict__.items(): if value is None: continue @@ -59,53 +64,117 @@ def _populate_crew_attributes(self): elif key == "llms": self._parse_llms(value) else: - self.crew[key] = str(value) + self._set_attribute(f"crewai.crew.{key}", str(value)) - def _populate_agent_attributes(self): - return self._extract_attributes(self.instance) + def _process_agent(self): + """Process an Agent instance.""" + agent = {} + for key, value in self.instance.__dict__.items(): + if key == "tools": + value = _parse_tools(value) + if value is None: + continue + agent[key] = str(value) + + # Set agent attributes using our semantic conventions + self._set_attribute(AgentAttributes.AGENT_ID, agent.get('id', '')) + self._set_attribute(AgentAttributes.AGENT_ROLE, agent.get('role', '')) + self._set_attribute(AgentAttributes.AGENT_NAME, agent.get('name', '')) + self._set_attribute(AgentAttributes.AGENT_TOOLS, agent.get('tools', '')) + + self._set_attribute("crewai.agent.goal", agent.get('goal', '')) + self._set_attribute("crewai.agent.backstory", agent.get('backstory', '')) + self._set_attribute("crewai.agent.cache", agent.get('cache', '')) + self._set_attribute("crewai.agent.allow_delegation", agent.get('allow_delegation', '')) + self._set_attribute("crewai.agent.allow_code_execution", agent.get('allow_code_execution', '')) + self._set_attribute("crewai.agent.max_retry_limit", agent.get('max_retry_limit', '')) + self._set_attribute("crewai.agent.tools_results", agent.get('tools_results', '')) - def _populate_task_attributes(self): - task_data = self._extract_attributes(self.instance) - if "agent" in task_data: - task_data["agent"] = self.instance.agent.role if self.instance.agent else None - return task_data + def _process_task(self): + """Process a Task instance.""" + task = {} + for key, value in self.instance.__dict__.items(): + if value is None: + continue + if key == "tools": + value = _parse_tools(value) + task[key] = value + elif key == "agent": + task[key] = value.role if value else None + else: + task[key] = str(value) + + # Set task attributes using our semantic conventions + self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_NAME, task.get('description', '')) + self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_TYPE, "task") + self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_INPUT, task.get('context', '')) + self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_OUTPUT, task.get('expected_output', '')) + + self._set_attribute("crewai.task.id", task.get('id', '')) + self._set_attribute("crewai.task.agent", task.get('agent', '')) + self._set_attribute("crewai.task.human_input", task.get('human_input', '')) + self._set_attribute("crewai.task.output", task.get('output', '')) + self._set_attribute("crewai.task.processed_by_agents", str(task.get('processed_by_agents', ''))) - def _populate_llm_attributes(self): - return self._extract_attributes(self.instance) + def _process_llm(self): + """Process an LLM instance.""" + llm = {} + for key, value in self.instance.__dict__.items(): + if value is None: + continue + llm[key] = str(value) + + # Set LLM attributes using our semantic conventions + self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, llm.get('model_name', '')) + self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, llm.get('temperature', '')) + self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, llm.get('max_tokens', '')) + self._set_attribute(SpanAttributes.LLM_REQUEST_TOP_P, llm.get('top_p', '')) def _parse_agents(self, agents): - self.crew["agents"] = [self._extract_agent_data(agent) for agent in agents if agent is not None] + """Parse agents into a list of dictionaries.""" + for agent in agents: + if agent is not None: + agent_data = self._extract_agent_data(agent) + for key, value in agent_data.items(): + self._set_attribute(f"crewai.agent.{key}", value) def _parse_tasks(self, tasks): - self.crew["tasks"] = [ - { - "agent": task.agent.role if task.agent else None, - "description": task.description, - "async_execution": task.async_execution, - "expected_output": task.expected_output, - "human_input": task.human_input, - "tools": task.tools, - "output_file": task.output_file, - } - for task in tasks - ] + """Parse tasks into a list of dictionaries.""" + for task in tasks: + if task is not None: + task_data = { + "agent": task.agent.role if task.agent else None, + "description": task.description, + "async_execution": task.async_execution, + "expected_output": task.expected_output, + "human_input": task.human_input, + "tools": task.tools, + "output_file": task.output_file, + } + for key, value in task_data.items(): + if value is not None: + self._set_attribute(f"crewai.task.{key}", str(value)) def _parse_llms(self, llms): - self.crew["tasks"] = [ - { - "temperature": llm.temperature, - "max_tokens": llm.max_tokens, - "max_completion_tokens": llm.max_completion_tokens, - "top_p": llm.top_p, - "n": llm.n, - "seed": llm.seed, - "base_url": llm.base_url, - "api_version": llm.api_version, - } - for llm in llms - ] + """Parse LLMs into a list of dictionaries.""" + for llm in llms: + if llm is not None: + llm_data = { + "temperature": llm.temperature, + "max_tokens": llm.max_tokens, + "max_completion_tokens": llm.max_completion_tokens, + "top_p": llm.top_p, + "n": llm.n, + "seed": llm.seed, + "base_url": llm.base_url, + "api_version": llm.api_version, + } + for key, value in llm_data.items(): + if value is not None: + self._set_attribute(f"crewai.llm.{key}", str(value)) def _extract_agent_data(self, agent): + """Extract data from an agent.""" model = getattr(agent.llm, "model", None) or getattr(agent.llm, "model_name", None) or "" return { @@ -122,22 +191,7 @@ def _extract_agent_data(self, agent): "llm": str(model), } - def _extract_attributes(self, obj): - attributes = {} - for key, value in obj.__dict__.items(): - if value is None: - continue - if key == "tools": - attributes[key] = self._serialize_tools(value) - else: - attributes[key] = str(value) - return attributes - - def _serialize_tools(self, tools): - return json.dumps( - [{k: v for k, v in vars(tool).items() if v is not None and k in ["name", "description"]} for tool in tools] - ) - def _set_attribute(self, key, value): - if value: - set_span_attribute(self.span, key, str(value) if isinstance(value, list) else value) + """Set an attribute on the span.""" + if value is not None and value != "": + set_span_attribute(self.span, key, value) diff --git a/third_party/opentelemetry/instrumentation/crewai/instrumentation.py b/third_party/opentelemetry/instrumentation/crewai/instrumentation.py index 9611c7272..033f46419 100644 --- a/third_party/opentelemetry/instrumentation/crewai/instrumentation.py +++ b/third_party/opentelemetry/instrumentation/crewai/instrumentation.py @@ -1,5 +1,6 @@ import os import time +import logging from typing import Collection from wrapt import wrap_function_wrapper @@ -8,10 +9,14 @@ from opentelemetry.metrics import Histogram, Meter, get_meter from opentelemetry.instrumentation.utils import unwrap from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT from opentelemetry.instrumentation.crewai.version import __version__ from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute +# Initialize logger for logging potential issues and operations +logger = logging.getLogger(__name__) + _instruments = ("crewai >= 0.70.0",) @@ -20,6 +25,8 @@ def instrumentation_dependencies(self) -> Collection[str]: return _instruments def _instrument(self, **kwargs): + application_name = kwargs.get("application_name", "default_application") + environment = kwargs.get("environment", "default_environment") tracer_provider = kwargs.get("tracer_provider") tracer = get_tracer(__name__, __version__, tracer_provider) @@ -35,16 +42,16 @@ def _instrument(self, **kwargs): ( token_histogram, duration_histogram, - ) = (None, None, None, None) + ) = (None, None) - wrap_function_wrapper("crewai.crew", "Crew.kickoff", wrap_kickoff(tracer, duration_histogram, token_histogram)) + wrap_function_wrapper("crewai.crew", "Crew.kickoff", wrap_kickoff(tracer, duration_histogram, token_histogram, environment, application_name)) wrap_function_wrapper( - "crewai.agent", "Agent.execute_task", wrap_agent_execute_task(tracer, duration_histogram, token_histogram) + "crewai.agent", "Agent.execute_task", wrap_agent_execute_task(tracer, duration_histogram, token_histogram, environment, application_name) ) wrap_function_wrapper( - "crewai.task", "Task.execute_sync", wrap_task_execute(tracer, duration_histogram, token_histogram) + "crewai.task", "Task.execute_sync", wrap_task_execute(tracer, duration_histogram, token_histogram, environment, application_name) ) - wrap_function_wrapper("crewai.llm", "LLM.call", wrap_llm_call(tracer, duration_histogram, token_histogram)) + wrap_function_wrapper("crewai.llm", "LLM.call", wrap_llm_call(tracer, duration_histogram, token_histogram, environment, application_name)) def _uninstrument(self, **kwargs): unwrap("crewai.crew.Crew", "kickoff") @@ -56,9 +63,9 @@ def _uninstrument(self, **kwargs): def with_tracer_wrapper(func): """Helper for providing tracer for wrapper functions.""" - def _with_tracer(tracer, duration_histogram, token_histogram): + def _with_tracer(tracer, duration_histogram, token_histogram, environment, application_name): def wrapper(wrapped, instance, args, kwargs): - return func(tracer, duration_histogram, token_histogram, wrapped, instance, args, kwargs) + return func(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs) return wrapper @@ -67,7 +74,7 @@ def wrapper(wrapped, instance, args, kwargs): @with_tracer_wrapper def wrap_kickoff( - tracer: Tracer, duration_histogram: Histogram, token_histogram: Histogram, wrapped, instance, args, kwargs + tracer: Tracer, duration_histogram: Histogram, token_histogram: Histogram, environment, application_name, wrapped, instance, args, kwargs ): with tracer.start_as_current_span( "crewai.workflow", @@ -77,8 +84,17 @@ def wrap_kickoff( }, ) as span: try: + # Set base span attributes + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + # Process instance attributes CrewAISpanAttributes(span=span, instance=instance) + + # Execute the wrapped function result = wrapped(*args, **kwargs) + if result: class_name = instance.__class__.__name__ span.set_attribute(f"crewai.{class_name.lower()}.result", str(result)) @@ -90,11 +106,12 @@ def wrap_kickoff( return result except Exception as ex: span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) raise @with_tracer_wrapper -def wrap_agent_execute_task(tracer, duration_histogram, token_histogram, wrapped, instance, args, kwargs): +def wrap_agent_execute_task(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs): agent_name = instance.role if hasattr(instance, "role") else "agent" with tracer.start_as_current_span( f"{agent_name}.agent", @@ -104,9 +121,18 @@ def wrap_agent_execute_task(tracer, duration_histogram, token_histogram, wrapped }, ) as span: try: + # Set base span attributes + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + # Process instance attributes CrewAISpanAttributes(span=span, instance=instance) + + # Execute the wrapped function result = wrapped(*args, **kwargs) - if token_histogram: + + if token_histogram and hasattr(instance, "_token_process"): token_histogram.record( instance._token_process.get_summary().prompt_tokens, attributes={ @@ -124,17 +150,20 @@ def wrap_agent_execute_task(tracer, duration_histogram, token_histogram, wrapped }, ) - set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, str(instance.llm.model)) - set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, str(instance.llm.model)) + if hasattr(instance, "llm") and hasattr(instance.llm, "model"): + set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, str(instance.llm.model)) + set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, str(instance.llm.model)) + span.set_status(Status(StatusCode.OK)) return result except Exception as ex: span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) raise @with_tracer_wrapper -def wrap_task_execute(tracer, duration_histogram, token_histogram, wrapped, instance, args, kwargs): +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" with tracer.start_as_current_span( @@ -145,23 +174,41 @@ def wrap_task_execute(tracer, duration_histogram, token_histogram, wrapped, inst }, ) as span: try: + # Set base span attributes + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + # Process instance attributes CrewAISpanAttributes(span=span, instance=instance) + + # Execute the wrapped function result = wrapped(*args, **kwargs) + set_span_attribute(span, SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(result)) span.set_status(Status(StatusCode.OK)) return result except Exception as ex: span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) raise @with_tracer_wrapper -def wrap_llm_call(tracer, duration_histogram, token_histogram, wrapped, instance, args, kwargs): +def wrap_llm_call(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs): llm = instance.model if hasattr(instance, "model") else "llm" with tracer.start_as_current_span(f"{llm}.llm", kind=SpanKind.CLIENT, attributes={}) as span: start_time = time.time() try: + # Set base span attributes + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + # Process instance attributes CrewAISpanAttributes(span=span, instance=instance) + + # Execute the wrapped function result = wrapped(*args, **kwargs) if duration_histogram: @@ -178,6 +225,7 @@ def wrap_llm_call(tracer, duration_histogram, token_histogram, wrapped, instance return result except Exception as ex: span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) raise From a032bbb72d2c75a01d1fa19c148a3f5ddc4f1d3b Mon Sep 17 00:00:00 2001 From: Dwij Patel Date: Sat, 19 Apr 2025 05:57:29 +0530 Subject: [PATCH 2/4] Refactored CrewAISpanAttributes to improve handling of agent, crew, and task attributes. --- agentops/semconv/agent.py | 1 + .../crewai/crewai_span_attributes.py | 221 ++++++++--- .../instrumentation/crewai/instrumentation.py | 342 ++++++++++++++++-- 3 files changed, 496 insertions(+), 68 deletions(-) diff --git a/agentops/semconv/agent.py b/agentops/semconv/agent.py index db5bd97ca..296e77851 100644 --- a/agentops/semconv/agent.py +++ b/agentops/semconv/agent.py @@ -8,6 +8,7 @@ class AgentAttributes: AGENT_ID = "agent.id" # Unique identifier for the agent AGENT_NAME = "agent.name" # Name of the agent AGENT_ROLE = "agent.role" # Role of the agent + AGENT = "agent" # Root prefix for agent attributes # Capabilities AGENT_TOOLS = "agent.tools" # Tools available to the agent diff --git a/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py b/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py index a68c2267b..d1fb83264 100644 --- a/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py +++ b/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py @@ -7,7 +7,8 @@ from agentops.semconv.span_attributes import SpanAttributes from agentops.semconv.agent import AgentAttributes -from agentops.semconv.workflow import WorkflowAttributes +from agentops.semconv.tool import ToolAttributes +from agentops.semconv.message import MessageAttributes # Initialize logger for logging potential issues and operations logger = logging.getLogger(__name__) @@ -23,25 +24,31 @@ def _parse_tools(tools): res["description"] = tool.description if res: result.append(res) - return json.dumps(result) + return result def set_span_attribute(span: Span, key: str, value: Any) -> None: """Set a single attribute on a span.""" if value is not None and value != "": + if hasattr(value, "__str__"): + value = str(value) span.set_attribute(key, value) class CrewAISpanAttributes: """Manages span attributes for CrewAI instrumentation.""" - def __init__(self, span: Span, instance) -> None: + def __init__(self, span: Span, instance, skip_agent_processing=False) -> None: self.span = span self.instance = instance + self.skip_agent_processing = skip_agent_processing self.process_instance() def process_instance(self): """Process the instance based on its type.""" instance_type = self.instance.__class__.__name__ + self._set_attribute(SpanAttributes.LLM_SYSTEM, "crewai") + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_NAME, instance_type) + method_mapping = { "Crew": self._process_crew, "Agent": self._process_agent, @@ -54,112 +61,225 @@ def process_instance(self): def _process_crew(self): """Process a Crew instance.""" + crew_id = getattr(self.instance, "id", "") + self._set_attribute("crewai.crew.id", str(crew_id)) + self._set_attribute("crewai.crew.type", "crewai.crew") + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "workflow") + + logger.debug(f"CrewAI: Processing crew with id {crew_id}") + for key, value in self.instance.__dict__.items(): if value is None: continue + if key == "tasks": - self._parse_tasks(value) + if isinstance(value, list): + self._set_attribute("crewai.crew.max_turns", str(len(value))) + logger.debug(f"CrewAI: Found {len(value)} tasks") elif key == "agents": - self._parse_agents(value) + if isinstance(value, list): + logger.debug(f"CrewAI: Found {len(value)} agents in crew") + + if not self.skip_agent_processing: + self._parse_agents(value) elif key == "llms": self._parse_llms(value) + elif key == "result": + self._set_attribute("crewai.crew.final_output", str(value)) + self._set_attribute("crewai.crew.output", str(value)) + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(value)) else: self._set_attribute(f"crewai.crew.{key}", str(value)) def _process_agent(self): """Process an Agent instance.""" agent = {} + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "agent") + for key, value in self.instance.__dict__.items(): if key == "tools": - value = _parse_tools(value) + parsed_tools = _parse_tools(value) + for i, tool in enumerate(parsed_tools): + tool_prefix = f"crewai.agent.tool.{i}." + for tool_key, tool_value in tool.items(): + self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) + + agent[key] = json.dumps(parsed_tools) + if value is None: continue - agent[key] = str(value) + + if key != "tools": + agent[key] = str(value) - # Set agent attributes using our semantic conventions self._set_attribute(AgentAttributes.AGENT_ID, agent.get('id', '')) self._set_attribute(AgentAttributes.AGENT_ROLE, agent.get('role', '')) self._set_attribute(AgentAttributes.AGENT_NAME, agent.get('name', '')) self._set_attribute(AgentAttributes.AGENT_TOOLS, agent.get('tools', '')) + if 'reasoning' in agent: + self._set_attribute(AgentAttributes.AGENT_REASONING, agent.get('reasoning', '')) + + if 'goal' in agent: + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_INPUT, agent.get('goal', '')) + self._set_attribute("crewai.agent.goal", agent.get('goal', '')) self._set_attribute("crewai.agent.backstory", agent.get('backstory', '')) self._set_attribute("crewai.agent.cache", agent.get('cache', '')) self._set_attribute("crewai.agent.allow_delegation", agent.get('allow_delegation', '')) self._set_attribute("crewai.agent.allow_code_execution", agent.get('allow_code_execution', '')) self._set_attribute("crewai.agent.max_retry_limit", agent.get('max_retry_limit', '')) - self._set_attribute("crewai.agent.tools_results", agent.get('tools_results', '')) + + if hasattr(self.instance, "llm") and self.instance.llm is not None: + model_name = getattr(self.instance.llm, "model", None) or getattr(self.instance.llm, "model_name", None) or "" + temp = getattr(self.instance.llm, "temperature", None) + max_tokens = getattr(self.instance.llm, "max_tokens", None) + top_p = getattr(self.instance.llm, "top_p", None) + + self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) + if temp is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, str(temp)) + if max_tokens is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, str(max_tokens)) + if top_p is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_TOP_P, str(top_p)) + + self._set_attribute("crewai.agent.llm", str(model_name)) + self._set_attribute(AgentAttributes.AGENT_MODELS, str(model_name)) def _process_task(self): """Process a Task instance.""" task = {} + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "workflow.step") + for key, value in self.instance.__dict__.items(): if value is None: continue if key == "tools": - value = _parse_tools(value) - task[key] = value + parsed_tools = _parse_tools(value) + for i, tool in enumerate(parsed_tools): + tool_prefix = f"crewai.task.tool.{i}." + for tool_key, tool_value in tool.items(): + self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) + + task[key] = json.dumps(parsed_tools) + elif key == "agent": task[key] = value.role if value else None + if value: + agent_id = getattr(value, "id", "") + self._set_attribute(AgentAttributes.FROM_AGENT, str(agent_id)) else: task[key] = str(value) - # Set task attributes using our semantic conventions - self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_NAME, task.get('description', '')) - self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_TYPE, "task") - self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_INPUT, task.get('context', '')) - self._set_attribute(WorkflowAttributes.WORKFLOW_STEP_OUTPUT, task.get('expected_output', '')) + self._set_attribute("crewai.task.name", task.get('description', '')) + self._set_attribute("crewai.task.type", "task") + self._set_attribute("crewai.task.input", task.get('context', '')) + self._set_attribute("crewai.task.expected_output", task.get('expected_output', '')) + + if 'description' in task: + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_INPUT, task.get('description', '')) + if 'output' in task: + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_OUTPUT, task.get('output', '')) + self._set_attribute("crewai.task.output", task.get('output', '')) + + if 'id' in task: + self._set_attribute("crewai.task.id", str(task.get('id', ''))) + + if 'status' in task: + self._set_attribute("crewai.task.status", task.get('status', '')) - self._set_attribute("crewai.task.id", task.get('id', '')) self._set_attribute("crewai.task.agent", task.get('agent', '')) self._set_attribute("crewai.task.human_input", task.get('human_input', '')) - self._set_attribute("crewai.task.output", task.get('output', '')) self._set_attribute("crewai.task.processed_by_agents", str(task.get('processed_by_agents', ''))) + + if 'tools' in task and task['tools']: + try: + tools = json.loads(task['tools']) + for i, tool in enumerate(tools): + self._set_attribute(MessageAttributes.TOOL_CALL_NAME.format(i=i), tool.get("name", "")) + self._set_attribute(MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=i), tool.get("description", "")) + except (json.JSONDecodeError, TypeError): + logger.warning(f"Failed to parse tools for task: {task.get('id', 'unknown')}") def _process_llm(self): """Process an LLM instance.""" llm = {} + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "llm") + for key, value in self.instance.__dict__.items(): if value is None: continue llm[key] = str(value) - # Set LLM attributes using our semantic conventions - self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, llm.get('model_name', '')) + model_name = llm.get('model_name', '') or llm.get('model', '') + self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, llm.get('temperature', '')) self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, llm.get('max_tokens', '')) self._set_attribute(SpanAttributes.LLM_REQUEST_TOP_P, llm.get('top_p', '')) + + if 'frequency_penalty' in llm: + self._set_attribute(SpanAttributes.LLM_REQUEST_FREQUENCY_PENALTY, llm.get('frequency_penalty', '')) + if 'presence_penalty' in llm: + self._set_attribute(SpanAttributes.LLM_REQUEST_PRESENCE_PENALTY, llm.get('presence_penalty', '')) + if 'streaming' in llm: + self._set_attribute(SpanAttributes.LLM_REQUEST_STREAMING, llm.get('streaming', '')) + + if 'api_key' in llm: + self._set_attribute("gen_ai.request.api_key_present", "true") + + if 'base_url' in llm: + self._set_attribute(SpanAttributes.LLM_OPENAI_API_BASE, llm.get('base_url', '')) + + if 'api_version' in llm: + self._set_attribute(SpanAttributes.LLM_OPENAI_API_VERSION, llm.get('api_version', '')) def _parse_agents(self, agents): """Parse agents into a list of dictionaries.""" - for agent in agents: - if agent is not None: + if not agents: + logger.debug("CrewAI: No agents to parse") + return + + agent_count = len(agents) + logger.debug(f"CrewAI: Parsing {agent_count} agents") + + # Pre-process all agents to collect their data first + agent_data_list = [] + + for idx, agent in enumerate(agents): + if agent is None: + logger.debug(f"CrewAI: Agent at index {idx} is None, skipping") + agent_data_list.append(None) + continue + + logger.debug(f"CrewAI: Processing agent at index {idx}") + try: agent_data = self._extract_agent_data(agent) - for key, value in agent_data.items(): - self._set_attribute(f"crewai.agent.{key}", value) - - def _parse_tasks(self, tasks): - """Parse tasks into a list of dictionaries.""" - for task in tasks: - if task is not None: - task_data = { - "agent": task.agent.role if task.agent else None, - "description": task.description, - "async_execution": task.async_execution, - "expected_output": task.expected_output, - "human_input": task.human_input, - "tools": task.tools, - "output_file": task.output_file, - } - for key, value in task_data.items(): - if value is not None: - self._set_attribute(f"crewai.task.{key}", str(value)) + agent_data_list.append(agent_data) + except Exception as e: + logger.error(f"CrewAI: Error extracting data for agent at index {idx}: {str(e)}") + agent_data_list.append(None) + + # Now set all attributes at once for each agent + for idx, agent_data in enumerate(agent_data_list): + if agent_data is None: + continue + + for key, value in agent_data.items(): + if key == "tools" and isinstance(value, list): + for tool_idx, tool in enumerate(value): + for tool_key, tool_value in tool.items(): + self._set_attribute(f"crewai.agents.{idx}.tools.{tool_idx}.{tool_key}", str(tool_value)) + else: + self._set_attribute(f"crewai.agents.{idx}.{key}", value) def _parse_llms(self, llms): """Parse LLMs into a list of dictionaries.""" - for llm in llms: + for idx, llm in enumerate(llms): if llm is not None: + model_name = getattr(llm, "model", None) or getattr(llm, "model_name", None) or "" llm_data = { + "model": model_name, "temperature": llm.temperature, "max_tokens": llm.max_tokens, "max_completion_tokens": llm.max_completion_tokens, @@ -169,14 +289,27 @@ def _parse_llms(self, llms): "base_url": llm.base_url, "api_version": llm.api_version, } + + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_MODEL}.{idx}", model_name) + if hasattr(llm, "temperature"): + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_TEMPERATURE}.{idx}", str(llm.temperature)) + if hasattr(llm, "max_tokens"): + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_MAX_TOKENS}.{idx}", str(llm.max_tokens)) + if hasattr(llm, "top_p"): + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_TOP_P}.{idx}", str(llm.top_p)) + for key, value in llm_data.items(): if value is not None: - self._set_attribute(f"crewai.llm.{key}", str(value)) + self._set_attribute(f"crewai.llms.{idx}.{key}", str(value)) def _extract_agent_data(self, agent): """Extract data from an agent.""" model = getattr(agent.llm, "model", None) or getattr(agent.llm, "model_name", None) or "" + tools_list = [] + if hasattr(agent, "tools") and agent.tools: + tools_list = _parse_tools(agent.tools) + return { "id": str(agent.id), "role": agent.role, @@ -186,7 +319,7 @@ def _extract_agent_data(self, agent): "config": agent.config, "verbose": agent.verbose, "allow_delegation": agent.allow_delegation, - "tools": agent.tools, + "tools": tools_list, "max_iter": agent.max_iter, "llm": str(model), } diff --git a/third_party/opentelemetry/instrumentation/crewai/instrumentation.py b/third_party/opentelemetry/instrumentation/crewai/instrumentation.py index 033f46419..3b07c97bf 100644 --- a/third_party/opentelemetry/instrumentation/crewai/instrumentation.py +++ b/third_party/opentelemetry/instrumentation/crewai/instrumentation.py @@ -1,24 +1,61 @@ import os import time import logging -from typing import Collection +from typing import Collection, Dict, List, Any +from contextlib import contextmanager from wrapt import wrap_function_wrapper -from opentelemetry.trace import SpanKind, get_tracer, Tracer +from opentelemetry.trace import SpanKind, get_tracer, Tracer, get_current_span from opentelemetry.trace.status import Status, StatusCode from opentelemetry.metrics import Histogram, Meter, get_meter from opentelemetry.instrumentation.utils import unwrap from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT from opentelemetry.instrumentation.crewai.version import __version__ -from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters +from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute -# Initialize logger for logging potential issues and operations +# Initialize logger logger = logging.getLogger(__name__) _instruments = ("crewai >= 0.70.0",) +# Global context to store tool executions by parent span ID +_tool_executions_by_agent = {} + +@contextmanager +def store_tool_execution(): + """Context manager to store tool execution details for later attachment to agent spans.""" + parent_span = get_current_span() + parent_span_id = getattr(parent_span.get_span_context(), "span_id", None) + + if parent_span_id: + if parent_span_id not in _tool_executions_by_agent: + _tool_executions_by_agent[parent_span_id] = [] + + tool_details = {} + + try: + yield tool_details + + if tool_details: + _tool_executions_by_agent[parent_span_id].append(tool_details) + finally: + pass + + +def attach_tool_executions_to_agent_span(span): + """Attach stored tool executions to the agent span.""" + span_id = getattr(span.get_span_context(), "span_id", None) + + if span_id and span_id in _tool_executions_by_agent: + for idx, tool_execution in enumerate(_tool_executions_by_agent[span_id]): + for key, value in tool_execution.items(): + if value is not None: + span.set_attribute(f"crewai.agent.tool_execution.{idx}.{key}", str(value)) + + del _tool_executions_by_agent[span_id] + class CrewAIInstrumentor(BaseInstrumentor): def instrumentation_dependencies(self) -> Collection[str]: @@ -52,12 +89,24 @@ def _instrument(self, **kwargs): "crewai.task", "Task.execute_sync", wrap_task_execute(tracer, duration_histogram, token_histogram, environment, application_name) ) wrap_function_wrapper("crewai.llm", "LLM.call", wrap_llm_call(tracer, duration_histogram, token_histogram, environment, application_name)) + + wrap_function_wrapper( + "crewai.utilities.tool_utils", "execute_tool_and_check_finality", + wrap_tool_execution(tracer, duration_histogram, environment, application_name) + ) + + wrap_function_wrapper( + "crewai.tools.tool_usage", "ToolUsage.use", + wrap_tool_usage(tracer, environment, application_name) + ) def _uninstrument(self, **kwargs): - unwrap("crewai.crew.Crew", "kickoff") - unwrap("crewai.agent.Agent", "execute_task") - unwrap("crewai.task.Task", "execute_sync") - unwrap("crewai.llm.LLM", "call") + unwrap("crewai.crew", "Crew.kickoff") + unwrap("crewai.agent", "Agent.execute_task") + unwrap("crewai.task", "Task.execute_sync") + unwrap("crewai.llm", "LLM.call") + unwrap("crewai.utilities.tool_utils", "execute_tool_and_check_finality") + unwrap("crewai.tools.tool_usage", "ToolUsage.use") def with_tracer_wrapper(func): @@ -76,6 +125,7 @@ def wrapper(wrapped, instance, args, kwargs): def wrap_kickoff( tracer: Tracer, duration_histogram: Histogram, token_histogram: Histogram, environment, application_name, wrapped, instance, args, kwargs ): + logger.debug(f"CrewAI: Starting workflow instrumentation for Crew with {len(getattr(instance, 'agents', []))} agents") with tracer.start_as_current_span( "crewai.workflow", kind=SpanKind.INTERNAL, @@ -84,15 +134,21 @@ def wrap_kickoff( }, ) as span: try: - # Set base span attributes span.set_attribute(TELEMETRY_SDK_NAME, "agentops") span.set_attribute(SERVICE_NAME, application_name) span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - # Process instance attributes - CrewAISpanAttributes(span=span, instance=instance) + logger.debug("CrewAI: Processing crew instance attributes") - # Execute the wrapped function + # First set general crew attributes but skip agent processing + crew_attrs = CrewAISpanAttributes(span=span, instance=instance, skip_agent_processing=True) + + # Prioritize agent processing before task execution + if hasattr(instance, 'agents') and instance.agents: + logger.debug(f"CrewAI: Explicitly processing {len(instance.agents)} agents before task execution") + crew_attrs._parse_agents(instance.agents) + + logger.debug("CrewAI: Executing wrapped crew kickoff function") result = wrapped(*args, **kwargs) if result: @@ -100,9 +156,116 @@ def wrap_kickoff( span.set_attribute(f"crewai.{class_name.lower()}.result", str(result)) span.set_status(Status(StatusCode.OK)) if class_name == "Crew": - for attr in ["tasks_output", "token_usage", "usage_metrics"]: - if hasattr(result, attr): - span.set_attribute(f"crewai.crew.{attr}", str(getattr(result, attr))) + if hasattr(result, "usage_metrics"): + span.set_attribute("crewai.crew.usage_metrics", str(getattr(result, "usage_metrics"))) + + if hasattr(result, "tasks_output") and result.tasks_output: + span.set_attribute("crewai.crew.tasks_output", str(result.tasks_output)) + + try: + task_details_by_description = {} + if hasattr(instance, "tasks"): + for task in instance.tasks: + if task is not None: + agent_id = "" + agent_role = "" + if hasattr(task, "agent") and task.agent: + agent_id = str(getattr(task.agent, "id", "")) + agent_role = getattr(task.agent, "role", "") + + tools = [] + if hasattr(task, "tools") and task.tools: + for tool in task.tools: + tool_info = {} + if hasattr(tool, "name"): + tool_info["name"] = tool.name + if hasattr(tool, "description"): + tool_info["description"] = tool.description + if tool_info: + tools.append(tool_info) + + task_details_by_description[task.description] = { + "agent_id": agent_id, + "agent_role": agent_role, + "async_execution": getattr(task, "async_execution", False), + "human_input": getattr(task, "human_input", False), + "output_file": getattr(task, "output_file", ""), + "tools": tools + } + + for idx, task_output in enumerate(result.tasks_output): + task_prefix = f"crewai.crew.tasks.{idx}" + + task_attrs = { + "description": getattr(task_output, "description", ""), + "name": getattr(task_output, "name", ""), + "expected_output": getattr(task_output, "expected_output", ""), + "summary": getattr(task_output, "summary", ""), + "raw": getattr(task_output, "raw", ""), + "agent": getattr(task_output, "agent", ""), + "output_format": str(getattr(task_output, "output_format", "")), + } + + for attr_name, attr_value in task_attrs.items(): + if attr_value: + if attr_name == "raw" and len(str(attr_value)) > 1000: + attr_value = str(attr_value)[:997] + "..." + span.set_attribute(f"{task_prefix}.{attr_name}", str(attr_value)) + + span.set_attribute(f"{task_prefix}.status", "completed") + span.set_attribute(f"{task_prefix}.id", str(idx)) + + description = task_attrs.get("description", "") + if description and description in task_details_by_description: + details = task_details_by_description[description] + + span.set_attribute(f"{task_prefix}.agent_id", details["agent_id"]) + span.set_attribute(f"{task_prefix}.async_execution", str(details["async_execution"])) + span.set_attribute(f"{task_prefix}.human_input", str(details["human_input"])) + + if details["output_file"]: + span.set_attribute(f"{task_prefix}.output_file", details["output_file"]) + + for tool_idx, tool in enumerate(details["tools"]): + for tool_key, tool_value in tool.items(): + span.set_attribute(f"{task_prefix}.tools.{tool_idx}.{tool_key}", str(tool_value)) + except Exception as ex: + logger.warning(f"Failed to parse task outputs: {ex}") + + if hasattr(result, "token_usage"): + token_usage = str(getattr(result, "token_usage")) + span.set_attribute("crewai.crew.token_usage", token_usage) + + try: + metrics = {} + for item in token_usage.split(): + if "=" in item: + key, value = item.split("=") + try: + metrics[key] = int(value) + except ValueError: + metrics[key] = value + + if "total_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_TOTAL_TOKENS, metrics["total_tokens"]) + if "prompt_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_PROMPT_TOKENS, metrics["prompt_tokens"]) + if "completion_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, metrics["completion_tokens"]) + if "cached_prompt_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS, metrics["cached_prompt_tokens"]) + if "successful_requests" in metrics: + span.set_attribute("crewai.crew.successful_requests", metrics["successful_requests"]) + + if "prompt_tokens" in metrics and "completion_tokens" in metrics and metrics["prompt_tokens"] > 0: + efficiency = metrics["completion_tokens"] / metrics["prompt_tokens"] + span.set_attribute("crewai.crew.token_efficiency", f"{efficiency:.4f}") + + if "cached_prompt_tokens" in metrics and "prompt_tokens" in metrics and metrics["prompt_tokens"] > 0: + cache_ratio = metrics["cached_prompt_tokens"] / metrics["prompt_tokens"] + span.set_attribute("crewai.crew.cache_efficiency", f"{cache_ratio:.4f}") + except Exception as ex: + logger.warning(f"Failed to parse token usage metrics: {ex}") return result except Exception as ex: span.set_status(Status(StatusCode.ERROR, str(ex))) @@ -121,17 +284,16 @@ def wrap_agent_execute_task(tracer, duration_histogram, token_histogram, environ }, ) as span: try: - # Set base span attributes span.set_attribute(TELEMETRY_SDK_NAME, "agentops") span.set_attribute(SERVICE_NAME, application_name) span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - # Process instance attributes CrewAISpanAttributes(span=span, instance=instance) - # Execute the wrapped function result = wrapped(*args, **kwargs) + attach_tool_executions_to_agent_span(span) + if token_histogram and hasattr(instance, "_token_process"): token_histogram.record( instance._token_process.get_summary().prompt_tokens, @@ -174,15 +336,12 @@ def wrap_task_execute(tracer, duration_histogram, token_histogram, environment, }, ) as span: try: - # Set base span attributes span.set_attribute(TELEMETRY_SDK_NAME, "agentops") span.set_attribute(SERVICE_NAME, application_name) span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - # Process instance attributes CrewAISpanAttributes(span=span, instance=instance) - # Execute the wrapped function result = wrapped(*args, **kwargs) set_span_attribute(span, SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(result)) @@ -200,15 +359,12 @@ def wrap_llm_call(tracer, duration_histogram, token_histogram, environment, appl with tracer.start_as_current_span(f"{llm}.llm", kind=SpanKind.CLIENT, attributes={}) as span: start_time = time.time() try: - # Set base span attributes span.set_attribute(TELEMETRY_SDK_NAME, "agentops") span.set_attribute(SERVICE_NAME, application_name) span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - # Process instance attributes CrewAISpanAttributes(span=span, instance=instance) - # Execute the wrapped function result = wrapped(*args, **kwargs) if duration_histogram: @@ -229,6 +385,144 @@ def wrap_llm_call(tracer, duration_histogram, token_histogram, environment, appl raise +def wrap_tool_execution(tracer, duration_histogram, environment, application_name): + """Wrapper for tool execution function.""" + def wrapper(wrapped, instance, args, kwargs): + agent_action = args[0] if args else None + tools = args[1] if len(args) > 1 else [] + + if not agent_action: + return wrapped(*args, **kwargs) + + tool_name = getattr(agent_action, "tool", "unknown_tool") + tool_input = getattr(agent_action, "tool_input", "") + + with store_tool_execution() as tool_details: + tool_details["name"] = tool_name + tool_details["parameters"] = str(tool_input) + + matching_tool = next((tool for tool in tools if hasattr(tool, "name") and tool.name == tool_name), None) + if matching_tool and hasattr(matching_tool, "description"): + tool_details["description"] = str(matching_tool.description) + + with tracer.start_as_current_span( + f"{tool_name}.tool", + kind=SpanKind.CLIENT, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: "tool", + ToolAttributes.TOOL_NAME: tool_name, + ToolAttributes.TOOL_PARAMETERS: str(tool_input), + }, + ) as span: + start_time = time.time() + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + if matching_tool and hasattr(matching_tool, "description"): + span.set_attribute(ToolAttributes.TOOL_DESCRIPTION, str(matching_tool.description)) + + result = wrapped(*args, **kwargs) + + if duration_histogram: + duration = time.time() - start_time + duration_histogram.record( + duration, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + ToolAttributes.TOOL_NAME: tool_name, + }, + ) + + if hasattr(result, "result"): + tool_result = str(result.result) + span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result) + tool_details["result"] = tool_result + + tool_status = "success" if not hasattr(result, "error") or not result.error else "error" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + + if hasattr(result, "error") and result.error: + tool_details["error"] = str(result.error) + + duration = time.time() - start_time + tool_details["duration"] = f"{duration:.3f}" + + span.set_status(Status(StatusCode.OK)) + return result + except Exception as ex: + tool_status = "error" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + tool_details["error"] = str(ex) + + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error(f"Error in tool execution trace: {ex}") + raise + + return wrapper + + +def wrap_tool_usage(tracer, environment, application_name): + """Wrapper for ToolUsage.use method.""" + def wrapper(wrapped, instance, args, kwargs): + calling = args[0] if args else None + tool_string = args[1] if len(args) > 1 else "" + + if not calling: + return wrapped(*args, **kwargs) + + tool_name = getattr(calling, "tool_name", "unknown_tool") + + with store_tool_execution() as tool_details: + tool_details["name"] = tool_name + + if hasattr(calling, "arguments") and calling.arguments: + tool_details["parameters"] = str(calling.arguments) + + with tracer.start_as_current_span( + f"{tool_name}.tool_usage", + kind=SpanKind.INTERNAL, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: "tool.usage", + ToolAttributes.TOOL_NAME: tool_name, + }, + ) as span: + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + if hasattr(calling, "arguments") and calling.arguments: + span.set_attribute(ToolAttributes.TOOL_PARAMETERS, str(calling.arguments)) + + result = wrapped(*args, **kwargs) + + tool_result = str(result) + span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result) + tool_details["result"] = tool_result + + tool_status = "success" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + + span.set_status(Status(StatusCode.OK)) + return result + except Exception as ex: + tool_status = "error" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + tool_details["error"] = str(ex) + + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error(f"Error in tool usage trace: {ex}") + raise + + return wrapper + + def is_metrics_enabled() -> bool: return (os.getenv("AGENTOPS_METRICS_ENABLED") or "true").lower() == "true" From 921f0eca46d08ff9beb84172b70c5de22b6a6c29 Mon Sep 17 00:00:00 2001 From: Dwij Patel Date: Thu, 24 Apr 2025 01:44:15 +0530 Subject: [PATCH 3/4] transfer crew ai from third party --- agentops/instrumentation/__init__.py | 2 +- agentops/instrumentation/crewai/LICENSE | 201 +++++++ agentops/instrumentation/crewai/NOTICE.md | 10 + agentops/instrumentation/crewai/__init__.py | 6 + .../crewai/crewai_span_attributes.py | 305 +++++++++++ .../instrumentation/crewai/instrumentation.py | 492 ++++++++++++++++++ agentops/instrumentation/crewai/version.py | 1 + 7 files changed, 1016 insertions(+), 1 deletion(-) create mode 100644 agentops/instrumentation/crewai/LICENSE create mode 100644 agentops/instrumentation/crewai/NOTICE.md create mode 100644 agentops/instrumentation/crewai/__init__.py create mode 100644 agentops/instrumentation/crewai/crewai_span_attributes.py create mode 100644 agentops/instrumentation/crewai/instrumentation.py create mode 100644 agentops/instrumentation/crewai/version.py diff --git a/agentops/instrumentation/__init__.py b/agentops/instrumentation/__init__.py index b9cdcb226..6f75cc65c 100644 --- a/agentops/instrumentation/__init__.py +++ b/agentops/instrumentation/__init__.py @@ -63,7 +63,7 @@ def get_instance(self) -> BaseInstrumentor: provider_import_name="anthropic", ), InstrumentorLoader( - module_name="opentelemetry.instrumentation.crewai", + module_name="agentops.instrumentation.crewai", class_name="CrewAIInstrumentor", provider_import_name="crewai", ), diff --git a/agentops/instrumentation/crewai/LICENSE b/agentops/instrumentation/crewai/LICENSE new file mode 100644 index 000000000..6bfeb3c2c --- /dev/null +++ b/agentops/instrumentation/crewai/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 openllmetry + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/agentops/instrumentation/crewai/NOTICE.md b/agentops/instrumentation/crewai/NOTICE.md new file mode 100644 index 000000000..ff234ba5c --- /dev/null +++ b/agentops/instrumentation/crewai/NOTICE.md @@ -0,0 +1,10 @@ +This package contains code derived from the OpenLLMetry project, which is licensed under the Apache License, Version 2.0. + +Original repository: https://github.com/traceloop/openllmetry + +Copyright notice from the original project: +Copyright (c) Traceloop (https://traceloop.com) + +The Apache 2.0 license can be found in the LICENSE file in this directory. + +This code has been modified and adapted for use in the AgentOps project. \ No newline at end of file diff --git a/agentops/instrumentation/crewai/__init__.py b/agentops/instrumentation/crewai/__init__.py new file mode 100644 index 000000000..8414f2b4e --- /dev/null +++ b/agentops/instrumentation/crewai/__init__.py @@ -0,0 +1,6 @@ +"""AgentOps CrewAI instrumentation""" + +from agentops.instrumentation.crewai.version import __version__ +from agentops.instrumentation.crewai.instrumentation import CrewAIInstrumentor + +__all__ = ["CrewAIInstrumentor", "__version__"] \ No newline at end of file diff --git a/agentops/instrumentation/crewai/crewai_span_attributes.py b/agentops/instrumentation/crewai/crewai_span_attributes.py new file mode 100644 index 000000000..ceedc8835 --- /dev/null +++ b/agentops/instrumentation/crewai/crewai_span_attributes.py @@ -0,0 +1,305 @@ +"""OpenTelemetry instrumentation for CrewAI.""" + +import json +import logging +from typing import Any +from opentelemetry.trace import Span + +from agentops.semconv.span_attributes import SpanAttributes +from agentops.semconv.agent import AgentAttributes +from agentops.semconv.tool import ToolAttributes +from agentops.semconv.message import MessageAttributes +from agentops.instrumentation.common.attributes import AttributeMap + +# Initialize logger for logging potential issues and operations +logger = logging.getLogger(__name__) + +def _parse_tools(tools): + """Parse tools into a JSON string with name and description.""" + result = [] + for tool in tools: + res = {} + if hasattr(tool, "name") and tool.name is not None: + res["name"] = tool.name + if hasattr(tool, "description") and tool.description is not None: + res["description"] = tool.description + if res: + result.append(res) + return result + +def set_span_attribute(span: Span, key: str, value: Any) -> None: + """Set a single attribute on a span.""" + if value is not None and value != "": + if hasattr(value, "__str__"): + value = str(value) + span.set_attribute(key, value) + + +class CrewAISpanAttributes: + """Manages span attributes for CrewAI instrumentation.""" + + def __init__(self, span: Span, instance, skip_agent_processing=False) -> None: + self.span = span + self.instance = instance + self.skip_agent_processing = skip_agent_processing + self.process_instance() + + def process_instance(self): + """Process the instance based on its type.""" + instance_type = self.instance.__class__.__name__ + self._set_attribute(SpanAttributes.LLM_SYSTEM, "crewai") + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_NAME, instance_type) + + method_mapping = { + "Crew": self._process_crew, + "Agent": self._process_agent, + "Task": self._process_task, + "LLM": self._process_llm, + } + method = method_mapping.get(instance_type) + if method: + method() + + def _process_crew(self): + """Process a Crew instance.""" + crew_id = getattr(self.instance, "id", "") + self._set_attribute("crewai.crew.id", str(crew_id)) + self._set_attribute("crewai.crew.type", "crewai.crew") + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "workflow") + + logger.debug(f"CrewAI: Processing crew with id {crew_id}") + + for key, value in self.instance.__dict__.items(): + if value is None: + continue + + if key == "tasks": + if isinstance(value, list): + self._set_attribute("crewai.crew.max_turns", str(len(value))) + logger.debug(f"CrewAI: Found {len(value)} tasks") + elif key == "agents": + if isinstance(value, list): + logger.debug(f"CrewAI: Found {len(value)} agents in crew") + + if not self.skip_agent_processing: + self._parse_agents(value) + elif key == "llms": + self._parse_llms(value) + elif key == "result": + self._set_attribute("crewai.crew.final_output", str(value)) + self._set_attribute("crewai.crew.output", str(value)) + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(value)) + else: + self._set_attribute(f"crewai.crew.{key}", str(value)) + + def _process_agent(self): + """Process an Agent instance.""" + agent = {} + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "agent") + + for key, value in self.instance.__dict__.items(): + if key == "tools": + parsed_tools = _parse_tools(value) + for i, tool in enumerate(parsed_tools): + tool_prefix = f"crewai.agent.tool.{i}." + for tool_key, tool_value in tool.items(): + self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) + + agent[key] = json.dumps(parsed_tools) + + if value is None: + continue + + if key != "tools": + agent[key] = str(value) + + self._set_attribute(AgentAttributes.AGENT_ID, agent.get('id', '')) + self._set_attribute(AgentAttributes.AGENT_ROLE, agent.get('role', '')) + self._set_attribute(AgentAttributes.AGENT_NAME, agent.get('name', '')) + self._set_attribute(AgentAttributes.AGENT_TOOLS, agent.get('tools', '')) + + if 'reasoning' in agent: + self._set_attribute(AgentAttributes.AGENT_REASONING, agent.get('reasoning', '')) + + if 'goal' in agent: + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_INPUT, agent.get('goal', '')) + + self._set_attribute("crewai.agent.goal", agent.get('goal', '')) + self._set_attribute("crewai.agent.backstory", agent.get('backstory', '')) + self._set_attribute("crewai.agent.cache", agent.get('cache', '')) + self._set_attribute("crewai.agent.allow_delegation", agent.get('allow_delegation', '')) + self._set_attribute("crewai.agent.allow_code_execution", agent.get('allow_code_execution', '')) + self._set_attribute("crewai.agent.max_retry_limit", agent.get('max_retry_limit', '')) + + if hasattr(self.instance, "llm") and self.instance.llm is not None: + model_name = getattr(self.instance.llm, "model", None) or getattr(self.instance.llm, "model_name", None) or "" + temp = getattr(self.instance.llm, "temperature", None) + max_tokens = getattr(self.instance.llm, "max_tokens", None) + top_p = getattr(self.instance.llm, "top_p", None) + + self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) + if temp is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, str(temp)) + if max_tokens is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, str(max_tokens)) + if top_p is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_TOP_P, str(top_p)) + + self._set_attribute("crewai.agent.llm", str(model_name)) + self._set_attribute(AgentAttributes.AGENT_MODELS, str(model_name)) + + def _process_task(self): + """Process a Task instance.""" + task = {} + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "workflow.step") + + for key, value in self.instance.__dict__.items(): + if value is None: + continue + if key == "tools": + parsed_tools = _parse_tools(value) + for i, tool in enumerate(parsed_tools): + tool_prefix = f"crewai.task.tool.{i}." + for tool_key, tool_value in tool.items(): + self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) + + task[key] = json.dumps(parsed_tools) + + elif key == "agent": + task[key] = value.role if value else None + if value: + agent_id = getattr(value, "id", "") + self._set_attribute(AgentAttributes.FROM_AGENT, str(agent_id)) + else: + task[key] = str(value) + + self._set_attribute("crewai.task.name", task.get('description', '')) + self._set_attribute("crewai.task.type", "task") + self._set_attribute("crewai.task.input", task.get('context', '')) + self._set_attribute("crewai.task.expected_output", task.get('expected_output', '')) + + if 'description' in task: + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_INPUT, task.get('description', '')) + if 'output' in task: + self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_OUTPUT, task.get('output', '')) + self._set_attribute("crewai.task.output", task.get('output', '')) + + if 'id' in task: + self._set_attribute("crewai.task.id", str(task.get('id', ''))) + + if 'status' in task: + self._set_attribute("crewai.task.status", task.get('status', '')) + + self._set_attribute("crewai.task.agent", task.get('agent', '')) + self._set_attribute("crewai.task.human_input", task.get('human_input', '')) + self._set_attribute("crewai.task.processed_by_agents", str(task.get('processed_by_agents', ''))) + + if 'tools' in task and task['tools']: + try: + tools = json.loads(task['tools']) + for i, tool in enumerate(tools): + self._set_attribute(MessageAttributes.TOOL_CALL_NAME.format(i=i), tool.get("name", "")) + self._set_attribute(MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=i), tool.get("description", "")) + except: + logger.warning(f"Failed to parse tools json: {task['tools']}") + + def _process_llm(self): + """Process an LLM instance.""" + self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "llm") + + # Parse model parameters + model_name = getattr(self.instance, "model", None) or getattr(self.instance, "model_name", None) or "" + temp = getattr(self.instance, "temperature", None) + max_tokens = getattr(self.instance, "max_tokens", None) + + self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) + if temp is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, str(temp)) + if max_tokens is not None: + self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, str(max_tokens)) + + # Add provider info based on class attributes or parent class + if hasattr(self.instance, 'provider'): + provider = self.instance.provider + self._set_attribute(SpanAttributes.LLM_PROVIDER, provider) + + # Set additional LLM attributes + for key, value in self.instance.__dict__.items(): + if value is None: + continue + self._set_attribute(f"crewai.llm.{key}", str(value)) + + def _parse_agents(self, agents): + """Process a list of agents for a crew instance.""" + # Track agents in an array + for i, agent in enumerate(agents): + if not agent: + continue + + agent_data = self._extract_agent_data(agent) + + # Set span attributes for each agent + agent_prefix = f"crewai.crew.agent.{i}." + for key, value in agent_data.items(): + self._set_attribute(f"{agent_prefix}{key}", value) + + # Process tools if available + if hasattr(agent, "tools") and agent.tools: + parsed_tools = _parse_tools(agent.tools) + for j, tool in enumerate(parsed_tools): + tool_prefix = f"{agent_prefix}tool.{j}." + for tool_key, tool_value in tool.items(): + self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) + + # Process LLM if available + if hasattr(agent, "llm") and agent.llm: + llm = agent.llm + if hasattr(llm, "model") and llm.model: + self._set_attribute(f"{agent_prefix}llm.model", str(llm.model)) + elif hasattr(llm, "model_name") and llm.model_name: + self._set_attribute(f"{agent_prefix}llm.model", str(llm.model_name)) + + def _parse_llms(self, llms): + """Process a dictionary of LLMs for a crew instance.""" + if not llms or not isinstance(llms, dict): + return + + # Track LLMs in an array + for i, (role, llm) in enumerate(llms.items()): + if not llm: + continue + + # Set basic LLM information + llm_prefix = f"crewai.crew.llm.{i}." + self._set_attribute(f"{llm_prefix}role", str(role)) + + # Extract model information + if hasattr(llm, "model") and llm.model: + self._set_attribute(f"{llm_prefix}model", str(llm.model)) + elif hasattr(llm, "model_name") and llm.model_name: + self._set_attribute(f"{llm_prefix}model", str(llm.model_name)) + + # Extract other important LLM parameters + for key in ["temperature", "max_tokens", "top_p"]: + if hasattr(llm, key) and getattr(llm, key) is not None: + self._set_attribute(f"{llm_prefix}{key}", str(getattr(llm, key))) + + def _extract_agent_data(self, agent): + """Extract relevant data from an agent instance.""" + agent_data = {} + + # Extract basic agent information + for key in ["id", "role", "name", "goal", "backstory"]: + if hasattr(agent, key) and getattr(agent, key): + agent_data[key] = str(getattr(agent, key)) + + # Extract configuration settings + for key in ["allow_delegation", "allow_code_execution", "max_iter", "max_rpm", "verbose"]: + if hasattr(agent, key) and getattr(agent, key) is not None: + agent_data[key] = str(getattr(agent, key)) + + return agent_data + + def _set_attribute(self, key, value): + """Set an attribute on the span with validation.""" + set_span_attribute(self.span, key, value) \ No newline at end of file diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py new file mode 100644 index 000000000..aa09f9b69 --- /dev/null +++ b/agentops/instrumentation/crewai/instrumentation.py @@ -0,0 +1,492 @@ +import os +import time +import logging +from typing import Collection, Dict, List, Any +from contextlib import contextmanager + +from wrapt import wrap_function_wrapper +from opentelemetry.trace import SpanKind, get_tracer, Tracer, get_current_span +from opentelemetry.trace.status import Status, StatusCode +from opentelemetry.metrics import Histogram, Meter, get_meter +from opentelemetry.instrumentation.utils import unwrap +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT + +from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes +from agentops.semconv.agent import AgentAttributes +from agentops.semconv.message import MessageAttributes +from agentops.instrumentation.common.wrappers import WrapConfig, wrap +from agentops.instrumentation.common.attributes import AttributeMap +from agentops.instrumentation.crewai.version import __version__ +from agentops.instrumentation.crewai.crewai_span_attributes import CrewAISpanAttributes, set_span_attribute + +# Initialize logger +logger = logging.getLogger(__name__) + +_instruments = ("crewai >= 0.70.0",) + +# Global context to store tool executions by parent span ID +_tool_executions_by_agent = {} + +@contextmanager +def store_tool_execution(): + """Context manager to store tool execution details for later attachment to agent spans.""" + parent_span = get_current_span() + parent_span_id = getattr(parent_span.get_span_context(), "span_id", None) + + if parent_span_id: + if parent_span_id not in _tool_executions_by_agent: + _tool_executions_by_agent[parent_span_id] = [] + + tool_details = {} + + try: + yield tool_details + + if tool_details: + _tool_executions_by_agent[parent_span_id].append(tool_details) + finally: + pass + + +def attach_tool_executions_to_agent_span(span): + """Attach stored tool executions to the agent span.""" + span_id = getattr(span.get_span_context(), "span_id", None) + + if span_id and span_id in _tool_executions_by_agent: + for idx, tool_execution in enumerate(_tool_executions_by_agent[span_id]): + for key, value in tool_execution.items(): + if value is not None: + span.set_attribute(f"crewai.agent.tool_execution.{idx}.{key}", str(value)) + + del _tool_executions_by_agent[span_id] + + +class CrewAIInstrumentor(BaseInstrumentor): + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + application_name = kwargs.get("application_name", "default_application") + environment = kwargs.get("environment", "default_environment") + tracer_provider = kwargs.get("tracer_provider") + tracer = get_tracer(__name__, __version__, tracer_provider) + + meter_provider = kwargs.get("meter_provider") + meter = get_meter(__name__, __version__, meter_provider) + + if is_metrics_enabled(): + ( + token_histogram, + duration_histogram, + ) = _create_metrics(meter) + else: + ( + token_histogram, + duration_histogram, + ) = (None, None) + + # Use WrapConfig for consistent instrumentation patterns + wrap_configs = [ + WrapConfig( + trace_name="crewai.workflow", + package="crewai.crew", + class_name="Crew", + method_name="kickoff", + handler=self._create_crew_handler(tracer, duration_histogram, token_histogram, environment, application_name), + span_kind=SpanKind.INTERNAL + ), + WrapConfig( + trace_name="crewai.agent.execute_task", + package="crewai.agent", + class_name="Agent", + method_name="execute_task", + handler=self._create_agent_handler(tracer, duration_histogram, token_histogram, environment, application_name), + span_kind=SpanKind.INTERNAL + ), + WrapConfig( + trace_name="crewai.task.execute", + package="crewai.task", + class_name="Task", + method_name="execute_sync", + handler=self._create_task_handler(tracer, duration_histogram, token_histogram, environment, application_name), + span_kind=SpanKind.INTERNAL + ), + WrapConfig( + trace_name="crewai.llm.call", + package="crewai.llm", + class_name="LLM", + method_name="call", + handler=self._create_llm_handler(tracer, duration_histogram, token_histogram, environment, application_name), + span_kind=SpanKind.CLIENT + ), + ] + + # Apply all wrap configurations + for wrap_config in wrap_configs: + wrap(wrap_config, tracer) + + # Wrap tool execution functions using original pattern for backward compatibility + wrap_function_wrapper( + "crewai.utilities.tool_utils", "execute_tool_and_check_finality", + wrap_tool_execution(tracer, duration_histogram, environment, application_name) + ) + + wrap_function_wrapper( + "crewai.tools.tool_usage", "ToolUsage.use", + wrap_tool_usage(tracer, environment, application_name) + ) + + def _uninstrument(self, **kwargs): + unwrap("crewai.crew", "Crew.kickoff") + unwrap("crewai.agent", "Agent.execute_task") + unwrap("crewai.task", "Task.execute_sync") + unwrap("crewai.llm", "LLM.call") + unwrap("crewai.utilities.tool_utils", "execute_tool_and_check_finality") + unwrap("crewai.tools.tool_usage", "ToolUsage.use") + + def _create_crew_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): + def handler(args=None, kwargs=None, return_value=None): + attributes = dict() + + if args and len(args) > 0: + instance = args[0] + logger.debug(f"CrewAI: Starting workflow instrumentation for Crew with {len(getattr(instance, 'agents', []))} agents") + + # Set core span attributes + attributes[TELEMETRY_SDK_NAME] = "agentops" + attributes[SERVICE_NAME] = application_name + attributes[DEPLOYMENT_ENVIRONMENT] = environment + attributes[SpanAttributes.LLM_SYSTEM] = "crewai" + + # Process crew instance but skip agent processing at this point + if hasattr(instance, 'agents') and instance.agents: + logger.debug(f"CrewAI: Found {len(instance.agents)} agents in crew") + + if return_value: + logger.debug("CrewAI: Processing crew return value") + if hasattr(return_value, "usage_metrics"): + attributes["crewai.crew.usage_metrics"] = str(getattr(return_value, "usage_metrics")) + + if hasattr(return_value, "tasks_output") and return_value.tasks_output: + attributes["crewai.crew.tasks_output"] = str(return_value.tasks_output) + + return attributes + return handler + + def _create_agent_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): + def handler(args=None, kwargs=None, return_value=None): + attributes = dict() + + # Set base attributes + attributes[TELEMETRY_SDK_NAME] = "agentops" + attributes[SERVICE_NAME] = application_name + attributes[DEPLOYMENT_ENVIRONMENT] = environment + attributes[SpanAttributes.LLM_SYSTEM] = "crewai" + + if args and len(args) > 0: + instance = args[0] + + # Extract agent information + if hasattr(instance, 'id'): + attributes[AgentAttributes.AGENT_ID] = str(instance.id) + + if hasattr(instance, 'role'): + attributes[AgentAttributes.AGENT_ROLE] = str(instance.role) + + if hasattr(instance, 'name'): + attributes[AgentAttributes.AGENT_NAME] = str(instance.name) + + # Process task if available + if len(args) > 1 and args[1]: + task = args[1] + if hasattr(task, 'description'): + attributes["crewai.task.description"] = str(task.description) + attributes[SpanAttributes.AGENTOPS_ENTITY_INPUT] = str(task.description) + + if hasattr(task, 'expected_output'): + attributes["crewai.task.expected_output"] = str(task.expected_output) + + # Process return value (task output) + if return_value: + if isinstance(return_value, str): + attributes["crewai.agent.task_output"] = return_value + attributes[SpanAttributes.AGENTOPS_ENTITY_OUTPUT] = return_value + + return attributes + return handler + + def _create_task_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): + def handler(args=None, kwargs=None, return_value=None): + attributes = dict() + + # Set base attributes + attributes[TELEMETRY_SDK_NAME] = "agentops" + attributes[SERVICE_NAME] = application_name + attributes[DEPLOYMENT_ENVIRONMENT] = environment + attributes[SpanAttributes.LLM_SYSTEM] = "crewai" + attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = "workflow.step" + + if args and len(args) > 0: + instance = args[0] + + # Process task attributes + if hasattr(instance, 'description'): + attributes["crewai.task.description"] = str(instance.description) + attributes[SpanAttributes.AGENTOPS_ENTITY_INPUT] = str(instance.description) + + if hasattr(instance, 'expected_output'): + attributes["crewai.task.expected_output"] = str(instance.expected_output) + + if hasattr(instance, 'agent') and instance.agent: + agent = instance.agent + if hasattr(agent, 'id'): + attributes[AgentAttributes.FROM_AGENT] = str(agent.id) + if hasattr(agent, 'role'): + attributes["crewai.task.agent.role"] = str(agent.role) + + # Process return value + if return_value: + attributes["crewai.task.output"] = str(return_value) + attributes[SpanAttributes.AGENTOPS_ENTITY_OUTPUT] = str(return_value) + + return attributes + return handler + + def _create_llm_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): + def handler(args=None, kwargs=None, return_value=None): + attributes = dict() + + # Set base attributes + attributes[TELEMETRY_SDK_NAME] = "agentops" + attributes[SERVICE_NAME] = application_name + attributes[DEPLOYMENT_ENVIRONMENT] = environment + attributes[SpanAttributes.LLM_SYSTEM] = "crewai" + attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = "llm" + + if args and len(args) > 0: + instance = args[0] + + # Extract LLM model information + model_name = getattr(instance, "model", None) or getattr(instance, "model_name", None) or "" + attributes[SpanAttributes.LLM_REQUEST_MODEL] = model_name + + # Extract LLM parameters + for param_name, attr_name in [ + ("temperature", SpanAttributes.LLM_REQUEST_TEMPERATURE), + ("max_tokens", SpanAttributes.LLM_REQUEST_MAX_TOKENS), + ("top_p", SpanAttributes.LLM_REQUEST_TOP_P) + ]: + if hasattr(instance, param_name) and getattr(instance, param_name) is not None: + attributes[attr_name] = str(getattr(instance, param_name)) + + # Extract request content using MessageAttributes for indexed values + if len(args) > 1: + prompt = args[1] + # Use MessageAttributes for prompt instead of SpanAttributes + attributes[MessageAttributes.CONTENT.format(i=0)] = str(prompt) + attributes[MessageAttributes.ROLE.format(i=0)] = "user" + # Keep the standard attributes for backward compatibility + attributes[SpanAttributes.LLM_REQUEST_PROMPT] = str(prompt) + attributes[SpanAttributes.LLM_REQUEST_CONTENT] = str(prompt) + + # Process response using MessageAttributes for completion + if return_value: + # Use MessageAttributes for completion + attributes[MessageAttributes.CONTENT.format(i=1)] = str(return_value) + attributes[MessageAttributes.ROLE.format(i=1)] = "assistant" + # Keep standard attribute for backward compatibility + attributes[SpanAttributes.LLM_RESPONSE_CONTENT] = str(return_value) + + return attributes + return handler + + +def wrap_tool_execution(tracer, duration_histogram, environment, application_name): + """Wrapper for tool execution functions.""" + + def wrapper(wrapped, instance, args, kwargs): + logger.debug("CrewAI: Starting tool execution instrumentation") + + tool_name = "" + tool_description = "" + input_str = "" + agent_id = "" + agent_role = "" + + # Extract tool info from args + if len(args) >= 1 and args[0]: + tool = args[0] + if hasattr(tool, "name"): + tool_name = tool.name + if hasattr(tool, "description"): + tool_description = tool.description + + # Extract input from args + if len(args) >= 2: + input_str = str(args[1]) + + # Extract agent from args + if len(args) >= 3 and args[2]: + agent = args[2] + if hasattr(agent, "id"): + agent_id = str(agent.id) + if hasattr(agent, "role"): + agent_role = str(agent.role) + + with tracer.start_as_current_span( + "crewai.tool.execution", + kind=SpanKind.INTERNAL, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + TELEMETRY_SDK_NAME: "agentops", + SERVICE_NAME: application_name, + DEPLOYMENT_ENVIRONMENT: environment, + SpanAttributes.AGENTOPS_SPAN_KIND: "tool", + ToolAttributes.TOOL_NAME: tool_name, + ToolAttributes.TOOL_DESCRIPTION: tool_description, + ToolAttributes.TOOL_INPUT: input_str, + AgentAttributes.FROM_AGENT: agent_id, + "crewai.agent.role": agent_role, + }, + ) as span: + try: + # Capture start time for duration measurement + start_time = time.time() + + # Execute the wrapped function + result = wrapped(*args, **kwargs) + + # Calculate duration in milliseconds + end_time = time.time() + duration_ms = (end_time - start_time) * 1000 + + # Store duration in span + span.set_attribute("crewai.tool.duration_ms", str(duration_ms)) + + # Record duration in histogram if available + if duration_histogram: + duration_histogram.record(duration_ms) + + # Store result in span - use MessageAttributes + if result: + span.set_attribute(ToolAttributes.TOOL_OUTPUT, str(result)) + # Add message format for tools as well + span.set_attribute(MessageAttributes.TOOL_CALL_RESPONSE.format(i=0), str(result)) + + # Store tool execution details for later attachment to agent span + with store_tool_execution() as tool_details: + tool_details["name"] = tool_name + tool_details["description"] = tool_description + tool_details["input"] = input_str + tool_details["output"] = str(result) if result else "" + tool_details["duration_ms"] = str(duration_ms) + + # Mark span as successful + span.set_status(Status(StatusCode.OK)) + + return result + except Exception as e: + # Record exception in span + span.record_exception(e) + span.set_status(Status(StatusCode.ERROR, str(e))) + raise + + return result + + return wrapper + + +def wrap_tool_usage(tracer, environment, application_name): + """Wrapper for ToolUsage.use method.""" + + def wrapper(wrapped, instance, args, kwargs): + logger.debug("CrewAI: Starting tool usage instrumentation") + + tool_name = "" + tool_description = "" + input_str = "" + + # Extract tool info from instance + if hasattr(instance, "tool"): + tool = instance.tool + if hasattr(tool, "name"): + tool_name = tool.name + if hasattr(tool, "description"): + tool_description = tool.description + + # Extract input from kwargs + if "input_str" in kwargs: + input_str = str(kwargs["input_str"]) + + with tracer.start_as_current_span( + "crewai.tool.usage", + kind=SpanKind.INTERNAL, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + TELEMETRY_SDK_NAME: "agentops", + SERVICE_NAME: application_name, + DEPLOYMENT_ENVIRONMENT: environment, + SpanAttributes.AGENTOPS_SPAN_KIND: "tool", + ToolAttributes.TOOL_NAME: tool_name, + ToolAttributes.TOOL_DESCRIPTION: tool_description, + ToolAttributes.TOOL_INPUT: input_str, + # Add message format for tool inputs + MessageAttributes.TOOL_CALL_NAME.format(i=0): tool_name, + MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=0): tool_description, + MessageAttributes.TOOL_CALL_ARGS.format(i=0): input_str, + }, + ) as span: + try: + # Capture start time for duration measurement + start_time = time.time() + + # Execute the wrapped function + result = wrapped(*args, **kwargs) + + # Calculate duration in milliseconds + end_time = time.time() + duration_ms = (end_time - start_time) * 1000 + + # Store duration in span + span.set_attribute("crewai.tool.duration_ms", str(duration_ms)) + + # Store result in span using both standard and message formats + if result: + output = getattr(result, "output", str(result)) + span.set_attribute(ToolAttributes.TOOL_OUTPUT, str(output)) + span.set_attribute(MessageAttributes.TOOL_CALL_RESPONSE.format(i=0), str(output)) + + # Mark span as successful + span.set_status(Status(StatusCode.OK)) + + return result + except Exception as e: + # Record exception in span + span.record_exception(e) + span.set_status(Status(StatusCode.ERROR, str(e))) + raise + + return wrapper + + +def is_metrics_enabled() -> bool: + """Check if metrics are enabled from the environment variable.""" + return os.environ.get("OTEL_METRICS_ENABLED", "1").lower() in ["1", "true", "yes"] + + +def _create_metrics(meter: Meter): + """Create metrics for monitoring.""" + token_histogram = meter.create_histogram( + name=Meters.LLM_TOKEN_USAGE, + unit="token", + description="Measures number of input and output tokens used", + ) + + duration_histogram = meter.create_histogram( + name=Meters.LLM_OPERATION_DURATION, + unit="s", + description="GenAI operation duration", + ) + + return token_histogram, duration_histogram \ No newline at end of file diff --git a/agentops/instrumentation/crewai/version.py b/agentops/instrumentation/crewai/version.py new file mode 100644 index 000000000..472d5d046 --- /dev/null +++ b/agentops/instrumentation/crewai/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" \ No newline at end of file From a43712d5aba2dfab27d43737fd729d27f17c812f Mon Sep 17 00:00:00 2001 From: Dwij Patel Date: Thu, 24 Apr 2025 03:39:52 +0530 Subject: [PATCH 4/4] Refactor CrewAI instrumentation to enhance attribute management and update version to 0.36.0. Removed third-party dependencies and improved error handling in agent and task processing. --- agentops/instrumentation/crewai/__init__.py | 4 +- .../crewai/crewai_span_attributes.py | 193 +++-- .../instrumentation/crewai/instrumentation.py | 737 ++++++++++-------- agentops/instrumentation/crewai/version.py | 2 +- .../data_level0.bin | Bin 6284000 -> 0 bytes .../header.bin | Bin 100 -> 0 bytes .../length.bin | Bin 4000 -> 0 bytes .../link_lists.bin | 0 examples/crewai_examples/db/chroma.sqlite3 | Bin 450560 -> 0 bytes examples/crewai_examples/job_posting.md | 85 +- .../instrumentation/crewai/LICENSE | 201 ----- .../instrumentation/crewai/NOTICE.md | 8 - .../instrumentation/crewai/__init__.py | 6 - .../crewai/crewai_span_attributes.py | 330 -------- .../instrumentation/crewai/instrumentation.py | 543 ------------- .../instrumentation/crewai/version.py | 1 - 16 files changed, 556 insertions(+), 1554 deletions(-) delete mode 100644 examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/data_level0.bin delete mode 100644 examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/header.bin delete mode 100644 examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/length.bin delete mode 100644 examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/link_lists.bin delete mode 100644 examples/crewai_examples/db/chroma.sqlite3 delete mode 100644 third_party/opentelemetry/instrumentation/crewai/LICENSE delete mode 100644 third_party/opentelemetry/instrumentation/crewai/NOTICE.md delete mode 100644 third_party/opentelemetry/instrumentation/crewai/__init__.py delete mode 100644 third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py delete mode 100644 third_party/opentelemetry/instrumentation/crewai/instrumentation.py delete mode 100644 third_party/opentelemetry/instrumentation/crewai/version.py diff --git a/agentops/instrumentation/crewai/__init__.py b/agentops/instrumentation/crewai/__init__.py index 8414f2b4e..a5f1a5a99 100644 --- a/agentops/instrumentation/crewai/__init__.py +++ b/agentops/instrumentation/crewai/__init__.py @@ -1,6 +1,6 @@ -"""AgentOps CrewAI instrumentation""" +"""OpenTelemetry CrewAI instrumentation""" from agentops.instrumentation.crewai.version import __version__ from agentops.instrumentation.crewai.instrumentation import CrewAIInstrumentor -__all__ = ["CrewAIInstrumentor", "__version__"] \ No newline at end of file +__all__ = ["CrewAIInstrumentor", "__version__"] diff --git a/agentops/instrumentation/crewai/crewai_span_attributes.py b/agentops/instrumentation/crewai/crewai_span_attributes.py index ceedc8835..d1fb83264 100644 --- a/agentops/instrumentation/crewai/crewai_span_attributes.py +++ b/agentops/instrumentation/crewai/crewai_span_attributes.py @@ -9,7 +9,6 @@ from agentops.semconv.agent import AgentAttributes from agentops.semconv.tool import ToolAttributes from agentops.semconv.message import MessageAttributes -from agentops.instrumentation.common.attributes import AttributeMap # Initialize logger for logging potential issues and operations logger = logging.getLogger(__name__) @@ -200,106 +199,132 @@ def _process_task(self): for i, tool in enumerate(tools): self._set_attribute(MessageAttributes.TOOL_CALL_NAME.format(i=i), tool.get("name", "")) self._set_attribute(MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=i), tool.get("description", "")) - except: - logger.warning(f"Failed to parse tools json: {task['tools']}") + except (json.JSONDecodeError, TypeError): + logger.warning(f"Failed to parse tools for task: {task.get('id', 'unknown')}") def _process_llm(self): """Process an LLM instance.""" + llm = {} self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "llm") - # Parse model parameters - model_name = getattr(self.instance, "model", None) or getattr(self.instance, "model_name", None) or "" - temp = getattr(self.instance, "temperature", None) - max_tokens = getattr(self.instance, "max_tokens", None) - - self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) - if temp is not None: - self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, str(temp)) - if max_tokens is not None: - self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, str(max_tokens)) - - # Add provider info based on class attributes or parent class - if hasattr(self.instance, 'provider'): - provider = self.instance.provider - self._set_attribute(SpanAttributes.LLM_PROVIDER, provider) - - # Set additional LLM attributes for key, value in self.instance.__dict__.items(): if value is None: continue - self._set_attribute(f"crewai.llm.{key}", str(value)) + llm[key] = str(value) - def _parse_agents(self, agents): - """Process a list of agents for a crew instance.""" - # Track agents in an array - for i, agent in enumerate(agents): - if not agent: - continue - - agent_data = self._extract_agent_data(agent) - - # Set span attributes for each agent - agent_prefix = f"crewai.crew.agent.{i}." - for key, value in agent_data.items(): - self._set_attribute(f"{agent_prefix}{key}", value) - - # Process tools if available - if hasattr(agent, "tools") and agent.tools: - parsed_tools = _parse_tools(agent.tools) - for j, tool in enumerate(parsed_tools): - tool_prefix = f"{agent_prefix}tool.{j}." - for tool_key, tool_value in tool.items(): - self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) + model_name = llm.get('model_name', '') or llm.get('model', '') + self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) + self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, llm.get('temperature', '')) + self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, llm.get('max_tokens', '')) + self._set_attribute(SpanAttributes.LLM_REQUEST_TOP_P, llm.get('top_p', '')) + + if 'frequency_penalty' in llm: + self._set_attribute(SpanAttributes.LLM_REQUEST_FREQUENCY_PENALTY, llm.get('frequency_penalty', '')) + if 'presence_penalty' in llm: + self._set_attribute(SpanAttributes.LLM_REQUEST_PRESENCE_PENALTY, llm.get('presence_penalty', '')) + if 'streaming' in llm: + self._set_attribute(SpanAttributes.LLM_REQUEST_STREAMING, llm.get('streaming', '')) + + if 'api_key' in llm: + self._set_attribute("gen_ai.request.api_key_present", "true") - # Process LLM if available - if hasattr(agent, "llm") and agent.llm: - llm = agent.llm - if hasattr(llm, "model") and llm.model: - self._set_attribute(f"{agent_prefix}llm.model", str(llm.model)) - elif hasattr(llm, "model_name") and llm.model_name: - self._set_attribute(f"{agent_prefix}llm.model", str(llm.model_name)) + if 'base_url' in llm: + self._set_attribute(SpanAttributes.LLM_OPENAI_API_BASE, llm.get('base_url', '')) + + if 'api_version' in llm: + self._set_attribute(SpanAttributes.LLM_OPENAI_API_VERSION, llm.get('api_version', '')) - def _parse_llms(self, llms): - """Process a dictionary of LLMs for a crew instance.""" - if not llms or not isinstance(llms, dict): + def _parse_agents(self, agents): + """Parse agents into a list of dictionaries.""" + if not agents: + logger.debug("CrewAI: No agents to parse") return + + agent_count = len(agents) + logger.debug(f"CrewAI: Parsing {agent_count} agents") + + # Pre-process all agents to collect their data first + agent_data_list = [] - # Track LLMs in an array - for i, (role, llm) in enumerate(llms.items()): - if not llm: + for idx, agent in enumerate(agents): + if agent is None: + logger.debug(f"CrewAI: Agent at index {idx} is None, skipping") + agent_data_list.append(None) continue - # Set basic LLM information - llm_prefix = f"crewai.crew.llm.{i}." - self._set_attribute(f"{llm_prefix}role", str(role)) - - # Extract model information - if hasattr(llm, "model") and llm.model: - self._set_attribute(f"{llm_prefix}model", str(llm.model)) - elif hasattr(llm, "model_name") and llm.model_name: - self._set_attribute(f"{llm_prefix}model", str(llm.model_name)) + logger.debug(f"CrewAI: Processing agent at index {idx}") + try: + agent_data = self._extract_agent_data(agent) + agent_data_list.append(agent_data) + except Exception as e: + logger.error(f"CrewAI: Error extracting data for agent at index {idx}: {str(e)}") + agent_data_list.append(None) + + # Now set all attributes at once for each agent + for idx, agent_data in enumerate(agent_data_list): + if agent_data is None: + continue - # Extract other important LLM parameters - for key in ["temperature", "max_tokens", "top_p"]: - if hasattr(llm, key) and getattr(llm, key) is not None: - self._set_attribute(f"{llm_prefix}{key}", str(getattr(llm, key))) + for key, value in agent_data.items(): + if key == "tools" and isinstance(value, list): + for tool_idx, tool in enumerate(value): + for tool_key, tool_value in tool.items(): + self._set_attribute(f"crewai.agents.{idx}.tools.{tool_idx}.{tool_key}", str(tool_value)) + else: + self._set_attribute(f"crewai.agents.{idx}.{key}", value) + + def _parse_llms(self, llms): + """Parse LLMs into a list of dictionaries.""" + for idx, llm in enumerate(llms): + if llm is not None: + model_name = getattr(llm, "model", None) or getattr(llm, "model_name", None) or "" + llm_data = { + "model": model_name, + "temperature": llm.temperature, + "max_tokens": llm.max_tokens, + "max_completion_tokens": llm.max_completion_tokens, + "top_p": llm.top_p, + "n": llm.n, + "seed": llm.seed, + "base_url": llm.base_url, + "api_version": llm.api_version, + } + + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_MODEL}.{idx}", model_name) + if hasattr(llm, "temperature"): + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_TEMPERATURE}.{idx}", str(llm.temperature)) + if hasattr(llm, "max_tokens"): + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_MAX_TOKENS}.{idx}", str(llm.max_tokens)) + if hasattr(llm, "top_p"): + self._set_attribute(f"{SpanAttributes.LLM_REQUEST_TOP_P}.{idx}", str(llm.top_p)) + + for key, value in llm_data.items(): + if value is not None: + self._set_attribute(f"crewai.llms.{idx}.{key}", str(value)) def _extract_agent_data(self, agent): - """Extract relevant data from an agent instance.""" - agent_data = {} - - # Extract basic agent information - for key in ["id", "role", "name", "goal", "backstory"]: - if hasattr(agent, key) and getattr(agent, key): - agent_data[key] = str(getattr(agent, key)) - - # Extract configuration settings - for key in ["allow_delegation", "allow_code_execution", "max_iter", "max_rpm", "verbose"]: - if hasattr(agent, key) and getattr(agent, key) is not None: - agent_data[key] = str(getattr(agent, key)) - - return agent_data + """Extract data from an agent.""" + model = getattr(agent.llm, "model", None) or getattr(agent.llm, "model_name", None) or "" + + tools_list = [] + if hasattr(agent, "tools") and agent.tools: + tools_list = _parse_tools(agent.tools) + + return { + "id": str(agent.id), + "role": agent.role, + "goal": agent.goal, + "backstory": agent.backstory, + "cache": agent.cache, + "config": agent.config, + "verbose": agent.verbose, + "allow_delegation": agent.allow_delegation, + "tools": tools_list, + "max_iter": agent.max_iter, + "llm": str(model), + } def _set_attribute(self, key, value): - """Set an attribute on the span with validation.""" - set_span_attribute(self.span, key, value) \ No newline at end of file + """Set an attribute on the span.""" + if value is not None and value != "": + set_span_attribute(self.span, key, value) diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py index aa09f9b69..b1e2bdcde 100644 --- a/agentops/instrumentation/crewai/instrumentation.py +++ b/agentops/instrumentation/crewai/instrumentation.py @@ -11,14 +11,9 @@ from opentelemetry.instrumentation.utils import unwrap from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT - -from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes -from agentops.semconv.agent import AgentAttributes -from agentops.semconv.message import MessageAttributes -from agentops.instrumentation.common.wrappers import WrapConfig, wrap -from agentops.instrumentation.common.attributes import AttributeMap from agentops.instrumentation.crewai.version import __version__ -from agentops.instrumentation.crewai.crewai_span_attributes import CrewAISpanAttributes, set_span_attribute +from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes +from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute # Initialize logger logger = logging.getLogger(__name__) @@ -86,47 +81,15 @@ def _instrument(self, **kwargs): duration_histogram, ) = (None, None) - # Use WrapConfig for consistent instrumentation patterns - wrap_configs = [ - WrapConfig( - trace_name="crewai.workflow", - package="crewai.crew", - class_name="Crew", - method_name="kickoff", - handler=self._create_crew_handler(tracer, duration_histogram, token_histogram, environment, application_name), - span_kind=SpanKind.INTERNAL - ), - WrapConfig( - trace_name="crewai.agent.execute_task", - package="crewai.agent", - class_name="Agent", - method_name="execute_task", - handler=self._create_agent_handler(tracer, duration_histogram, token_histogram, environment, application_name), - span_kind=SpanKind.INTERNAL - ), - WrapConfig( - trace_name="crewai.task.execute", - package="crewai.task", - class_name="Task", - method_name="execute_sync", - handler=self._create_task_handler(tracer, duration_histogram, token_histogram, environment, application_name), - span_kind=SpanKind.INTERNAL - ), - WrapConfig( - trace_name="crewai.llm.call", - package="crewai.llm", - class_name="LLM", - method_name="call", - handler=self._create_llm_handler(tracer, duration_histogram, token_histogram, environment, application_name), - span_kind=SpanKind.CLIENT - ), - ] - - # Apply all wrap configurations - for wrap_config in wrap_configs: - wrap(wrap_config, tracer) - - # Wrap tool execution functions using original pattern for backward compatibility + wrap_function_wrapper("crewai.crew", "Crew.kickoff", wrap_kickoff(tracer, duration_histogram, token_histogram, environment, application_name)) + wrap_function_wrapper( + "crewai.agent", "Agent.execute_task", wrap_agent_execute_task(tracer, duration_histogram, token_histogram, environment, application_name) + ) + wrap_function_wrapper( + "crewai.task", "Task.execute_sync", wrap_task_execute(tracer, duration_histogram, token_histogram, environment, application_name) + ) + wrap_function_wrapper("crewai.llm", "LLM.call", wrap_llm_call(tracer, duration_histogram, token_histogram, environment, application_name)) + wrap_function_wrapper( "crewai.utilities.tool_utils", "execute_tool_and_check_finality", wrap_tool_execution(tracer, duration_histogram, environment, application_name) @@ -145,338 +108,426 @@ def _uninstrument(self, **kwargs): unwrap("crewai.utilities.tool_utils", "execute_tool_and_check_finality") unwrap("crewai.tools.tool_usage", "ToolUsage.use") - def _create_crew_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): - def handler(args=None, kwargs=None, return_value=None): - attributes = dict() + +def with_tracer_wrapper(func): + """Helper for providing tracer for wrapper functions.""" + + def _with_tracer(tracer, duration_histogram, token_histogram, environment, application_name): + def wrapper(wrapped, instance, args, kwargs): + return func(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs) + + return wrapper + + return _with_tracer + + +@with_tracer_wrapper +def wrap_kickoff( + tracer: Tracer, duration_histogram: Histogram, token_histogram: Histogram, environment, application_name, wrapped, instance, args, kwargs +): + logger.debug(f"CrewAI: Starting workflow instrumentation for Crew with {len(getattr(instance, 'agents', []))} agents") + with tracer.start_as_current_span( + "crewai.workflow", + kind=SpanKind.INTERNAL, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + }, + ) as span: + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - if args and len(args) > 0: - instance = args[0] - logger.debug(f"CrewAI: Starting workflow instrumentation for Crew with {len(getattr(instance, 'agents', []))} agents") - - # Set core span attributes - attributes[TELEMETRY_SDK_NAME] = "agentops" - attributes[SERVICE_NAME] = application_name - attributes[DEPLOYMENT_ENVIRONMENT] = environment - attributes[SpanAttributes.LLM_SYSTEM] = "crewai" - - # Process crew instance but skip agent processing at this point - if hasattr(instance, 'agents') and instance.agents: - logger.debug(f"CrewAI: Found {len(instance.agents)} agents in crew") - - if return_value: - logger.debug("CrewAI: Processing crew return value") - if hasattr(return_value, "usage_metrics"): - attributes["crewai.crew.usage_metrics"] = str(getattr(return_value, "usage_metrics")) - - if hasattr(return_value, "tasks_output") and return_value.tasks_output: - attributes["crewai.crew.tasks_output"] = str(return_value.tasks_output) - - return attributes - return handler - - def _create_agent_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): - def handler(args=None, kwargs=None, return_value=None): - attributes = dict() + logger.debug("CrewAI: Processing crew instance attributes") - # Set base attributes - attributes[TELEMETRY_SDK_NAME] = "agentops" - attributes[SERVICE_NAME] = application_name - attributes[DEPLOYMENT_ENVIRONMENT] = environment - attributes[SpanAttributes.LLM_SYSTEM] = "crewai" + # First set general crew attributes but skip agent processing + crew_attrs = CrewAISpanAttributes(span=span, instance=instance, skip_agent_processing=True) - if args and len(args) > 0: - instance = args[0] - - # Extract agent information - if hasattr(instance, 'id'): - attributes[AgentAttributes.AGENT_ID] = str(instance.id) - - if hasattr(instance, 'role'): - attributes[AgentAttributes.AGENT_ROLE] = str(instance.role) - - if hasattr(instance, 'name'): - attributes[AgentAttributes.AGENT_NAME] = str(instance.name) - - # Process task if available - if len(args) > 1 and args[1]: - task = args[1] - if hasattr(task, 'description'): - attributes["crewai.task.description"] = str(task.description) - attributes[SpanAttributes.AGENTOPS_ENTITY_INPUT] = str(task.description) + # Prioritize agent processing before task execution + if hasattr(instance, 'agents') and instance.agents: + logger.debug(f"CrewAI: Explicitly processing {len(instance.agents)} agents before task execution") + crew_attrs._parse_agents(instance.agents) + + logger.debug("CrewAI: Executing wrapped crew kickoff function") + result = wrapped(*args, **kwargs) + + if result: + class_name = instance.__class__.__name__ + span.set_attribute(f"crewai.{class_name.lower()}.result", str(result)) + span.set_status(Status(StatusCode.OK)) + if class_name == "Crew": + if hasattr(result, "usage_metrics"): + span.set_attribute("crewai.crew.usage_metrics", str(getattr(result, "usage_metrics"))) + + if hasattr(result, "tasks_output") and result.tasks_output: + span.set_attribute("crewai.crew.tasks_output", str(result.tasks_output)) + + try: + task_details_by_description = {} + if hasattr(instance, "tasks"): + for task in instance.tasks: + if task is not None: + agent_id = "" + agent_role = "" + if hasattr(task, "agent") and task.agent: + agent_id = str(getattr(task.agent, "id", "")) + agent_role = getattr(task.agent, "role", "") + + tools = [] + if hasattr(task, "tools") and task.tools: + for tool in task.tools: + tool_info = {} + if hasattr(tool, "name"): + tool_info["name"] = tool.name + if hasattr(tool, "description"): + tool_info["description"] = tool.description + if tool_info: + tools.append(tool_info) + + task_details_by_description[task.description] = { + "agent_id": agent_id, + "agent_role": agent_role, + "async_execution": getattr(task, "async_execution", False), + "human_input": getattr(task, "human_input", False), + "output_file": getattr(task, "output_file", ""), + "tools": tools + } + + for idx, task_output in enumerate(result.tasks_output): + task_prefix = f"crewai.crew.tasks.{idx}" + + task_attrs = { + "description": getattr(task_output, "description", ""), + "name": getattr(task_output, "name", ""), + "expected_output": getattr(task_output, "expected_output", ""), + "summary": getattr(task_output, "summary", ""), + "raw": getattr(task_output, "raw", ""), + "agent": getattr(task_output, "agent", ""), + "output_format": str(getattr(task_output, "output_format", "")), + } + + for attr_name, attr_value in task_attrs.items(): + if attr_value: + if attr_name == "raw" and len(str(attr_value)) > 1000: + attr_value = str(attr_value)[:997] + "..." + span.set_attribute(f"{task_prefix}.{attr_name}", str(attr_value)) + + span.set_attribute(f"{task_prefix}.status", "completed") + span.set_attribute(f"{task_prefix}.id", str(idx)) + + description = task_attrs.get("description", "") + if description and description in task_details_by_description: + details = task_details_by_description[description] + + span.set_attribute(f"{task_prefix}.agent_id", details["agent_id"]) + span.set_attribute(f"{task_prefix}.async_execution", str(details["async_execution"])) + span.set_attribute(f"{task_prefix}.human_input", str(details["human_input"])) + + if details["output_file"]: + span.set_attribute(f"{task_prefix}.output_file", details["output_file"]) + + for tool_idx, tool in enumerate(details["tools"]): + for tool_key, tool_value in tool.items(): + span.set_attribute(f"{task_prefix}.tools.{tool_idx}.{tool_key}", str(tool_value)) + except Exception as ex: + logger.warning(f"Failed to parse task outputs: {ex}") - if hasattr(task, 'expected_output'): - attributes["crewai.task.expected_output"] = str(task.expected_output) + if hasattr(result, "token_usage"): + token_usage = str(getattr(result, "token_usage")) + span.set_attribute("crewai.crew.token_usage", token_usage) + + try: + metrics = {} + for item in token_usage.split(): + if "=" in item: + key, value = item.split("=") + try: + metrics[key] = int(value) + except ValueError: + metrics[key] = value + + if "total_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_TOTAL_TOKENS, metrics["total_tokens"]) + if "prompt_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_PROMPT_TOKENS, metrics["prompt_tokens"]) + if "completion_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, metrics["completion_tokens"]) + if "cached_prompt_tokens" in metrics: + span.set_attribute(SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS, metrics["cached_prompt_tokens"]) + if "successful_requests" in metrics: + span.set_attribute("crewai.crew.successful_requests", metrics["successful_requests"]) + + if "prompt_tokens" in metrics and "completion_tokens" in metrics and metrics["prompt_tokens"] > 0: + efficiency = metrics["completion_tokens"] / metrics["prompt_tokens"] + span.set_attribute("crewai.crew.token_efficiency", f"{efficiency:.4f}") + + if "cached_prompt_tokens" in metrics and "prompt_tokens" in metrics and metrics["prompt_tokens"] > 0: + cache_ratio = metrics["cached_prompt_tokens"] / metrics["prompt_tokens"] + span.set_attribute("crewai.crew.cache_efficiency", f"{cache_ratio:.4f}") + except Exception as ex: + logger.warning(f"Failed to parse token usage metrics: {ex}") + return result + except Exception as ex: + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) + raise + + +@with_tracer_wrapper +def wrap_agent_execute_task(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs): + agent_name = instance.role if hasattr(instance, "role") else "agent" + with tracer.start_as_current_span( + f"{agent_name}.agent", + kind=SpanKind.CLIENT, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.AGENT.value, + }, + ) as span: + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - # Process return value (task output) - if return_value: - if isinstance(return_value, str): - attributes["crewai.agent.task_output"] = return_value - attributes[SpanAttributes.AGENTOPS_ENTITY_OUTPUT] = return_value - - return attributes - return handler - - def _create_task_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): - def handler(args=None, kwargs=None, return_value=None): - attributes = dict() + CrewAISpanAttributes(span=span, instance=instance) - # Set base attributes - attributes[TELEMETRY_SDK_NAME] = "agentops" - attributes[SERVICE_NAME] = application_name - attributes[DEPLOYMENT_ENVIRONMENT] = environment - attributes[SpanAttributes.LLM_SYSTEM] = "crewai" - attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = "workflow.step" + result = wrapped(*args, **kwargs) - if args and len(args) > 0: - instance = args[0] - - # Process task attributes - if hasattr(instance, 'description'): - attributes["crewai.task.description"] = str(instance.description) - attributes[SpanAttributes.AGENTOPS_ENTITY_INPUT] = str(instance.description) - - if hasattr(instance, 'expected_output'): - attributes["crewai.task.expected_output"] = str(instance.expected_output) - - if hasattr(instance, 'agent') and instance.agent: - agent = instance.agent - if hasattr(agent, 'id'): - attributes[AgentAttributes.FROM_AGENT] = str(agent.id) - if hasattr(agent, 'role'): - attributes["crewai.task.agent.role"] = str(agent.role) + attach_tool_executions_to_agent_span(span) - # Process return value - if return_value: - attributes["crewai.task.output"] = str(return_value) - attributes[SpanAttributes.AGENTOPS_ENTITY_OUTPUT] = str(return_value) + if token_histogram and hasattr(instance, "_token_process"): + token_histogram.record( + instance._token_process.get_summary().prompt_tokens, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + SpanAttributes.LLM_TOKEN_TYPE: "input", + SpanAttributes.LLM_RESPONSE_MODEL: str(instance.llm.model), + }, + ) + token_histogram.record( + instance._token_process.get_summary().completion_tokens, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + SpanAttributes.LLM_TOKEN_TYPE: "output", + SpanAttributes.LLM_RESPONSE_MODEL: str(instance.llm.model), + }, + ) + + if hasattr(instance, "llm") and hasattr(instance.llm, "model"): + set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, str(instance.llm.model)) + set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, str(instance.llm.model)) - return attributes - return handler + span.set_status(Status(StatusCode.OK)) + return result + except Exception as ex: + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) + raise - def _create_llm_handler(self, tracer, duration_histogram, token_histogram, environment, application_name): - def handler(args=None, kwargs=None, return_value=None): - attributes = dict() + +@with_tracer_wrapper +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" + + with tracer.start_as_current_span( + f"{task_name}.task", + kind=SpanKind.CLIENT, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, + }, + ) as span: + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + CrewAISpanAttributes(span=span, instance=instance) - # Set base attributes - attributes[TELEMETRY_SDK_NAME] = "agentops" - attributes[SERVICE_NAME] = application_name - attributes[DEPLOYMENT_ENVIRONMENT] = environment - attributes[SpanAttributes.LLM_SYSTEM] = "crewai" - attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = "llm" + result = wrapped(*args, **kwargs) - if args and len(args) > 0: - instance = args[0] - - # Extract LLM model information - model_name = getattr(instance, "model", None) or getattr(instance, "model_name", None) or "" - attributes[SpanAttributes.LLM_REQUEST_MODEL] = model_name - - # Extract LLM parameters - for param_name, attr_name in [ - ("temperature", SpanAttributes.LLM_REQUEST_TEMPERATURE), - ("max_tokens", SpanAttributes.LLM_REQUEST_MAX_TOKENS), - ("top_p", SpanAttributes.LLM_REQUEST_TOP_P) - ]: - if hasattr(instance, param_name) and getattr(instance, param_name) is not None: - attributes[attr_name] = str(getattr(instance, param_name)) - - # Extract request content using MessageAttributes for indexed values - if len(args) > 1: - prompt = args[1] - # Use MessageAttributes for prompt instead of SpanAttributes - attributes[MessageAttributes.CONTENT.format(i=0)] = str(prompt) - attributes[MessageAttributes.ROLE.format(i=0)] = "user" - # Keep the standard attributes for backward compatibility - attributes[SpanAttributes.LLM_REQUEST_PROMPT] = str(prompt) - attributes[SpanAttributes.LLM_REQUEST_CONTENT] = str(prompt) + set_span_attribute(span, SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(result)) + span.set_status(Status(StatusCode.OK)) + return result + except Exception as ex: + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) + raise + + +@with_tracer_wrapper +def wrap_llm_call(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs): + llm = instance.model if hasattr(instance, "model") else "llm" + with tracer.start_as_current_span(f"{llm}.llm", kind=SpanKind.CLIENT, attributes={}) as span: + start_time = time.time() + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - # Process response using MessageAttributes for completion - if return_value: - # Use MessageAttributes for completion - attributes[MessageAttributes.CONTENT.format(i=1)] = str(return_value) - attributes[MessageAttributes.ROLE.format(i=1)] = "assistant" - # Keep standard attribute for backward compatibility - attributes[SpanAttributes.LLM_RESPONSE_CONTENT] = str(return_value) + CrewAISpanAttributes(span=span, instance=instance) - return attributes - return handler + result = wrapped(*args, **kwargs) + + if duration_histogram: + duration = time.time() - start_time + duration_histogram.record( + duration, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + SpanAttributes.LLM_RESPONSE_MODEL: str(instance.model), + }, + ) + + span.set_status(Status(StatusCode.OK)) + return result + except Exception as ex: + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error("Error in trace creation: %s", ex) + raise def wrap_tool_execution(tracer, duration_histogram, environment, application_name): - """Wrapper for tool execution functions.""" - + """Wrapper for tool execution function.""" def wrapper(wrapped, instance, args, kwargs): - logger.debug("CrewAI: Starting tool execution instrumentation") + agent_action = args[0] if args else None + tools = args[1] if len(args) > 1 else [] - tool_name = "" - tool_description = "" - input_str = "" - agent_id = "" - agent_role = "" - - # Extract tool info from args - if len(args) >= 1 and args[0]: - tool = args[0] - if hasattr(tool, "name"): - tool_name = tool.name - if hasattr(tool, "description"): - tool_description = tool.description - - # Extract input from args - if len(args) >= 2: - input_str = str(args[1]) + if not agent_action: + return wrapped(*args, **kwargs) + + tool_name = getattr(agent_action, "tool", "unknown_tool") + tool_input = getattr(agent_action, "tool_input", "") + + with store_tool_execution() as tool_details: + tool_details["name"] = tool_name + tool_details["parameters"] = str(tool_input) - # Extract agent from args - if len(args) >= 3 and args[2]: - agent = args[2] - if hasattr(agent, "id"): - agent_id = str(agent.id) - if hasattr(agent, "role"): - agent_role = str(agent.role) + matching_tool = next((tool for tool in tools if hasattr(tool, "name") and tool.name == tool_name), None) + if matching_tool and hasattr(matching_tool, "description"): + tool_details["description"] = str(matching_tool.description) - with tracer.start_as_current_span( - "crewai.tool.execution", - kind=SpanKind.INTERNAL, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - TELEMETRY_SDK_NAME: "agentops", - SERVICE_NAME: application_name, - DEPLOYMENT_ENVIRONMENT: environment, - SpanAttributes.AGENTOPS_SPAN_KIND: "tool", - ToolAttributes.TOOL_NAME: tool_name, - ToolAttributes.TOOL_DESCRIPTION: tool_description, - ToolAttributes.TOOL_INPUT: input_str, - AgentAttributes.FROM_AGENT: agent_id, - "crewai.agent.role": agent_role, - }, - ) as span: - try: - # Capture start time for duration measurement + with tracer.start_as_current_span( + f"{tool_name}.tool", + kind=SpanKind.CLIENT, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: "tool", + ToolAttributes.TOOL_NAME: tool_name, + ToolAttributes.TOOL_PARAMETERS: str(tool_input), + }, + ) as span: start_time = time.time() - - # Execute the wrapped function - result = wrapped(*args, **kwargs) - - # Calculate duration in milliseconds - end_time = time.time() - duration_ms = (end_time - start_time) * 1000 - - # Store duration in span - span.set_attribute("crewai.tool.duration_ms", str(duration_ms)) - - # Record duration in histogram if available - if duration_histogram: - duration_histogram.record(duration_ms) - - # Store result in span - use MessageAttributes - if result: - span.set_attribute(ToolAttributes.TOOL_OUTPUT, str(result)) - # Add message format for tools as well - span.set_attribute(MessageAttributes.TOOL_CALL_RESPONSE.format(i=0), str(result)) - - # Store tool execution details for later attachment to agent span - with store_tool_execution() as tool_details: - tool_details["name"] = tool_name - tool_details["description"] = tool_description - tool_details["input"] = input_str - tool_details["output"] = str(result) if result else "" - tool_details["duration_ms"] = str(duration_ms) - - # Mark span as successful - span.set_status(Status(StatusCode.OK)) - - return result - except Exception as e: - # Record exception in span - span.record_exception(e) - span.set_status(Status(StatusCode.ERROR, str(e))) - raise - - return result - + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + if matching_tool and hasattr(matching_tool, "description"): + span.set_attribute(ToolAttributes.TOOL_DESCRIPTION, str(matching_tool.description)) + + result = wrapped(*args, **kwargs) + + if duration_histogram: + duration = time.time() - start_time + duration_histogram.record( + duration, + attributes={ + SpanAttributes.LLM_SYSTEM: "crewai", + ToolAttributes.TOOL_NAME: tool_name, + }, + ) + + if hasattr(result, "result"): + tool_result = str(result.result) + span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result) + tool_details["result"] = tool_result + + tool_status = "success" if not hasattr(result, "error") or not result.error else "error" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + + if hasattr(result, "error") and result.error: + tool_details["error"] = str(result.error) + + duration = time.time() - start_time + tool_details["duration"] = f"{duration:.3f}" + + span.set_status(Status(StatusCode.OK)) + return result + except Exception as ex: + tool_status = "error" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + tool_details["error"] = str(ex) + + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error(f"Error in tool execution trace: {ex}") + raise + return wrapper def wrap_tool_usage(tracer, environment, application_name): """Wrapper for ToolUsage.use method.""" - def wrapper(wrapped, instance, args, kwargs): - logger.debug("CrewAI: Starting tool usage instrumentation") + calling = args[0] if args else None + tool_string = args[1] if len(args) > 1 else "" - tool_name = "" - tool_description = "" - input_str = "" + if not calling: + return wrapped(*args, **kwargs) - # Extract tool info from instance - if hasattr(instance, "tool"): - tool = instance.tool - if hasattr(tool, "name"): - tool_name = tool.name - if hasattr(tool, "description"): - tool_description = tool.description + tool_name = getattr(calling, "tool_name", "unknown_tool") - # Extract input from kwargs - if "input_str" in kwargs: - input_str = str(kwargs["input_str"]) + with store_tool_execution() as tool_details: + tool_details["name"] = tool_name - with tracer.start_as_current_span( - "crewai.tool.usage", - kind=SpanKind.INTERNAL, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - TELEMETRY_SDK_NAME: "agentops", - SERVICE_NAME: application_name, - DEPLOYMENT_ENVIRONMENT: environment, - SpanAttributes.AGENTOPS_SPAN_KIND: "tool", - ToolAttributes.TOOL_NAME: tool_name, - ToolAttributes.TOOL_DESCRIPTION: tool_description, - ToolAttributes.TOOL_INPUT: input_str, - # Add message format for tool inputs - MessageAttributes.TOOL_CALL_NAME.format(i=0): tool_name, - MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=0): tool_description, - MessageAttributes.TOOL_CALL_ARGS.format(i=0): input_str, - }, - ) as span: - try: - # Capture start time for duration measurement - start_time = time.time() - - # Execute the wrapped function - result = wrapped(*args, **kwargs) - - # Calculate duration in milliseconds - end_time = time.time() - duration_ms = (end_time - start_time) * 1000 - - # Store duration in span - span.set_attribute("crewai.tool.duration_ms", str(duration_ms)) - - # Store result in span using both standard and message formats - if result: - output = getattr(result, "output", str(result)) - span.set_attribute(ToolAttributes.TOOL_OUTPUT, str(output)) - span.set_attribute(MessageAttributes.TOOL_CALL_RESPONSE.format(i=0), str(output)) - - # Mark span as successful - span.set_status(Status(StatusCode.OK)) - - return result - except Exception as e: - # Record exception in span - span.record_exception(e) - span.set_status(Status(StatusCode.ERROR, str(e))) - raise - + if hasattr(calling, "arguments") and calling.arguments: + tool_details["parameters"] = str(calling.arguments) + + with tracer.start_as_current_span( + f"{tool_name}.tool_usage", + kind=SpanKind.INTERNAL, + attributes={ + SpanAttributes.AGENTOPS_SPAN_KIND: "tool.usage", + ToolAttributes.TOOL_NAME: tool_name, + }, + ) as span: + try: + span.set_attribute(TELEMETRY_SDK_NAME, "agentops") + span.set_attribute(SERVICE_NAME, application_name) + span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) + + if hasattr(calling, "arguments") and calling.arguments: + span.set_attribute(ToolAttributes.TOOL_PARAMETERS, str(calling.arguments)) + + result = wrapped(*args, **kwargs) + + tool_result = str(result) + span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result) + tool_details["result"] = tool_result + + tool_status = "success" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + + span.set_status(Status(StatusCode.OK)) + return result + except Exception as ex: + tool_status = "error" + span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) + tool_details["status"] = tool_status + tool_details["error"] = str(ex) + + span.set_status(Status(StatusCode.ERROR, str(ex))) + logger.error(f"Error in tool usage trace: {ex}") + raise + return wrapper def is_metrics_enabled() -> bool: - """Check if metrics are enabled from the environment variable.""" - return os.environ.get("OTEL_METRICS_ENABLED", "1").lower() in ["1", "true", "yes"] + return (os.getenv("AGENTOPS_METRICS_ENABLED") or "true").lower() == "true" def _create_metrics(meter: Meter): - """Create metrics for monitoring.""" token_histogram = meter.create_histogram( name=Meters.LLM_TOKEN_USAGE, unit="token", @@ -488,5 +539,5 @@ def _create_metrics(meter: Meter): unit="s", description="GenAI operation duration", ) - - return token_histogram, duration_histogram \ No newline at end of file + + return token_histogram, duration_histogram diff --git a/agentops/instrumentation/crewai/version.py b/agentops/instrumentation/crewai/version.py index 472d5d046..d9f2629e2 100644 --- a/agentops/instrumentation/crewai/version.py +++ b/agentops/instrumentation/crewai/version.py @@ -1 +1 @@ -__version__ = "0.1.0" \ No newline at end of file +__version__ = "0.36.0" diff --git a/examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/data_level0.bin b/examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/data_level0.bin deleted file mode 100644 index ea3192e8ec5112eb8b4f2a7bde5d13bbb95e6ac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6284000 zcmeFtfdBvi0Dz$VsTV1P3IhfV7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjxE(qc00000802p~jU9!M z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA hz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VqIS}Y^00961 diff --git a/examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/header.bin b/examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/header.bin deleted file mode 100644 index 3e0932a7d0033aedf7dad4109641188fbb1f6091..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100 tcmZQ%K!6v_2sVh-BLU&Jz-XxSe<%=u@)e*ojQ_7mJJntEx_t^%8~|Sk4$lAp diff --git a/examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/length.bin b/examples/crewai_examples/db/2acd4cdf-cba0-40f5-ae71-ffed7e64c152/length.bin deleted file mode 100644 index fd74c49c5a8a12427eed21250b8a31e1b067f00b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4000 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
|FQQTU{PId+bCs(Vd#j6C_18I14N3z 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 Tz}~-_QA^UI1S|?H(6N63(>c*x diff --git a/examples/crewai_examples/job_posting.md b/examples/crewai_examples/job_posting.md index dfb284fd5..c6dc0275f 100644 --- a/examples/crewai_examples/job_posting.md +++ b/examples/crewai_examples/job_posting.md @@ -1,35 +1,50 @@ -# Game Design Specialist -**Location:** Los Angeles, CA (Hybrid Work Available) - -## Introduction: -At Riot Games, we are driven by a single mission—to be the most player-focused gaming company in the world. Our culture thrives on innovation, empowerment, and collaboration, where every member of our team is valued and has the opportunity to influence player experiences at every turn. If you're passionate about gaming and excited to create unforgettable experiences that resonate with players, we would love to hear from you! - -## Role Description: -We are seeking a dedicated and talented **Game Design Specialist** who is ready to contribute to our mission. As a key member of our development team, you will play an essential role in designing engaging game mechanics, fostering player engagement, and ensuring our games exceed player expectations. Your insights will be critical in shaping the future of gaming at Riot Games. - -## Responsibilities: -- Collaborate with cross-functional teams to design and implement game features and mechanics. -- Analyze player feedback and data to continuously improve game experience and engagement. -- Develop and maintain game design documentation and prototypes for new gameplay features. -- Conduct playtests to gather insights and iterate on game designs. -- Engage with the community to understand player needs and incorporate their feedback into design choices. - -## Requirements: -- At least 5 years of experience in game design or a related technical field. -- Proven expertise in game mechanics design and player engagement strategies. -- Strong analytical skills with a knack for data analysis and user feedback interpretation. -- Excellent teamwork and interpersonal skills, fostering a collaborative work environment. -- A genuine passion for gaming and in-depth knowledge of current trends in the gaming industry. - -## Qualities and Characteristics: -- Player-focused mindset ensuring that player feedback drives every design choice. -- Creative problem solver with a history of innovative solutions to complex design challenges. -- Ambitious and humble, eager to learn continuously and share knowledge with the team. - -## Unique Benefits: -- Join a dynamic and inclusive workplace that values diversity and creativity. -- Opportunities for social impact through community engagement projects. -- A modern workspace featuring themed meeting rooms and recreational areas designed for player and employee experiences alike. -- Enjoy food and perks that reflect our dedication to our team’s comfort and engagement. - -Are you ready to take on the challenge and join a passionate team dedicated to enriching the world of gaming? **Apply now** and contribute to creating impactful and memorable player experiences with Riot Games! \ No newline at end of file +```markdown +# Senior Project Manager + +## About Burwood Partners +At Burwood Partners, we pride ourselves on our commitment to integrity, innovation, and a client-centric approach. We thrive in an adaptable, collaborative environment where our diverse talents converge to tackle complex consulting challenges. + +## Job Description +We are seeking a **Senior Project Manager** to join our dynamic team. In this role, you will lead cross-functional projects, ensure the successful delivery of consulting solutions, and foster strong relationships with our clients. Your expertise will help us navigate the evolving landscape of management consulting and make a significant impact on our clients' success. + +### Key Responsibilities +- Manage and oversee multiple projects from initiation to completion while ensuring adherence to quality standards and deadlines. +- Collaborate with clients to understand their unique needs, leverage technology, and develop solutions that drive value. +- Lead and mentor project team members, promoting a culture of continuous learning and improvement. +- Use data analysis and strategic planning skills to provide insightful recommendations to clients. +- Drive innovative solutions that align with industry trends, particularly in Environmental, Social, and Governance (ESG) practices. + +### Key Skills +- **Technology Proficiency**: Experience with AI, data analytics, and consulting tools. +- **Data Analysis**: Strong analytical skills to extract insights and make informed decisions. +- **Change Management**: Proven ability to facilitate organizational change effectively. +- **Strategic Planning**: Develop strategies that align with client goals. + +### Qualities +- Integrity and Strong Ethical Standards: A commitment to maintaining trust and ethical practices with clients. +- Client-Centric Orientation: A focus on delivering tailored, impactful solutions for clients. +- Adaptability and Innovation: Open to changes and developing innovative approaches to complex challenges. +- Strong Communication Skills: Excellent verbal and written skills to interact effectively with clients and team members. + +### What We Offer +- A flexible remote work environment. +- Opportunities for professional growth and advancement. +- A chance to work with industry leaders in ESG consulting. +- Competitive compensation and benefits package. + +## Join Us! +This is an exciting opportunity for professionals who thrive in a fast-paced, collaborative setting and are enthusiastic about driving positive change. If you are an innovative thinker with a commitment to excellence, we invite you to apply for the position of Senior Project Manager at Burwood Partners. + +**Apply Now!** + +--- + +We look forward to meeting candidates who are passionate about consulting and eager to make a difference. +``` + +### Feedback on Potential Improvements: +1. **Engage with Candidates**: Use proactive language that speaks directly to prospective applicants and encourages them to envision themselves in the role. +2. **Diversity and Inclusion**: Consider adding a statement about your commitment to a diverse and inclusive workplace. +3. **Application Process**: Clearly outline how to apply to enhance the candidate experience. + +With these revisions, the job posting is ready for final approval and publishing. \ No newline at end of file diff --git a/third_party/opentelemetry/instrumentation/crewai/LICENSE b/third_party/opentelemetry/instrumentation/crewai/LICENSE deleted file mode 100644 index 0f2a333f0..000000000 --- a/third_party/opentelemetry/instrumentation/crewai/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023 openllmetry - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/third_party/opentelemetry/instrumentation/crewai/NOTICE.md b/third_party/opentelemetry/instrumentation/crewai/NOTICE.md deleted file mode 100644 index ca711b794..000000000 --- a/third_party/opentelemetry/instrumentation/crewai/NOTICE.md +++ /dev/null @@ -1,8 +0,0 @@ -This package contains code derived from the OpenLLMetry project, which is licensed under the Apache License, Version 2.0. - -Original repository: https://github.com/traceloop/openllmetry - -Copyright notice from the original project: -Copyright (c) Traceloop (https://traceloop.com) - -The Apache 2.0 license can be found in the LICENSE file in this directory. diff --git a/third_party/opentelemetry/instrumentation/crewai/__init__.py b/third_party/opentelemetry/instrumentation/crewai/__init__.py deleted file mode 100644 index a452a7f28..000000000 --- a/third_party/opentelemetry/instrumentation/crewai/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""OpenTelemetry CrewAI instrumentation""" - -from opentelemetry.instrumentation.crewai.version import __version__ -from opentelemetry.instrumentation.crewai.instrumentation import CrewAIInstrumentor - -__all__ = ["CrewAIInstrumentor", "__version__"] diff --git a/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py b/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py deleted file mode 100644 index d1fb83264..000000000 --- a/third_party/opentelemetry/instrumentation/crewai/crewai_span_attributes.py +++ /dev/null @@ -1,330 +0,0 @@ -"""OpenTelemetry instrumentation for CrewAI.""" - -import json -import logging -from typing import Any -from opentelemetry.trace import Span - -from agentops.semconv.span_attributes import SpanAttributes -from agentops.semconv.agent import AgentAttributes -from agentops.semconv.tool import ToolAttributes -from agentops.semconv.message import MessageAttributes - -# Initialize logger for logging potential issues and operations -logger = logging.getLogger(__name__) - -def _parse_tools(tools): - """Parse tools into a JSON string with name and description.""" - result = [] - for tool in tools: - res = {} - if hasattr(tool, "name") and tool.name is not None: - res["name"] = tool.name - if hasattr(tool, "description") and tool.description is not None: - res["description"] = tool.description - if res: - result.append(res) - return result - -def set_span_attribute(span: Span, key: str, value: Any) -> None: - """Set a single attribute on a span.""" - if value is not None and value != "": - if hasattr(value, "__str__"): - value = str(value) - span.set_attribute(key, value) - - -class CrewAISpanAttributes: - """Manages span attributes for CrewAI instrumentation.""" - - def __init__(self, span: Span, instance, skip_agent_processing=False) -> None: - self.span = span - self.instance = instance - self.skip_agent_processing = skip_agent_processing - self.process_instance() - - def process_instance(self): - """Process the instance based on its type.""" - instance_type = self.instance.__class__.__name__ - self._set_attribute(SpanAttributes.LLM_SYSTEM, "crewai") - self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_NAME, instance_type) - - method_mapping = { - "Crew": self._process_crew, - "Agent": self._process_agent, - "Task": self._process_task, - "LLM": self._process_llm, - } - method = method_mapping.get(instance_type) - if method: - method() - - def _process_crew(self): - """Process a Crew instance.""" - crew_id = getattr(self.instance, "id", "") - self._set_attribute("crewai.crew.id", str(crew_id)) - self._set_attribute("crewai.crew.type", "crewai.crew") - self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "workflow") - - logger.debug(f"CrewAI: Processing crew with id {crew_id}") - - for key, value in self.instance.__dict__.items(): - if value is None: - continue - - if key == "tasks": - if isinstance(value, list): - self._set_attribute("crewai.crew.max_turns", str(len(value))) - logger.debug(f"CrewAI: Found {len(value)} tasks") - elif key == "agents": - if isinstance(value, list): - logger.debug(f"CrewAI: Found {len(value)} agents in crew") - - if not self.skip_agent_processing: - self._parse_agents(value) - elif key == "llms": - self._parse_llms(value) - elif key == "result": - self._set_attribute("crewai.crew.final_output", str(value)) - self._set_attribute("crewai.crew.output", str(value)) - self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(value)) - else: - self._set_attribute(f"crewai.crew.{key}", str(value)) - - def _process_agent(self): - """Process an Agent instance.""" - agent = {} - self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "agent") - - for key, value in self.instance.__dict__.items(): - if key == "tools": - parsed_tools = _parse_tools(value) - for i, tool in enumerate(parsed_tools): - tool_prefix = f"crewai.agent.tool.{i}." - for tool_key, tool_value in tool.items(): - self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) - - agent[key] = json.dumps(parsed_tools) - - if value is None: - continue - - if key != "tools": - agent[key] = str(value) - - self._set_attribute(AgentAttributes.AGENT_ID, agent.get('id', '')) - self._set_attribute(AgentAttributes.AGENT_ROLE, agent.get('role', '')) - self._set_attribute(AgentAttributes.AGENT_NAME, agent.get('name', '')) - self._set_attribute(AgentAttributes.AGENT_TOOLS, agent.get('tools', '')) - - if 'reasoning' in agent: - self._set_attribute(AgentAttributes.AGENT_REASONING, agent.get('reasoning', '')) - - if 'goal' in agent: - self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_INPUT, agent.get('goal', '')) - - self._set_attribute("crewai.agent.goal", agent.get('goal', '')) - self._set_attribute("crewai.agent.backstory", agent.get('backstory', '')) - self._set_attribute("crewai.agent.cache", agent.get('cache', '')) - self._set_attribute("crewai.agent.allow_delegation", agent.get('allow_delegation', '')) - self._set_attribute("crewai.agent.allow_code_execution", agent.get('allow_code_execution', '')) - self._set_attribute("crewai.agent.max_retry_limit", agent.get('max_retry_limit', '')) - - if hasattr(self.instance, "llm") and self.instance.llm is not None: - model_name = getattr(self.instance.llm, "model", None) or getattr(self.instance.llm, "model_name", None) or "" - temp = getattr(self.instance.llm, "temperature", None) - max_tokens = getattr(self.instance.llm, "max_tokens", None) - top_p = getattr(self.instance.llm, "top_p", None) - - self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) - if temp is not None: - self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, str(temp)) - if max_tokens is not None: - self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, str(max_tokens)) - if top_p is not None: - self._set_attribute(SpanAttributes.LLM_REQUEST_TOP_P, str(top_p)) - - self._set_attribute("crewai.agent.llm", str(model_name)) - self._set_attribute(AgentAttributes.AGENT_MODELS, str(model_name)) - - def _process_task(self): - """Process a Task instance.""" - task = {} - self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "workflow.step") - - for key, value in self.instance.__dict__.items(): - if value is None: - continue - if key == "tools": - parsed_tools = _parse_tools(value) - for i, tool in enumerate(parsed_tools): - tool_prefix = f"crewai.task.tool.{i}." - for tool_key, tool_value in tool.items(): - self._set_attribute(f"{tool_prefix}{tool_key}", str(tool_value)) - - task[key] = json.dumps(parsed_tools) - - elif key == "agent": - task[key] = value.role if value else None - if value: - agent_id = getattr(value, "id", "") - self._set_attribute(AgentAttributes.FROM_AGENT, str(agent_id)) - else: - task[key] = str(value) - - self._set_attribute("crewai.task.name", task.get('description', '')) - self._set_attribute("crewai.task.type", "task") - self._set_attribute("crewai.task.input", task.get('context', '')) - self._set_attribute("crewai.task.expected_output", task.get('expected_output', '')) - - if 'description' in task: - self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_INPUT, task.get('description', '')) - if 'output' in task: - self._set_attribute(SpanAttributes.AGENTOPS_ENTITY_OUTPUT, task.get('output', '')) - self._set_attribute("crewai.task.output", task.get('output', '')) - - if 'id' in task: - self._set_attribute("crewai.task.id", str(task.get('id', ''))) - - if 'status' in task: - self._set_attribute("crewai.task.status", task.get('status', '')) - - self._set_attribute("crewai.task.agent", task.get('agent', '')) - self._set_attribute("crewai.task.human_input", task.get('human_input', '')) - self._set_attribute("crewai.task.processed_by_agents", str(task.get('processed_by_agents', ''))) - - if 'tools' in task and task['tools']: - try: - tools = json.loads(task['tools']) - for i, tool in enumerate(tools): - self._set_attribute(MessageAttributes.TOOL_CALL_NAME.format(i=i), tool.get("name", "")) - self._set_attribute(MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=i), tool.get("description", "")) - except (json.JSONDecodeError, TypeError): - logger.warning(f"Failed to parse tools for task: {task.get('id', 'unknown')}") - - def _process_llm(self): - """Process an LLM instance.""" - llm = {} - self._set_attribute(SpanAttributes.AGENTOPS_SPAN_KIND, "llm") - - for key, value in self.instance.__dict__.items(): - if value is None: - continue - llm[key] = str(value) - - model_name = llm.get('model_name', '') or llm.get('model', '') - self._set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) - self._set_attribute(SpanAttributes.LLM_REQUEST_TEMPERATURE, llm.get('temperature', '')) - self._set_attribute(SpanAttributes.LLM_REQUEST_MAX_TOKENS, llm.get('max_tokens', '')) - self._set_attribute(SpanAttributes.LLM_REQUEST_TOP_P, llm.get('top_p', '')) - - if 'frequency_penalty' in llm: - self._set_attribute(SpanAttributes.LLM_REQUEST_FREQUENCY_PENALTY, llm.get('frequency_penalty', '')) - if 'presence_penalty' in llm: - self._set_attribute(SpanAttributes.LLM_REQUEST_PRESENCE_PENALTY, llm.get('presence_penalty', '')) - if 'streaming' in llm: - self._set_attribute(SpanAttributes.LLM_REQUEST_STREAMING, llm.get('streaming', '')) - - if 'api_key' in llm: - self._set_attribute("gen_ai.request.api_key_present", "true") - - if 'base_url' in llm: - self._set_attribute(SpanAttributes.LLM_OPENAI_API_BASE, llm.get('base_url', '')) - - if 'api_version' in llm: - self._set_attribute(SpanAttributes.LLM_OPENAI_API_VERSION, llm.get('api_version', '')) - - def _parse_agents(self, agents): - """Parse agents into a list of dictionaries.""" - if not agents: - logger.debug("CrewAI: No agents to parse") - return - - agent_count = len(agents) - logger.debug(f"CrewAI: Parsing {agent_count} agents") - - # Pre-process all agents to collect their data first - agent_data_list = [] - - for idx, agent in enumerate(agents): - if agent is None: - logger.debug(f"CrewAI: Agent at index {idx} is None, skipping") - agent_data_list.append(None) - continue - - logger.debug(f"CrewAI: Processing agent at index {idx}") - try: - agent_data = self._extract_agent_data(agent) - agent_data_list.append(agent_data) - except Exception as e: - logger.error(f"CrewAI: Error extracting data for agent at index {idx}: {str(e)}") - agent_data_list.append(None) - - # Now set all attributes at once for each agent - for idx, agent_data in enumerate(agent_data_list): - if agent_data is None: - continue - - for key, value in agent_data.items(): - if key == "tools" and isinstance(value, list): - for tool_idx, tool in enumerate(value): - for tool_key, tool_value in tool.items(): - self._set_attribute(f"crewai.agents.{idx}.tools.{tool_idx}.{tool_key}", str(tool_value)) - else: - self._set_attribute(f"crewai.agents.{idx}.{key}", value) - - def _parse_llms(self, llms): - """Parse LLMs into a list of dictionaries.""" - for idx, llm in enumerate(llms): - if llm is not None: - model_name = getattr(llm, "model", None) or getattr(llm, "model_name", None) or "" - llm_data = { - "model": model_name, - "temperature": llm.temperature, - "max_tokens": llm.max_tokens, - "max_completion_tokens": llm.max_completion_tokens, - "top_p": llm.top_p, - "n": llm.n, - "seed": llm.seed, - "base_url": llm.base_url, - "api_version": llm.api_version, - } - - self._set_attribute(f"{SpanAttributes.LLM_REQUEST_MODEL}.{idx}", model_name) - if hasattr(llm, "temperature"): - self._set_attribute(f"{SpanAttributes.LLM_REQUEST_TEMPERATURE}.{idx}", str(llm.temperature)) - if hasattr(llm, "max_tokens"): - self._set_attribute(f"{SpanAttributes.LLM_REQUEST_MAX_TOKENS}.{idx}", str(llm.max_tokens)) - if hasattr(llm, "top_p"): - self._set_attribute(f"{SpanAttributes.LLM_REQUEST_TOP_P}.{idx}", str(llm.top_p)) - - for key, value in llm_data.items(): - if value is not None: - self._set_attribute(f"crewai.llms.{idx}.{key}", str(value)) - - def _extract_agent_data(self, agent): - """Extract data from an agent.""" - model = getattr(agent.llm, "model", None) or getattr(agent.llm, "model_name", None) or "" - - tools_list = [] - if hasattr(agent, "tools") and agent.tools: - tools_list = _parse_tools(agent.tools) - - return { - "id": str(agent.id), - "role": agent.role, - "goal": agent.goal, - "backstory": agent.backstory, - "cache": agent.cache, - "config": agent.config, - "verbose": agent.verbose, - "allow_delegation": agent.allow_delegation, - "tools": tools_list, - "max_iter": agent.max_iter, - "llm": str(model), - } - - def _set_attribute(self, key, value): - """Set an attribute on the span.""" - if value is not None and value != "": - set_span_attribute(self.span, key, value) diff --git a/third_party/opentelemetry/instrumentation/crewai/instrumentation.py b/third_party/opentelemetry/instrumentation/crewai/instrumentation.py deleted file mode 100644 index 3b07c97bf..000000000 --- a/third_party/opentelemetry/instrumentation/crewai/instrumentation.py +++ /dev/null @@ -1,543 +0,0 @@ -import os -import time -import logging -from typing import Collection, Dict, List, Any -from contextlib import contextmanager - -from wrapt import wrap_function_wrapper -from opentelemetry.trace import SpanKind, get_tracer, Tracer, get_current_span -from opentelemetry.trace.status import Status, StatusCode -from opentelemetry.metrics import Histogram, Meter, get_meter -from opentelemetry.instrumentation.utils import unwrap -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT -from opentelemetry.instrumentation.crewai.version import __version__ -from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes -from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute - -# Initialize logger -logger = logging.getLogger(__name__) - -_instruments = ("crewai >= 0.70.0",) - -# Global context to store tool executions by parent span ID -_tool_executions_by_agent = {} - -@contextmanager -def store_tool_execution(): - """Context manager to store tool execution details for later attachment to agent spans.""" - parent_span = get_current_span() - parent_span_id = getattr(parent_span.get_span_context(), "span_id", None) - - if parent_span_id: - if parent_span_id not in _tool_executions_by_agent: - _tool_executions_by_agent[parent_span_id] = [] - - tool_details = {} - - try: - yield tool_details - - if tool_details: - _tool_executions_by_agent[parent_span_id].append(tool_details) - finally: - pass - - -def attach_tool_executions_to_agent_span(span): - """Attach stored tool executions to the agent span.""" - span_id = getattr(span.get_span_context(), "span_id", None) - - if span_id and span_id in _tool_executions_by_agent: - for idx, tool_execution in enumerate(_tool_executions_by_agent[span_id]): - for key, value in tool_execution.items(): - if value is not None: - span.set_attribute(f"crewai.agent.tool_execution.{idx}.{key}", str(value)) - - del _tool_executions_by_agent[span_id] - - -class CrewAIInstrumentor(BaseInstrumentor): - def instrumentation_dependencies(self) -> Collection[str]: - return _instruments - - def _instrument(self, **kwargs): - application_name = kwargs.get("application_name", "default_application") - environment = kwargs.get("environment", "default_environment") - tracer_provider = kwargs.get("tracer_provider") - tracer = get_tracer(__name__, __version__, tracer_provider) - - meter_provider = kwargs.get("meter_provider") - meter = get_meter(__name__, __version__, meter_provider) - - if is_metrics_enabled(): - ( - token_histogram, - duration_histogram, - ) = _create_metrics(meter) - else: - ( - token_histogram, - duration_histogram, - ) = (None, None) - - wrap_function_wrapper("crewai.crew", "Crew.kickoff", wrap_kickoff(tracer, duration_histogram, token_histogram, environment, application_name)) - wrap_function_wrapper( - "crewai.agent", "Agent.execute_task", wrap_agent_execute_task(tracer, duration_histogram, token_histogram, environment, application_name) - ) - wrap_function_wrapper( - "crewai.task", "Task.execute_sync", wrap_task_execute(tracer, duration_histogram, token_histogram, environment, application_name) - ) - wrap_function_wrapper("crewai.llm", "LLM.call", wrap_llm_call(tracer, duration_histogram, token_histogram, environment, application_name)) - - wrap_function_wrapper( - "crewai.utilities.tool_utils", "execute_tool_and_check_finality", - wrap_tool_execution(tracer, duration_histogram, environment, application_name) - ) - - wrap_function_wrapper( - "crewai.tools.tool_usage", "ToolUsage.use", - wrap_tool_usage(tracer, environment, application_name) - ) - - def _uninstrument(self, **kwargs): - unwrap("crewai.crew", "Crew.kickoff") - unwrap("crewai.agent", "Agent.execute_task") - unwrap("crewai.task", "Task.execute_sync") - unwrap("crewai.llm", "LLM.call") - unwrap("crewai.utilities.tool_utils", "execute_tool_and_check_finality") - unwrap("crewai.tools.tool_usage", "ToolUsage.use") - - -def with_tracer_wrapper(func): - """Helper for providing tracer for wrapper functions.""" - - def _with_tracer(tracer, duration_histogram, token_histogram, environment, application_name): - def wrapper(wrapped, instance, args, kwargs): - return func(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs) - - return wrapper - - return _with_tracer - - -@with_tracer_wrapper -def wrap_kickoff( - tracer: Tracer, duration_histogram: Histogram, token_histogram: Histogram, environment, application_name, wrapped, instance, args, kwargs -): - logger.debug(f"CrewAI: Starting workflow instrumentation for Crew with {len(getattr(instance, 'agents', []))} agents") - with tracer.start_as_current_span( - "crewai.workflow", - kind=SpanKind.INTERNAL, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - }, - ) as span: - try: - span.set_attribute(TELEMETRY_SDK_NAME, "agentops") - span.set_attribute(SERVICE_NAME, application_name) - span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - - logger.debug("CrewAI: Processing crew instance attributes") - - # First set general crew attributes but skip agent processing - crew_attrs = CrewAISpanAttributes(span=span, instance=instance, skip_agent_processing=True) - - # Prioritize agent processing before task execution - if hasattr(instance, 'agents') and instance.agents: - logger.debug(f"CrewAI: Explicitly processing {len(instance.agents)} agents before task execution") - crew_attrs._parse_agents(instance.agents) - - logger.debug("CrewAI: Executing wrapped crew kickoff function") - result = wrapped(*args, **kwargs) - - if result: - class_name = instance.__class__.__name__ - span.set_attribute(f"crewai.{class_name.lower()}.result", str(result)) - span.set_status(Status(StatusCode.OK)) - if class_name == "Crew": - if hasattr(result, "usage_metrics"): - span.set_attribute("crewai.crew.usage_metrics", str(getattr(result, "usage_metrics"))) - - if hasattr(result, "tasks_output") and result.tasks_output: - span.set_attribute("crewai.crew.tasks_output", str(result.tasks_output)) - - try: - task_details_by_description = {} - if hasattr(instance, "tasks"): - for task in instance.tasks: - if task is not None: - agent_id = "" - agent_role = "" - if hasattr(task, "agent") and task.agent: - agent_id = str(getattr(task.agent, "id", "")) - agent_role = getattr(task.agent, "role", "") - - tools = [] - if hasattr(task, "tools") and task.tools: - for tool in task.tools: - tool_info = {} - if hasattr(tool, "name"): - tool_info["name"] = tool.name - if hasattr(tool, "description"): - tool_info["description"] = tool.description - if tool_info: - tools.append(tool_info) - - task_details_by_description[task.description] = { - "agent_id": agent_id, - "agent_role": agent_role, - "async_execution": getattr(task, "async_execution", False), - "human_input": getattr(task, "human_input", False), - "output_file": getattr(task, "output_file", ""), - "tools": tools - } - - for idx, task_output in enumerate(result.tasks_output): - task_prefix = f"crewai.crew.tasks.{idx}" - - task_attrs = { - "description": getattr(task_output, "description", ""), - "name": getattr(task_output, "name", ""), - "expected_output": getattr(task_output, "expected_output", ""), - "summary": getattr(task_output, "summary", ""), - "raw": getattr(task_output, "raw", ""), - "agent": getattr(task_output, "agent", ""), - "output_format": str(getattr(task_output, "output_format", "")), - } - - for attr_name, attr_value in task_attrs.items(): - if attr_value: - if attr_name == "raw" and len(str(attr_value)) > 1000: - attr_value = str(attr_value)[:997] + "..." - span.set_attribute(f"{task_prefix}.{attr_name}", str(attr_value)) - - span.set_attribute(f"{task_prefix}.status", "completed") - span.set_attribute(f"{task_prefix}.id", str(idx)) - - description = task_attrs.get("description", "") - if description and description in task_details_by_description: - details = task_details_by_description[description] - - span.set_attribute(f"{task_prefix}.agent_id", details["agent_id"]) - span.set_attribute(f"{task_prefix}.async_execution", str(details["async_execution"])) - span.set_attribute(f"{task_prefix}.human_input", str(details["human_input"])) - - if details["output_file"]: - span.set_attribute(f"{task_prefix}.output_file", details["output_file"]) - - for tool_idx, tool in enumerate(details["tools"]): - for tool_key, tool_value in tool.items(): - span.set_attribute(f"{task_prefix}.tools.{tool_idx}.{tool_key}", str(tool_value)) - except Exception as ex: - logger.warning(f"Failed to parse task outputs: {ex}") - - if hasattr(result, "token_usage"): - token_usage = str(getattr(result, "token_usage")) - span.set_attribute("crewai.crew.token_usage", token_usage) - - try: - metrics = {} - for item in token_usage.split(): - if "=" in item: - key, value = item.split("=") - try: - metrics[key] = int(value) - except ValueError: - metrics[key] = value - - if "total_tokens" in metrics: - span.set_attribute(SpanAttributes.LLM_USAGE_TOTAL_TOKENS, metrics["total_tokens"]) - if "prompt_tokens" in metrics: - span.set_attribute(SpanAttributes.LLM_USAGE_PROMPT_TOKENS, metrics["prompt_tokens"]) - if "completion_tokens" in metrics: - span.set_attribute(SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, metrics["completion_tokens"]) - if "cached_prompt_tokens" in metrics: - span.set_attribute(SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS, metrics["cached_prompt_tokens"]) - if "successful_requests" in metrics: - span.set_attribute("crewai.crew.successful_requests", metrics["successful_requests"]) - - if "prompt_tokens" in metrics and "completion_tokens" in metrics and metrics["prompt_tokens"] > 0: - efficiency = metrics["completion_tokens"] / metrics["prompt_tokens"] - span.set_attribute("crewai.crew.token_efficiency", f"{efficiency:.4f}") - - if "cached_prompt_tokens" in metrics and "prompt_tokens" in metrics and metrics["prompt_tokens"] > 0: - cache_ratio = metrics["cached_prompt_tokens"] / metrics["prompt_tokens"] - span.set_attribute("crewai.crew.cache_efficiency", f"{cache_ratio:.4f}") - except Exception as ex: - logger.warning(f"Failed to parse token usage metrics: {ex}") - return result - except Exception as ex: - span.set_status(Status(StatusCode.ERROR, str(ex))) - logger.error("Error in trace creation: %s", ex) - raise - - -@with_tracer_wrapper -def wrap_agent_execute_task(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs): - agent_name = instance.role if hasattr(instance, "role") else "agent" - with tracer.start_as_current_span( - f"{agent_name}.agent", - kind=SpanKind.CLIENT, - attributes={ - SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.AGENT.value, - }, - ) as span: - try: - span.set_attribute(TELEMETRY_SDK_NAME, "agentops") - span.set_attribute(SERVICE_NAME, application_name) - span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - - CrewAISpanAttributes(span=span, instance=instance) - - result = wrapped(*args, **kwargs) - - attach_tool_executions_to_agent_span(span) - - if token_histogram and hasattr(instance, "_token_process"): - token_histogram.record( - instance._token_process.get_summary().prompt_tokens, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - SpanAttributes.LLM_TOKEN_TYPE: "input", - SpanAttributes.LLM_RESPONSE_MODEL: str(instance.llm.model), - }, - ) - token_histogram.record( - instance._token_process.get_summary().completion_tokens, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - SpanAttributes.LLM_TOKEN_TYPE: "output", - SpanAttributes.LLM_RESPONSE_MODEL: str(instance.llm.model), - }, - ) - - if hasattr(instance, "llm") and hasattr(instance.llm, "model"): - set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, str(instance.llm.model)) - set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, str(instance.llm.model)) - - span.set_status(Status(StatusCode.OK)) - return result - except Exception as ex: - span.set_status(Status(StatusCode.ERROR, str(ex))) - logger.error("Error in trace creation: %s", ex) - raise - - -@with_tracer_wrapper -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" - - with tracer.start_as_current_span( - f"{task_name}.task", - kind=SpanKind.CLIENT, - attributes={ - SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value, - }, - ) as span: - try: - span.set_attribute(TELEMETRY_SDK_NAME, "agentops") - span.set_attribute(SERVICE_NAME, application_name) - span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - - CrewAISpanAttributes(span=span, instance=instance) - - result = wrapped(*args, **kwargs) - - set_span_attribute(span, SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(result)) - span.set_status(Status(StatusCode.OK)) - return result - except Exception as ex: - span.set_status(Status(StatusCode.ERROR, str(ex))) - logger.error("Error in trace creation: %s", ex) - raise - - -@with_tracer_wrapper -def wrap_llm_call(tracer, duration_histogram, token_histogram, environment, application_name, wrapped, instance, args, kwargs): - llm = instance.model if hasattr(instance, "model") else "llm" - with tracer.start_as_current_span(f"{llm}.llm", kind=SpanKind.CLIENT, attributes={}) as span: - start_time = time.time() - try: - span.set_attribute(TELEMETRY_SDK_NAME, "agentops") - span.set_attribute(SERVICE_NAME, application_name) - span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - - CrewAISpanAttributes(span=span, instance=instance) - - result = wrapped(*args, **kwargs) - - if duration_histogram: - duration = time.time() - start_time - duration_histogram.record( - duration, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - SpanAttributes.LLM_RESPONSE_MODEL: str(instance.model), - }, - ) - - span.set_status(Status(StatusCode.OK)) - return result - except Exception as ex: - span.set_status(Status(StatusCode.ERROR, str(ex))) - logger.error("Error in trace creation: %s", ex) - raise - - -def wrap_tool_execution(tracer, duration_histogram, environment, application_name): - """Wrapper for tool execution function.""" - def wrapper(wrapped, instance, args, kwargs): - agent_action = args[0] if args else None - tools = args[1] if len(args) > 1 else [] - - if not agent_action: - return wrapped(*args, **kwargs) - - tool_name = getattr(agent_action, "tool", "unknown_tool") - tool_input = getattr(agent_action, "tool_input", "") - - with store_tool_execution() as tool_details: - tool_details["name"] = tool_name - tool_details["parameters"] = str(tool_input) - - matching_tool = next((tool for tool in tools if hasattr(tool, "name") and tool.name == tool_name), None) - if matching_tool and hasattr(matching_tool, "description"): - tool_details["description"] = str(matching_tool.description) - - with tracer.start_as_current_span( - f"{tool_name}.tool", - kind=SpanKind.CLIENT, - attributes={ - SpanAttributes.AGENTOPS_SPAN_KIND: "tool", - ToolAttributes.TOOL_NAME: tool_name, - ToolAttributes.TOOL_PARAMETERS: str(tool_input), - }, - ) as span: - start_time = time.time() - try: - span.set_attribute(TELEMETRY_SDK_NAME, "agentops") - span.set_attribute(SERVICE_NAME, application_name) - span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - - if matching_tool and hasattr(matching_tool, "description"): - span.set_attribute(ToolAttributes.TOOL_DESCRIPTION, str(matching_tool.description)) - - result = wrapped(*args, **kwargs) - - if duration_histogram: - duration = time.time() - start_time - duration_histogram.record( - duration, - attributes={ - SpanAttributes.LLM_SYSTEM: "crewai", - ToolAttributes.TOOL_NAME: tool_name, - }, - ) - - if hasattr(result, "result"): - tool_result = str(result.result) - span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result) - tool_details["result"] = tool_result - - tool_status = "success" if not hasattr(result, "error") or not result.error else "error" - span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) - tool_details["status"] = tool_status - - if hasattr(result, "error") and result.error: - tool_details["error"] = str(result.error) - - duration = time.time() - start_time - tool_details["duration"] = f"{duration:.3f}" - - span.set_status(Status(StatusCode.OK)) - return result - except Exception as ex: - tool_status = "error" - span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) - tool_details["status"] = tool_status - tool_details["error"] = str(ex) - - span.set_status(Status(StatusCode.ERROR, str(ex))) - logger.error(f"Error in tool execution trace: {ex}") - raise - - return wrapper - - -def wrap_tool_usage(tracer, environment, application_name): - """Wrapper for ToolUsage.use method.""" - def wrapper(wrapped, instance, args, kwargs): - calling = args[0] if args else None - tool_string = args[1] if len(args) > 1 else "" - - if not calling: - return wrapped(*args, **kwargs) - - tool_name = getattr(calling, "tool_name", "unknown_tool") - - with store_tool_execution() as tool_details: - tool_details["name"] = tool_name - - if hasattr(calling, "arguments") and calling.arguments: - tool_details["parameters"] = str(calling.arguments) - - with tracer.start_as_current_span( - f"{tool_name}.tool_usage", - kind=SpanKind.INTERNAL, - attributes={ - SpanAttributes.AGENTOPS_SPAN_KIND: "tool.usage", - ToolAttributes.TOOL_NAME: tool_name, - }, - ) as span: - try: - span.set_attribute(TELEMETRY_SDK_NAME, "agentops") - span.set_attribute(SERVICE_NAME, application_name) - span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment) - - if hasattr(calling, "arguments") and calling.arguments: - span.set_attribute(ToolAttributes.TOOL_PARAMETERS, str(calling.arguments)) - - result = wrapped(*args, **kwargs) - - tool_result = str(result) - span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result) - tool_details["result"] = tool_result - - tool_status = "success" - span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) - tool_details["status"] = tool_status - - span.set_status(Status(StatusCode.OK)) - return result - except Exception as ex: - tool_status = "error" - span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status) - tool_details["status"] = tool_status - tool_details["error"] = str(ex) - - span.set_status(Status(StatusCode.ERROR, str(ex))) - logger.error(f"Error in tool usage trace: {ex}") - raise - - return wrapper - - -def is_metrics_enabled() -> bool: - return (os.getenv("AGENTOPS_METRICS_ENABLED") or "true").lower() == "true" - - -def _create_metrics(meter: Meter): - token_histogram = meter.create_histogram( - name=Meters.LLM_TOKEN_USAGE, - unit="token", - description="Measures number of input and output tokens used", - ) - - duration_histogram = meter.create_histogram( - name=Meters.LLM_OPERATION_DURATION, - unit="s", - description="GenAI operation duration", - ) - - return token_histogram, duration_histogram diff --git a/third_party/opentelemetry/instrumentation/crewai/version.py b/third_party/opentelemetry/instrumentation/crewai/version.py deleted file mode 100644 index d9f2629e2..000000000 --- a/third_party/opentelemetry/instrumentation/crewai/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.36.0"