opentelemetry-instrumentation-crewai 0.36.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- opentelemetry/instrumentation/crewai/__init__.py +5 -0
- opentelemetry/instrumentation/crewai/crewai_span_attributes.py +150 -0
- opentelemetry/instrumentation/crewai/instrumentation.py +127 -0
- opentelemetry/instrumentation/crewai/version.py +1 -0
- opentelemetry_instrumentation_crewai-0.36.0.dist-info/METADATA +56 -0
- opentelemetry_instrumentation_crewai-0.36.0.dist-info/RECORD +8 -0
- opentelemetry_instrumentation_crewai-0.36.0.dist-info/WHEEL +4 -0
- opentelemetry_instrumentation_crewai-0.36.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
from opentelemetry.trace import Span
|
2
|
+
import json
|
3
|
+
|
4
|
+
|
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
|
10
|
+
|
11
|
+
|
12
|
+
class CrewAISpanAttributes:
|
13
|
+
def __init__(self, span: Span, instance) -> None:
|
14
|
+
self.span = span
|
15
|
+
self.instance = instance
|
16
|
+
self.crew = {"tasks": [], "agents": [], "llms": []}
|
17
|
+
self.process_instance()
|
18
|
+
|
19
|
+
def process_instance(self):
|
20
|
+
instance_type = self.instance.__class__.__name__
|
21
|
+
method_mapping = {
|
22
|
+
"Crew": self._process_crew,
|
23
|
+
"Agent": self._process_agent,
|
24
|
+
"Task": self._process_task,
|
25
|
+
"LLM": self._process_llm,
|
26
|
+
}
|
27
|
+
method = method_mapping.get(instance_type)
|
28
|
+
if method:
|
29
|
+
method()
|
30
|
+
|
31
|
+
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):
|
52
|
+
for key, value in self.instance.__dict__.items():
|
53
|
+
if value is None:
|
54
|
+
continue
|
55
|
+
if key == "tasks":
|
56
|
+
self._parse_tasks(value)
|
57
|
+
elif key == "agents":
|
58
|
+
self._parse_agents(value)
|
59
|
+
elif key == "llms":
|
60
|
+
self._parse_llms(value)
|
61
|
+
else:
|
62
|
+
self.crew[key] = str(value)
|
63
|
+
|
64
|
+
def _populate_agent_attributes(self):
|
65
|
+
return self._extract_attributes(self.instance)
|
66
|
+
|
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
|
72
|
+
|
73
|
+
def _populate_llm_attributes(self):
|
74
|
+
return self._extract_attributes(self.instance)
|
75
|
+
|
76
|
+
def _parse_agents(self, agents):
|
77
|
+
self.crew["agents"] = [
|
78
|
+
self._extract_agent_data(agent) for agent in agents if agent is not None
|
79
|
+
]
|
80
|
+
|
81
|
+
def _parse_tasks(self, tasks):
|
82
|
+
self.crew["tasks"] = [
|
83
|
+
{
|
84
|
+
"agent": task.agent.role if task.agent else None,
|
85
|
+
"description": task.description,
|
86
|
+
"async_execution": task.async_execution,
|
87
|
+
"expected_output": task.expected_output,
|
88
|
+
"human_input": task.human_input,
|
89
|
+
"tools": task.tools,
|
90
|
+
"output_file": task.output_file,
|
91
|
+
}
|
92
|
+
for task in tasks
|
93
|
+
]
|
94
|
+
|
95
|
+
def _parse_llms(self, llms):
|
96
|
+
self.crew["tasks"] = [
|
97
|
+
{
|
98
|
+
"temperature": llm.temperature,
|
99
|
+
"max_tokens": llm.max_tokens,
|
100
|
+
"max_completion_tokens": llm.max_completion_tokens,
|
101
|
+
"top_p": llm.top_p,
|
102
|
+
"n": llm.n,
|
103
|
+
"seed": llm.seed,
|
104
|
+
"base_url": llm.base_url,
|
105
|
+
"api_version": llm.api_version, }
|
106
|
+
for llm in llms
|
107
|
+
]
|
108
|
+
|
109
|
+
def _extract_agent_data(self, agent):
|
110
|
+
model = (
|
111
|
+
getattr(agent.llm, "model", None)
|
112
|
+
or getattr(agent.llm, "model_name", None)
|
113
|
+
or ""
|
114
|
+
)
|
115
|
+
|
116
|
+
return {
|
117
|
+
"id": str(agent.id),
|
118
|
+
"role": agent.role,
|
119
|
+
"goal": agent.goal,
|
120
|
+
"backstory": agent.backstory,
|
121
|
+
"cache": agent.cache,
|
122
|
+
"config": agent.config,
|
123
|
+
"verbose": agent.verbose,
|
124
|
+
"allow_delegation": agent.allow_delegation,
|
125
|
+
"tools": agent.tools,
|
126
|
+
"max_iter": agent.max_iter,
|
127
|
+
"llm": str(model), }
|
128
|
+
|
129
|
+
def _extract_attributes(self, obj):
|
130
|
+
attributes = {}
|
131
|
+
for key, value in obj.__dict__.items():
|
132
|
+
if value is None:
|
133
|
+
continue
|
134
|
+
if key == "tools":
|
135
|
+
attributes[key] = self._serialize_tools(value)
|
136
|
+
else:
|
137
|
+
attributes[key] = str(value)
|
138
|
+
return attributes
|
139
|
+
|
140
|
+
def _serialize_tools(self, tools):
|
141
|
+
return json.dumps(
|
142
|
+
[
|
143
|
+
{k: v for k, v in vars(tool).items() if v is not None and k in ["name", "description"]}
|
144
|
+
for tool in tools
|
145
|
+
]
|
146
|
+
)
|
147
|
+
|
148
|
+
def _set_attribute(self, key, value):
|
149
|
+
if value:
|
150
|
+
set_span_attribute(self.span, key, str(value) if isinstance(value, list) else value)
|
@@ -0,0 +1,127 @@
|
|
1
|
+
from typing import Collection
|
2
|
+
from wrapt import wrap_function_wrapper
|
3
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
4
|
+
from opentelemetry.trace import SpanKind, get_tracer
|
5
|
+
from opentelemetry.trace.status import Status, StatusCode
|
6
|
+
from opentelemetry.instrumentation.crewai.version import __version__
|
7
|
+
from opentelemetry.semconv_ai import SpanAttributes, TraceloopSpanKindValues
|
8
|
+
from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute
|
9
|
+
|
10
|
+
_instruments = ("crewai >= 0.70.0",)
|
11
|
+
|
12
|
+
|
13
|
+
class CrewAIInstrumentor(BaseInstrumentor):
|
14
|
+
|
15
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
16
|
+
return _instruments
|
17
|
+
|
18
|
+
def _instrument(self, **kwargs):
|
19
|
+
tracer_provider = kwargs.get("tracer_provider")
|
20
|
+
tracer = get_tracer(__name__, __version__, tracer_provider)
|
21
|
+
|
22
|
+
wrap_function_wrapper("crewai.crew", "Crew.kickoff", wrap_kickoff(tracer))
|
23
|
+
wrap_function_wrapper("crewai.agent", "Agent.execute_task", wrap_agent_execute_task(tracer))
|
24
|
+
wrap_function_wrapper("crewai.task", "Task.execute_sync", wrap_task_execute(tracer))
|
25
|
+
wrap_function_wrapper("crewai.llm", "LLM.call", wrap_llm_call(tracer))
|
26
|
+
|
27
|
+
def _uninstrument(self, **kwargs):
|
28
|
+
pass
|
29
|
+
|
30
|
+
|
31
|
+
def with_tracer_wrapper(func):
|
32
|
+
"""Helper for providing tracer for wrapper functions."""
|
33
|
+
|
34
|
+
def _with_tracer(tracer):
|
35
|
+
def wrapper(wrapped, instance, args, kwargs):
|
36
|
+
return func(tracer, wrapped, instance, args, kwargs)
|
37
|
+
return wrapper
|
38
|
+
return _with_tracer
|
39
|
+
|
40
|
+
|
41
|
+
@with_tracer_wrapper
|
42
|
+
def wrap_kickoff(tracer, wrapped, instance, args, kwargs):
|
43
|
+
with tracer.start_as_current_span(
|
44
|
+
"crewai.workflow",
|
45
|
+
kind=SpanKind.INTERNAL,
|
46
|
+
attributes={
|
47
|
+
}
|
48
|
+
) as span:
|
49
|
+
try:
|
50
|
+
CrewAISpanAttributes(span=span, instance=instance)
|
51
|
+
result = wrapped(*args, **kwargs)
|
52
|
+
if result:
|
53
|
+
class_name = instance.__class__.__name__
|
54
|
+
span.set_attribute(f"crewai.{class_name.lower()}.result", str(result))
|
55
|
+
span.set_status(Status(StatusCode.OK))
|
56
|
+
if class_name == "Crew":
|
57
|
+
for attr in ["tasks_output", "token_usage", "usage_metrics"]:
|
58
|
+
if hasattr(result, attr):
|
59
|
+
span.set_attribute(f"crewai.crew.{attr}", str(getattr(result, attr)))
|
60
|
+
return result
|
61
|
+
except Exception as ex:
|
62
|
+
span.set_status(Status(StatusCode.ERROR, str(ex)))
|
63
|
+
raise
|
64
|
+
|
65
|
+
|
66
|
+
@with_tracer_wrapper
|
67
|
+
def wrap_agent_execute_task(tracer, wrapped, instance, args, kwargs):
|
68
|
+
agent_name = instance.role if hasattr(instance, "role") else "agent"
|
69
|
+
with tracer.start_as_current_span(
|
70
|
+
f"{agent_name}.agent",
|
71
|
+
kind=SpanKind.CLIENT,
|
72
|
+
attributes={
|
73
|
+
SpanAttributes.TRACELOOP_SPAN_KIND: TraceloopSpanKindValues.AGENT.value,
|
74
|
+
}
|
75
|
+
) as span:
|
76
|
+
try:
|
77
|
+
CrewAISpanAttributes(span=span, instance=instance)
|
78
|
+
result = wrapped(*args, **kwargs)
|
79
|
+
set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, str(instance.llm.model))
|
80
|
+
span.set_status(Status(StatusCode.OK))
|
81
|
+
return result
|
82
|
+
except Exception as ex:
|
83
|
+
span.set_status(Status(StatusCode.ERROR, str(ex)))
|
84
|
+
raise
|
85
|
+
|
86
|
+
|
87
|
+
@with_tracer_wrapper
|
88
|
+
def wrap_task_execute(tracer, wrapped, instance, args, kwargs):
|
89
|
+
task_name = instance.description if hasattr(instance, "description") else "task"
|
90
|
+
|
91
|
+
with tracer.start_as_current_span(
|
92
|
+
f"{task_name}.task",
|
93
|
+
kind=SpanKind.CLIENT,
|
94
|
+
attributes={
|
95
|
+
SpanAttributes.TRACELOOP_SPAN_KIND: TraceloopSpanKindValues.TASK.value,
|
96
|
+
}
|
97
|
+
) as span:
|
98
|
+
try:
|
99
|
+
CrewAISpanAttributes(span=span, instance=instance)
|
100
|
+
result = wrapped(*args, **kwargs)
|
101
|
+
set_span_attribute(span, SpanAttributes.TRACELOOP_ENTITY_OUTPUT, str(result))
|
102
|
+
span.set_status(Status(StatusCode.OK))
|
103
|
+
return result
|
104
|
+
except Exception as ex:
|
105
|
+
span.set_status(Status(StatusCode.ERROR, str(ex)))
|
106
|
+
raise
|
107
|
+
|
108
|
+
|
109
|
+
@with_tracer_wrapper
|
110
|
+
def wrap_llm_call(tracer, wrapped, instance, args, kwargs):
|
111
|
+
llm = instance.model if hasattr(instance, "model") else "llm"
|
112
|
+
with tracer.start_as_current_span(
|
113
|
+
f"{llm}.llm",
|
114
|
+
kind=SpanKind.CLIENT,
|
115
|
+
attributes={
|
116
|
+
}
|
117
|
+
) as span:
|
118
|
+
try:
|
119
|
+
CrewAISpanAttributes(span=span, instance=instance)
|
120
|
+
result = wrapped(*args, **kwargs)
|
121
|
+
set_span_attribute(span, SpanAttributes.LLM_SYSTEM, "crewai")
|
122
|
+
set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, str(instance.model))
|
123
|
+
span.set_status(Status(StatusCode.OK))
|
124
|
+
return result
|
125
|
+
except Exception as ex:
|
126
|
+
span.set_status(Status(StatusCode.ERROR, str(ex)))
|
127
|
+
raise
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.36.0"
|
@@ -0,0 +1,56 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: opentelemetry-instrumentation-crewai
|
3
|
+
Version: 0.36.0
|
4
|
+
Summary: OpenTelemetry crewAI instrumentation
|
5
|
+
Home-page: https://github.com/traceloop/openllmetry/tree/main/packages/opentelemetry-instrumentation-crewai
|
6
|
+
License: Apache-2.0
|
7
|
+
Author: Gal Kleinman
|
8
|
+
Author-email: gal@traceloop.com
|
9
|
+
Requires-Python: >=3.10,<=3.13
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
|
+
Provides-Extra: instruments
|
16
|
+
Requires-Dist: opentelemetry-api (>=1.28.0,<2.0.0)
|
17
|
+
Requires-Dist: opentelemetry-instrumentation (>=0.50b0)
|
18
|
+
Requires-Dist: opentelemetry-semantic-conventions (>=0.50b0)
|
19
|
+
Requires-Dist: opentelemetry-semantic-conventions-ai (==0.4.2)
|
20
|
+
Project-URL: Repository, https://github.com/traceloop/openllmetry/tree/main/packages/opentelemetry-instrumentation-crewai
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
|
23
|
+
# OpenTelemetry CrewAI Instrumentation
|
24
|
+
|
25
|
+
<a href="https://pypi.org/project/opentelemetry-instrumentation-crewai/">
|
26
|
+
<img src="https://badge.fury.io/py/opentelemetry-instrumentation-crewai.svg">
|
27
|
+
</a>
|
28
|
+
|
29
|
+
This library allows tracing agentic workflows implemented with crewAI framework [crewAI library](https://github.com/crewAIInc/crewAI).
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
```bash
|
34
|
+
pip install opentelemetry-instrumentation-crewai
|
35
|
+
```
|
36
|
+
|
37
|
+
## Example usage
|
38
|
+
|
39
|
+
```python
|
40
|
+
from opentelemetry.instrumentation.crewai import CrewAIInstrumentor
|
41
|
+
|
42
|
+
CrewAIInstrumentor().instrument()
|
43
|
+
```
|
44
|
+
|
45
|
+
## Privacy
|
46
|
+
|
47
|
+
**By default, this instrumentation logs prompts, completions, and embeddings to span attributes**. This gives you a clear visibility into how your LLM application is working, and can make it easy to debug and evaluate the quality of the outputs.
|
48
|
+
|
49
|
+
However, you may want to disable this logging for privacy reasons, as they may contain highly sensitive data from your users. You may also simply want to reduce the size of your traces.
|
50
|
+
|
51
|
+
To disable logging, set the `TRACELOOP_TRACE_CONTENT` environment variable to `false`.
|
52
|
+
|
53
|
+
```bash
|
54
|
+
TRACELOOP_TRACE_CONTENT=false
|
55
|
+
```
|
56
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
opentelemetry/instrumentation/crewai/__init__.py,sha256=zdNyWV62d5IaueXR9clUD_c9F32MF39AIchZf66XI2c,245
|
2
|
+
opentelemetry/instrumentation/crewai/crewai_span_attributes.py,sha256=9ax6fn0IT4dqH2iaRG8VIZ0SWTkxQrMTiF7DWUdwfy0,4945
|
3
|
+
opentelemetry/instrumentation/crewai/instrumentation.py,sha256=_Y8dmgwYoSRkDAYSoR9zNkgQYAk1aqSTkDoQIsjV5DI,4867
|
4
|
+
opentelemetry/instrumentation/crewai/version.py,sha256=ITH8IVCEQOekRajZpPo6sKNlMBNDRQRnzipIN0dqXoc,23
|
5
|
+
opentelemetry_instrumentation_crewai-0.36.0.dist-info/METADATA,sha256=kiaD-n1C8INWdka9UjRmiOgtBK9jq4TIh4EG5txya04,2155
|
6
|
+
opentelemetry_instrumentation_crewai-0.36.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
7
|
+
opentelemetry_instrumentation_crewai-0.36.0.dist-info/entry_points.txt,sha256=4Zs98MjB2HIaX8fk8evs41x1J1xBNfztvPN428bf_b8,93
|
8
|
+
opentelemetry_instrumentation_crewai-0.36.0.dist-info/RECORD,,
|