Skip to content

Commit 66ace27

Browse files
Release 2.1.22 (#213)
* remove logs * remove requirements * DSPy instrumentation support (#210) * DSPy instrumentation basic * Fix * Fix * remove hardcodings * Bump version * CrewAI support (#212) * Write a stream wrapper * Example * run formatter * Bump version
1 parent a20edd9 commit 66ace27

File tree

12 files changed

+436
-213
lines changed

12 files changed

+436
-213
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
3+
os.environ["OPENAI_MODEL_NAME"] = "gpt-3.5-turbo"
4+
os.environ["SERPER_API_KEY"] = "" # serper.dev API key
5+
from langtrace_python_sdk import langtrace
6+
from langtrace_python_sdk.utils.with_root_span import with_langtrace_root_span
7+
from crewai import Crew, Process
8+
from crewai import Task
9+
from crewai import Agent
10+
from crewai_tools import SerperDevTool
11+
from crewai_tools import YoutubeVideoSearchTool
12+
13+
langtrace.init()
14+
15+
search_tool = SerperDevTool()
16+
17+
# Targeted search within a specific Youtube video's content
18+
youtube_tool = YoutubeVideoSearchTool(
19+
youtube_video_url="https://www.youtube.com/watch?v=blqIZGXWUpU"
20+
)
21+
22+
# Creating a senior researcher agent with memory and verbose mode
23+
researcher = Agent(
24+
role="Senior Researcher",
25+
goal="Uncover groundbreaking technologies in {topic}",
26+
verbose=True,
27+
memory=True,
28+
backstory=(
29+
"Driven by curiosity, you're at the forefront of"
30+
"innovation, eager to explore and share knowledge that could change"
31+
"the world."
32+
),
33+
tools=[youtube_tool],
34+
)
35+
36+
# Research task
37+
research_task = Task(
38+
description=(
39+
"Do a {topic} of the given youtube video."
40+
"Focus on identifying the overall narrative."
41+
"Your final report should clearly articulate the key points."
42+
),
43+
expected_output="10 key points from the shared video.",
44+
tools=[youtube_tool],
45+
agent=researcher,
46+
callback="research_callback", # Example of task callback
47+
human_input=True,
48+
)
49+
50+
51+
# Forming the tech-focused crew with some enhanced configurations
52+
crew = Crew(
53+
agents=[researcher],
54+
tasks=[research_task],
55+
process=Process.sequential, # Optional: Sequential task execution is default
56+
memory=False,
57+
cache=False,
58+
max_rpm=20,
59+
)
60+
61+
# Starting the task execution process with enhanced feedback
62+
63+
64+
@with_langtrace_root_span("Crew")
65+
def test_crew():
66+
result = crew.kickoff(inputs={"topic": "summary"})
67+
return result
68+
69+
70+
test_crew()

src/langtrace_python_sdk/constants/instrumentation/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"ANTHROPIC": "Anthropic",
1212
"AZURE": "Azure",
1313
"CHROMA": "Chroma",
14+
"CREWAI": "CrewAI",
1415
"DSPY": "DSPy",
1516
"GROQ": "Groq",
1617
"LANGCHAIN": "Langchain",

src/langtrace_python_sdk/instrumentation/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .anthropic import AnthropicInstrumentation
22
from .chroma import ChromaInstrumentation
33
from .cohere import CohereInstrumentation
4+
from .crewai import CrewAIInstrumentation
45
from .groq import GroqInstrumentation
56
from .langchain import LangchainInstrumentation
67
from .langchain_community import LangchainCommunityInstrumentation
@@ -12,12 +13,13 @@
1213
from .qdrant import QdrantInstrumentation
1314
from .weaviate import WeaviateInstrumentation
1415
from .ollama import OllamaInstrumentor
15-
from .dspy import DspyInstrumentor
16+
from .dspy import DspyInstrumentation
1617

1718
__all__ = [
1819
"AnthropicInstrumentation",
1920
"ChromaInstrumentation",
2021
"CohereInstrumentation",
22+
"CrewAIInstrumentation",
2123
"GroqInstrumentation",
2224
"LangchainInstrumentation",
2325
"LangchainCommunityInstrumentation",
@@ -29,5 +31,5 @@
2931
"QdrantInstrumentation",
3032
"WeaviateInstrumentation",
3133
"OllamaInstrumentor",
32-
"DspyInstrumentor",
34+
"DspyInstrumentation",
3335
]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .instrumentation import CrewAIInstrumentation
2+
3+
__all__ = ["CrewAIInstrumentation"]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
Copyright (c) 2024 Scale3 Labs
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
17+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
18+
from opentelemetry.trace import get_tracer
19+
from wrapt import wrap_function_wrapper as _W
20+
from typing import Collection
21+
from importlib_metadata import version as v
22+
from .patch import patch_crew
23+
24+
25+
class CrewAIInstrumentation(BaseInstrumentor):
26+
"""
27+
The CrewAIInstrumentation class represents the CrewAI instrumentation"""
28+
29+
def instrumentation_dependencies(self) -> Collection[str]:
30+
return ["crewai >= 0.32.0"]
31+
32+
def _instrument(self, **kwargs):
33+
tracer_provider = kwargs.get("tracer_provider")
34+
tracer = get_tracer(__name__, "", tracer_provider)
35+
version = v("crewai")
36+
_W(
37+
"crewai.crew",
38+
"Crew.kickoff",
39+
patch_crew("Crew.kickoff", version, tracer),
40+
)
41+
_W(
42+
"crewai.agent",
43+
"Agent.execute_task",
44+
patch_crew("Agent.execute_task", version, tracer),
45+
)
46+
_W(
47+
"crewai.task",
48+
"Task.execute",
49+
patch_crew("Task.execute", version, tracer),
50+
)
51+
52+
def _uninstrument(self, **kwargs):
53+
pass
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import json
2+
from importlib_metadata import version as v
3+
from langtrace_python_sdk.constants import LANGTRACE_SDK_NAME
4+
from langtrace_python_sdk.utils import set_span_attribute
5+
from langtrace_python_sdk.utils.silently_fail import silently_fail
6+
from langtrace_python_sdk.constants.instrumentation.common import (
7+
LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY,
8+
SERVICE_PROVIDERS,
9+
)
10+
from opentelemetry import baggage
11+
from langtrace.trace_attributes import FrameworkSpanAttributes
12+
from opentelemetry.trace import SpanKind
13+
from opentelemetry.trace.status import Status, StatusCode
14+
15+
16+
crew_properties = {
17+
"tasks": "object",
18+
"agents": "object",
19+
"cache": "bool",
20+
"process": "object",
21+
"verbose": "bool",
22+
"memory": "bool",
23+
"embedder": "json",
24+
"full_output": "bool",
25+
"manager_llm": "object",
26+
"manager_agent": "object",
27+
"manager_callbacks": "object",
28+
"function_calling_llm": "object",
29+
"config": "json",
30+
"id": "object",
31+
"max_rpm": "int",
32+
"share_crew": "bool",
33+
"step_callback": "object",
34+
"task_callback": "object",
35+
"prompt_file": "object",
36+
"output_log_file": "object",
37+
}
38+
39+
task_properties = {
40+
"id": "object",
41+
"used_tools": "int",
42+
"tools_errors": "int",
43+
"delegations": "int",
44+
"i18n": "object",
45+
"thread": "object",
46+
"prompt_context": "object",
47+
"description": "str",
48+
"expected_output": "str",
49+
"config": "object",
50+
"callback": "str",
51+
"agent": "object",
52+
"context": "object",
53+
"async_execution": "bool",
54+
"output_json": "object",
55+
"output_pydantic": "object",
56+
"output_file": "object",
57+
"output": "object",
58+
"tools": "object",
59+
"human_input": "bool",
60+
}
61+
62+
agent_properties = {
63+
"formatting_errors": "int",
64+
"id": "object",
65+
"role": "str",
66+
"goal": "str",
67+
"backstory": "str",
68+
"cache": "bool",
69+
"config": "object",
70+
"max_rpm": "int",
71+
"verbose": "bool",
72+
"allow_delegation": "bool",
73+
"tools": "object",
74+
"max_iter": "int",
75+
"max_execution_time": "object",
76+
"agent_executor": "object",
77+
"tools_handler": "object",
78+
"force_answer_max_iterations": "int",
79+
"crew": "object",
80+
"cache_handler": "object",
81+
"step_callback": "object",
82+
"i18n": "object",
83+
"llm": "object",
84+
"function_calling_llm": "object",
85+
"callbacks": "object",
86+
"system_template": "object",
87+
"prompt_template": "object",
88+
"response_template": "object",
89+
}
90+
91+
92+
def patch_crew(operation_name, version, tracer):
93+
def traced_method(wrapped, instance, args, kwargs):
94+
95+
service_provider = SERVICE_PROVIDERS["CREWAI"]
96+
extra_attributes = baggage.get_baggage(LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY)
97+
span_attributes = {
98+
"langtrace.sdk.name": "langtrace-python-sdk",
99+
"langtrace.service.name": service_provider,
100+
"langtrace.service.type": "framework",
101+
"langtrace.service.version": version,
102+
"langtrace.version": v(LANGTRACE_SDK_NAME),
103+
**(extra_attributes if extra_attributes is not None else {}),
104+
}
105+
106+
crew_config = {}
107+
for key, value in instance.__dict__.items():
108+
if instance.__class__.__name__ == "Crew":
109+
if key in crew_properties and value is not None:
110+
if crew_properties[key] == "json":
111+
crew_config[key] = json.dumps(value)
112+
elif crew_properties[key] == "object":
113+
crew_config[key] = str(value)
114+
else:
115+
crew_config[key] = value
116+
elif instance.__class__.__name__ == "Agent":
117+
if key in agent_properties and value is not None:
118+
if agent_properties[key] == "json":
119+
crew_config[key] = json.dumps(value)
120+
elif agent_properties[key] == "object":
121+
crew_config[key] = str(value)
122+
else:
123+
crew_config[key] = value
124+
elif instance.__class__.__name__ == "Task":
125+
if key in task_properties and value is not None:
126+
if task_properties[key] == "json":
127+
crew_config[key] = json.dumps(value)
128+
elif task_properties[key] == "object":
129+
crew_config[key] = str(value)
130+
else:
131+
crew_config[key] = value
132+
if crew_config:
133+
if instance.__class__.__name__ == "Crew":
134+
if "inputs" in kwargs and kwargs["inputs"]:
135+
crew_config["inputs"] = json.dumps(kwargs["inputs"])
136+
span_attributes["crewai.crew.config"] = json.dumps(crew_config)
137+
elif instance.__class__.__name__ == "Agent":
138+
if "context" in kwargs and kwargs["context"]:
139+
crew_config["context"] = json.dumps(kwargs["context"])
140+
span_attributes["crewai.agent.config"] = json.dumps(crew_config)
141+
elif instance.__class__.__name__ == "Task":
142+
span_attributes["crewai.task.config"] = json.dumps(crew_config)
143+
144+
attributes = FrameworkSpanAttributes(**span_attributes)
145+
146+
with tracer.start_as_current_span(operation_name, kind=SpanKind.CLIENT) as span:
147+
_set_input_attributes(span, kwargs, attributes)
148+
149+
try:
150+
result = wrapped(*args, **kwargs)
151+
if result:
152+
span.set_status(Status(StatusCode.OK))
153+
154+
span.end()
155+
return result
156+
157+
except Exception as err:
158+
# Record the exception in the span
159+
span.record_exception(err)
160+
161+
# Set the span status to indicate an error
162+
span.set_status(Status(StatusCode.ERROR, str(err)))
163+
164+
# Reraise the exception to ensure it's not swallowed
165+
raise
166+
167+
return traced_method
168+
169+
170+
@silently_fail
171+
def _set_input_attributes(span, kwargs, attributes):
172+
for field, value in attributes.model_dump(by_alias=True).items():
173+
set_span_attribute(span, field, value)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .instrumentation import DspyInstrumentor
1+
from .instrumentation import DspyInstrumentation
22

3-
__all__ = ["DspyInstrumentor"]
3+
__all__ = ["DspyInstrumentation"]

src/langtrace_python_sdk/instrumentation/dspy/instrumentation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .patch import patch_bootstrapfewshot_optimizer, patch_signature, patch_evaluate
2323

2424

25-
class DspyInstrumentor(BaseInstrumentor):
25+
class DspyInstrumentation(BaseInstrumentor):
2626
"""
2727
The DspyInstrumentor class represents the DSPy instrumentation"""
2828

0 commit comments

Comments
 (0)