1- from opentelemetry .trace import Span
1+ """OpenTelemetry instrumentation for CrewAI."""
2+
23import json
4+ import logging
5+ from typing import Any
6+ from opentelemetry .trace import Span
7+
8+ from agentops .semconv .span_attributes import SpanAttributes
9+ from agentops .semconv .agent import AgentAttributes
10+ from agentops .semconv .workflow import WorkflowAttributes
11+
12+ # Initialize logger for logging potential issues and operations
13+ logger = logging .getLogger (__name__ )
314
15+ def _parse_tools (tools ):
16+ """Parse tools into a JSON string with name and description."""
17+ result = []
18+ for tool in tools :
19+ res = {}
20+ if hasattr (tool , "name" ) and tool .name is not None :
21+ res ["name" ] = tool .name
22+ if hasattr (tool , "description" ) and tool .description is not None :
23+ res ["description" ] = tool .description
24+ if res :
25+ result .append (res )
26+ return json .dumps (result )
427
5- def set_span_attribute (span : Span , name , value ):
6- if value is not None :
7- if value != "" :
8- span .set_attribute (name , value )
9- return
28+ def set_span_attribute (span : Span , key : str , value : Any ) -> None :
29+ """Set a single attribute on a span."""
30+ if value is not None and value != "" :
31+ span .set_attribute (key , value )
1032
1133
1234class CrewAISpanAttributes :
35+ """Manages span attributes for CrewAI instrumentation."""
36+
1337 def __init__ (self , span : Span , instance ) -> None :
1438 self .span = span
1539 self .instance = instance
16- self .crew = {"tasks" : [], "agents" : [], "llms" : []}
1740 self .process_instance ()
1841
1942 def process_instance (self ):
43+ """Process the instance based on its type."""
2044 instance_type = self .instance .__class__ .__name__
2145 method_mapping = {
2246 "Crew" : self ._process_crew ,
@@ -29,26 +53,7 @@ def process_instance(self):
2953 method ()
3054
3155 def _process_crew (self ):
32- self ._populate_crew_attributes ()
33- for key , value in self .crew .items ():
34- self ._set_attribute (f"crewai.crew.{ key } " , value )
35-
36- def _process_agent (self ):
37- agent_data = self ._populate_agent_attributes ()
38- for key , value in agent_data .items ():
39- self ._set_attribute (f"crewai.agent.{ key } " , value )
40-
41- def _process_task (self ):
42- task_data = self ._populate_task_attributes ()
43- for key , value in task_data .items ():
44- self ._set_attribute (f"crewai.task.{ key } " , value )
45-
46- def _process_llm (self ):
47- llm_data = self ._populate_llm_attributes ()
48- for key , value in llm_data .items ():
49- self ._set_attribute (f"crewai.llm.{ key } " , value )
50-
51- def _populate_crew_attributes (self ):
56+ """Process a Crew instance."""
5257 for key , value in self .instance .__dict__ .items ():
5358 if value is None :
5459 continue
@@ -59,53 +64,117 @@ def _populate_crew_attributes(self):
5964 elif key == "llms" :
6065 self ._parse_llms (value )
6166 else :
62- self .crew [ key ] = str (value )
67+ self ._set_attribute ( f"crewai. crew. { key } " , str (value ) )
6368
64- def _populate_agent_attributes (self ):
65- return self ._extract_attributes (self .instance )
69+ def _process_agent (self ):
70+ """Process an Agent instance."""
71+ agent = {}
72+ for key , value in self .instance .__dict__ .items ():
73+ if key == "tools" :
74+ value = _parse_tools (value )
75+ if value is None :
76+ continue
77+ agent [key ] = str (value )
78+
79+ # Set agent attributes using our semantic conventions
80+ self ._set_attribute (AgentAttributes .AGENT_ID , agent .get ('id' , '' ))
81+ self ._set_attribute (AgentAttributes .AGENT_ROLE , agent .get ('role' , '' ))
82+ self ._set_attribute (AgentAttributes .AGENT_NAME , agent .get ('name' , '' ))
83+ self ._set_attribute (AgentAttributes .AGENT_TOOLS , agent .get ('tools' , '' ))
84+
85+ self ._set_attribute ("crewai.agent.goal" , agent .get ('goal' , '' ))
86+ self ._set_attribute ("crewai.agent.backstory" , agent .get ('backstory' , '' ))
87+ self ._set_attribute ("crewai.agent.cache" , agent .get ('cache' , '' ))
88+ self ._set_attribute ("crewai.agent.allow_delegation" , agent .get ('allow_delegation' , '' ))
89+ self ._set_attribute ("crewai.agent.allow_code_execution" , agent .get ('allow_code_execution' , '' ))
90+ self ._set_attribute ("crewai.agent.max_retry_limit" , agent .get ('max_retry_limit' , '' ))
91+ self ._set_attribute ("crewai.agent.tools_results" , agent .get ('tools_results' , '' ))
6692
67- def _populate_task_attributes (self ):
68- task_data = self ._extract_attributes (self .instance )
69- if "agent" in task_data :
70- task_data ["agent" ] = self .instance .agent .role if self .instance .agent else None
71- return task_data
93+ def _process_task (self ):
94+ """Process a Task instance."""
95+ task = {}
96+ for key , value in self .instance .__dict__ .items ():
97+ if value is None :
98+ continue
99+ if key == "tools" :
100+ value = _parse_tools (value )
101+ task [key ] = value
102+ elif key == "agent" :
103+ task [key ] = value .role if value else None
104+ else :
105+ task [key ] = str (value )
106+
107+ # Set task attributes using our semantic conventions
108+ self ._set_attribute (WorkflowAttributes .WORKFLOW_STEP_NAME , task .get ('description' , '' ))
109+ self ._set_attribute (WorkflowAttributes .WORKFLOW_STEP_TYPE , "task" )
110+ self ._set_attribute (WorkflowAttributes .WORKFLOW_STEP_INPUT , task .get ('context' , '' ))
111+ self ._set_attribute (WorkflowAttributes .WORKFLOW_STEP_OUTPUT , task .get ('expected_output' , '' ))
112+
113+ self ._set_attribute ("crewai.task.id" , task .get ('id' , '' ))
114+ self ._set_attribute ("crewai.task.agent" , task .get ('agent' , '' ))
115+ self ._set_attribute ("crewai.task.human_input" , task .get ('human_input' , '' ))
116+ self ._set_attribute ("crewai.task.output" , task .get ('output' , '' ))
117+ self ._set_attribute ("crewai.task.processed_by_agents" , str (task .get ('processed_by_agents' , '' )))
72118
73- def _populate_llm_attributes (self ):
74- return self ._extract_attributes (self .instance )
119+ def _process_llm (self ):
120+ """Process an LLM instance."""
121+ llm = {}
122+ for key , value in self .instance .__dict__ .items ():
123+ if value is None :
124+ continue
125+ llm [key ] = str (value )
126+
127+ # Set LLM attributes using our semantic conventions
128+ self ._set_attribute (SpanAttributes .LLM_REQUEST_MODEL , llm .get ('model_name' , '' ))
129+ self ._set_attribute (SpanAttributes .LLM_REQUEST_TEMPERATURE , llm .get ('temperature' , '' ))
130+ self ._set_attribute (SpanAttributes .LLM_REQUEST_MAX_TOKENS , llm .get ('max_tokens' , '' ))
131+ self ._set_attribute (SpanAttributes .LLM_REQUEST_TOP_P , llm .get ('top_p' , '' ))
75132
76133 def _parse_agents (self , agents ):
77- self .crew ["agents" ] = [self ._extract_agent_data (agent ) for agent in agents if agent is not None ]
134+ """Parse agents into a list of dictionaries."""
135+ for agent in agents :
136+ if agent is not None :
137+ agent_data = self ._extract_agent_data (agent )
138+ for key , value in agent_data .items ():
139+ self ._set_attribute (f"crewai.agent.{ key } " , value )
78140
79141 def _parse_tasks (self , tasks ):
80- self .crew ["tasks" ] = [
81- {
82- "agent" : task .agent .role if task .agent else None ,
83- "description" : task .description ,
84- "async_execution" : task .async_execution ,
85- "expected_output" : task .expected_output ,
86- "human_input" : task .human_input ,
87- "tools" : task .tools ,
88- "output_file" : task .output_file ,
89- }
90- for task in tasks
91- ]
142+ """Parse tasks into a list of dictionaries."""
143+ for task in tasks :
144+ if task is not None :
145+ task_data = {
146+ "agent" : task .agent .role if task .agent else None ,
147+ "description" : task .description ,
148+ "async_execution" : task .async_execution ,
149+ "expected_output" : task .expected_output ,
150+ "human_input" : task .human_input ,
151+ "tools" : task .tools ,
152+ "output_file" : task .output_file ,
153+ }
154+ for key , value in task_data .items ():
155+ if value is not None :
156+ self ._set_attribute (f"crewai.task.{ key } " , str (value ))
92157
93158 def _parse_llms (self , llms ):
94- self .crew ["tasks" ] = [
95- {
96- "temperature" : llm .temperature ,
97- "max_tokens" : llm .max_tokens ,
98- "max_completion_tokens" : llm .max_completion_tokens ,
99- "top_p" : llm .top_p ,
100- "n" : llm .n ,
101- "seed" : llm .seed ,
102- "base_url" : llm .base_url ,
103- "api_version" : llm .api_version ,
104- }
105- for llm in llms
106- ]
159+ """Parse LLMs into a list of dictionaries."""
160+ for llm in llms :
161+ if llm is not None :
162+ llm_data = {
163+ "temperature" : llm .temperature ,
164+ "max_tokens" : llm .max_tokens ,
165+ "max_completion_tokens" : llm .max_completion_tokens ,
166+ "top_p" : llm .top_p ,
167+ "n" : llm .n ,
168+ "seed" : llm .seed ,
169+ "base_url" : llm .base_url ,
170+ "api_version" : llm .api_version ,
171+ }
172+ for key , value in llm_data .items ():
173+ if value is not None :
174+ self ._set_attribute (f"crewai.llm.{ key } " , str (value ))
107175
108176 def _extract_agent_data (self , agent ):
177+ """Extract data from an agent."""
109178 model = getattr (agent .llm , "model" , None ) or getattr (agent .llm , "model_name" , None ) or ""
110179
111180 return {
@@ -122,22 +191,7 @@ def _extract_agent_data(self, agent):
122191 "llm" : str (model ),
123192 }
124193
125- def _extract_attributes (self , obj ):
126- attributes = {}
127- for key , value in obj .__dict__ .items ():
128- if value is None :
129- continue
130- if key == "tools" :
131- attributes [key ] = self ._serialize_tools (value )
132- else :
133- attributes [key ] = str (value )
134- return attributes
135-
136- def _serialize_tools (self , tools ):
137- return json .dumps (
138- [{k : v for k , v in vars (tool ).items () if v is not None and k in ["name" , "description" ]} for tool in tools ]
139- )
140-
141194 def _set_attribute (self , key , value ):
142- if value :
143- set_span_attribute (self .span , key , str (value ) if isinstance (value , list ) else value )
195+ """Set an attribute on the span."""
196+ if value is not None and value != "" :
197+ set_span_attribute (self .span , key , value )
0 commit comments