langtrace-python-sdk 2.1.21__py3-none-any.whl → 2.1.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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()
@@ -11,6 +11,7 @@ SERVICE_PROVIDERS = {
11
11
  "ANTHROPIC": "Anthropic",
12
12
  "AZURE": "Azure",
13
13
  "CHROMA": "Chroma",
14
+ "CREWAI": "CrewAI",
14
15
  "DSPY": "DSPy",
15
16
  "GROQ": "Groq",
16
17
  "LANGCHAIN": "Langchain",
@@ -1,6 +1,7 @@
1
1
  from .anthropic import AnthropicInstrumentation
2
2
  from .chroma import ChromaInstrumentation
3
3
  from .cohere import CohereInstrumentation
4
+ from .crewai import CrewAIInstrumentation
4
5
  from .groq import GroqInstrumentation
5
6
  from .langchain import LangchainInstrumentation
6
7
  from .langchain_community import LangchainCommunityInstrumentation
@@ -12,12 +13,13 @@ from .pinecone import PineconeInstrumentation
12
13
  from .qdrant import QdrantInstrumentation
13
14
  from .weaviate import WeaviateInstrumentation
14
15
  from .ollama import OllamaInstrumentor
15
- from .dspy import DspyInstrumentor
16
+ from .dspy import DspyInstrumentation
16
17
 
17
18
  __all__ = [
18
19
  "AnthropicInstrumentation",
19
20
  "ChromaInstrumentation",
20
21
  "CohereInstrumentation",
22
+ "CrewAIInstrumentation",
21
23
  "GroqInstrumentation",
22
24
  "LangchainInstrumentation",
23
25
  "LangchainCommunityInstrumentation",
@@ -29,5 +31,5 @@ __all__ = [
29
31
  "QdrantInstrumentation",
30
32
  "WeaviateInstrumentation",
31
33
  "OllamaInstrumentor",
32
- "DspyInstrumentor",
34
+ "DspyInstrumentation",
33
35
  ]
@@ -0,0 +1,3 @@
1
+ from .instrumentation import CrewAIInstrumentation
2
+
3
+ __all__ = ["CrewAIInstrumentation"]
@@ -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
@@ -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)
@@ -1,3 +1,3 @@
1
- from .instrumentation import DspyInstrumentor
1
+ from .instrumentation import DspyInstrumentation
2
2
 
3
- __all__ = ["DspyInstrumentor"]
3
+ __all__ = ["DspyInstrumentation"]
@@ -22,7 +22,7 @@ from importlib_metadata import version as v
22
22
  from .patch import patch_bootstrapfewshot_optimizer, patch_signature, patch_evaluate
23
23
 
24
24
 
25
- class DspyInstrumentor(BaseInstrumentor):
25
+ class DspyInstrumentation(BaseInstrumentor):
26
26
  """
27
27
  The DspyInstrumentor class represents the DSPy instrumentation"""
28
28
 
@@ -37,24 +37,24 @@ def patch_bootstrapfewshot_optimizer(operation_name, version, tracer):
37
37
  "signature": str(args[0].prog.signature) if args[0].prog.signature else None,
38
38
  }
39
39
  span_attributes["dspy.optimizer.module.prog"] = json.dumps(prog)
40
- if instance.metric:
40
+ if "metric" in instance and instance.metric:
41
41
  span_attributes["dspy.optimizer.metric"] = instance.metric.__name__
42
42
  if kwargs.get("trainset") and len(kwargs.get("trainset")) > 0:
43
43
  span_attributes["dspy.optimizer.trainset"] = str(kwargs.get("trainset"))
44
44
  config = {}
45
- if instance.metric_threshold:
45
+ if "metric_threshold" in instance and instance.metric_threshold:
46
46
  config["metric_threshold"] = instance.metric_threshold
47
- if instance.teacher_settings:
47
+ if "teacher_settings" in instance and instance.teacher_settings:
48
48
  config["teacher_settings"] = instance.teacher_settings
49
- if instance.max_bootstrapped_demos:
49
+ if "max_bootstrapped_demos" in instance and instance.max_bootstrapped_demos:
50
50
  config["max_bootstrapped_demos"] = instance.max_bootstrapped_demos
51
- if instance.max_labeled_demos:
51
+ if "max_labeled_demos" in instance and instance.max_labeled_demos:
52
52
  config["max_labeled_demos"] = instance.max_labeled_demos
53
- if instance.max_rounds:
53
+ if "max_rounds" in instance and instance.max_rounds:
54
54
  config["max_rounds"] = instance.max_rounds
55
- if instance.max_errors:
55
+ if "max_errors" in instance and instance.max_errors:
56
56
  config["max_errors"] = instance.max_errors
57
- if instance.error_count:
57
+ if "error_count" in instance and instance.error_count:
58
58
  config["error_count"] = instance.error_count
59
59
  if config and len(config) > 0:
60
60
  span_attributes["dspy.optimizer.config"] = json.dumps(config)
@@ -149,25 +149,25 @@ def patch_evaluate(operation_name, version, tracer):
149
149
  **(extra_attributes if extra_attributes is not None else {}),
150
150
  }
151
151
 
152
- if instance.devset is not None:
152
+ if "devset" in instance and instance.devset is not None:
153
153
  span_attributes["dspy.evaluate.devset"] = str(instance.devset)
154
- if instance.display is not None:
154
+ if "display" in instance and instance.display is not None:
155
155
  span_attributes["dspy.evaluate.display"] = str(instance.display)
156
- if instance.num_threads is not None:
156
+ if "num_threads" in instance and instance.num_threads is not None:
157
157
  span_attributes["dspy.evaluate.num_threads"] = str(instance.num_threads)
158
- if instance.return_outputs is not None:
158
+ if "return_outputs" in instance and instance.return_outputs is not None:
159
159
  span_attributes["dspy.evaluate.return_outputs"] = str(instance.return_outputs)
160
- if instance.display_table is not None:
160
+ if "display_table" in instance and instance.display_table is not None:
161
161
  span_attributes["dspy.evaluate.display_table"] = str(instance.display_table)
162
- if instance.display_progress is not None:
162
+ if "display_progress" in instance and instance.display_progress is not None:
163
163
  span_attributes["dspy.evaluate.display_progress"] = str(instance.display_progress)
164
- if instance.metric is not None:
164
+ if "metric" in instance and instance.metric is not None:
165
165
  span_attributes["dspy.evaluate.metric"] = instance.metric.__name__
166
- if instance.error_count is not None:
166
+ if "error_count" in instance and instance.error_count is not None:
167
167
  span_attributes["dspy.evaluate.error_count"] = str(instance.error_count)
168
- if instance.error_lock is not None:
168
+ if "error_lock" in instance and instance.error_lock is not None:
169
169
  span_attributes["dspy.evaluate.error_lock"] = str(instance.error_lock)
170
- if instance.max_errors is not None:
170
+ if "max_errors" in instance and instance.max_errors is not None:
171
171
  span_attributes["dspy.evaluate.max_errors"] = str(instance.max_errors)
172
172
  if args and len(args) > 0:
173
173
  span_attributes["dspy.evaluate.args"] = str(args)
@@ -275,6 +275,110 @@ def images_edit(original_method, version, tracer):
275
275
  return traced_method
276
276
 
277
277
 
278
+ class StreamWrapper:
279
+ def __init__(self, stream, span, prompt_tokens, function_call=False, tool_calls=False):
280
+ self.stream = stream
281
+ self.span = span
282
+ self.prompt_tokens = prompt_tokens
283
+ self.function_call = function_call
284
+ self.tool_calls = tool_calls
285
+ self.result_content = []
286
+ self.completion_tokens = 0
287
+
288
+ def __enter__(self):
289
+ self.span.add_event(Event.STREAM_START.value)
290
+ return self
291
+
292
+ def __exit__(self, exc_type, exc_val, exc_tb):
293
+ self.span.add_event(Event.STREAM_END.value)
294
+ self.span.set_attribute(
295
+ "llm.token.counts",
296
+ json.dumps(
297
+ {
298
+ "input_tokens": self.prompt_tokens,
299
+ "output_tokens": self.completion_tokens,
300
+ "total_tokens": self.prompt_tokens + self.completion_tokens,
301
+ }
302
+ ),
303
+ )
304
+ self.span.set_attribute(
305
+ "llm.responses",
306
+ json.dumps(
307
+ [
308
+ {
309
+ "role": "assistant",
310
+ "content": "".join(self.result_content),
311
+ }
312
+ ]
313
+ ),
314
+ )
315
+ self.span.set_status(StatusCode.OK)
316
+ self.span.end()
317
+
318
+ def __iter__(self):
319
+ return self
320
+
321
+ def __next__(self):
322
+ try:
323
+ chunk = next(self.stream)
324
+ self.process_chunk(chunk)
325
+ return chunk
326
+ except StopIteration:
327
+ raise
328
+
329
+ def process_chunk(self, chunk):
330
+ if hasattr(chunk, "model") and chunk.model is not None:
331
+ self.span.set_attribute("llm.model", chunk.model)
332
+ if hasattr(chunk, "choices") and chunk.choices is not None:
333
+ content = []
334
+ if not self.function_call and not self.tool_calls:
335
+ for choice in chunk.choices:
336
+ if choice.delta and choice.delta.content is not None:
337
+ token_counts = estimate_tokens(choice.delta.content)
338
+ self.completion_tokens += token_counts
339
+ content = [choice.delta.content]
340
+ elif self.function_call:
341
+ for choice in chunk.choices:
342
+ if (
343
+ choice.delta
344
+ and choice.delta.function_call is not None
345
+ and choice.delta.function_call.arguments is not None
346
+ ):
347
+ token_counts = estimate_tokens(
348
+ choice.delta.function_call.arguments
349
+ )
350
+ self.completion_tokens += token_counts
351
+ content = [choice.delta.function_call.arguments]
352
+ elif self.tool_calls:
353
+ for choice in chunk.choices:
354
+ if choice.delta and choice.delta.tool_calls is not None:
355
+ toolcalls = choice.delta.tool_calls
356
+ content = []
357
+ for tool_call in toolcalls:
358
+ if (
359
+ tool_call
360
+ and tool_call.function is not None
361
+ and tool_call.function.arguments is not None
362
+ ):
363
+ token_counts = estimate_tokens(
364
+ tool_call.function.arguments
365
+ )
366
+ self.completion_tokens += token_counts
367
+ content.append(tool_call.function.arguments)
368
+ self.span.add_event(
369
+ Event.STREAM_OUTPUT.value,
370
+ {
371
+ "response": (
372
+ "".join(content)
373
+ if len(content) > 0 and content[0] is not None
374
+ else ""
375
+ )
376
+ },
377
+ )
378
+ if content:
379
+ self.result_content.append(content[0])
380
+
381
+
278
382
  def chat_completions_create(original_method, version, tracer):
279
383
  """Wrap the `create` method of the `ChatCompletion` class to trace it."""
280
384
 
@@ -427,7 +531,7 @@ def chat_completions_create(original_method, version, tracer):
427
531
  json.dumps(function), kwargs.get("model")
428
532
  )
429
533
 
430
- return handle_streaming_response(
534
+ return StreamWrapper(
431
535
  result,
432
536
  span,
433
537
  prompt_tokens,
@@ -441,98 +545,6 @@ def chat_completions_create(original_method, version, tracer):
441
545
  span.end()
442
546
  raise
443
547
 
444
- def handle_streaming_response(
445
- result, span, prompt_tokens, function_call=False, tool_calls=False
446
- ):
447
- """Process and yield streaming response chunks."""
448
- result_content = []
449
- span.add_event(Event.STREAM_START.value)
450
- completion_tokens = 0
451
- try:
452
- for chunk in result:
453
- if hasattr(chunk, "model") and chunk.model is not None:
454
- span.set_attribute("llm.model", chunk.model)
455
- if hasattr(chunk, "choices") and chunk.choices is not None:
456
- if not function_call and not tool_calls:
457
- for choice in chunk.choices:
458
- if choice.delta and choice.delta.content is not None:
459
- token_counts = estimate_tokens(choice.delta.content)
460
- completion_tokens += token_counts
461
- content = [choice.delta.content]
462
- elif function_call:
463
- for choice in chunk.choices:
464
- if (
465
- choice.delta
466
- and choice.delta.function_call is not None
467
- and choice.delta.function_call.arguments is not None
468
- ):
469
- token_counts = estimate_tokens(
470
- choice.delta.function_call.arguments
471
- )
472
- completion_tokens += token_counts
473
- content = [choice.delta.function_call.arguments]
474
- elif tool_calls:
475
- for choice in chunk.choices:
476
- tool_call = ""
477
- if choice.delta and choice.delta.tool_calls is not None:
478
- toolcalls = choice.delta.tool_calls
479
- content = []
480
- for tool_call in toolcalls:
481
- if (
482
- tool_call
483
- and tool_call.function is not None
484
- and tool_call.function.arguments is not None
485
- ):
486
- token_counts = estimate_tokens(
487
- tool_call.function.arguments
488
- )
489
- completion_tokens += token_counts
490
- content = content + [
491
- tool_call.function.arguments
492
- ]
493
- else:
494
- content = content + []
495
- else:
496
- content = []
497
- span.add_event(
498
- Event.STREAM_OUTPUT.value,
499
- {
500
- "response": (
501
- "".join(content)
502
- if len(content) > 0 and content[0] is not None
503
- else ""
504
- )
505
- },
506
- )
507
- result_content.append(content[0] if len(content) > 0 else "")
508
- yield chunk
509
- finally:
510
- # Finalize span after processing all chunks
511
- span.add_event(Event.STREAM_END.value)
512
- span.set_attribute(
513
- "llm.token.counts",
514
- json.dumps(
515
- {
516
- "input_tokens": prompt_tokens,
517
- "output_tokens": completion_tokens,
518
- "total_tokens": prompt_tokens + completion_tokens,
519
- }
520
- ),
521
- )
522
- span.set_attribute(
523
- "llm.responses",
524
- json.dumps(
525
- [
526
- {
527
- "role": "assistant",
528
- "content": "".join(result_content),
529
- }
530
- ]
531
- ),
532
- )
533
- span.set_status(StatusCode.OK)
534
- span.end()
535
-
536
548
  # return the wrapped method
537
549
  return traced_method
538
550
 
@@ -687,7 +699,7 @@ def async_chat_completions_create(original_method, version, tracer):
687
699
  json.dumps(function), kwargs.get("model")
688
700
  )
689
701
 
690
- return ahandle_streaming_response(
702
+ return StreamWrapper(
691
703
  result,
692
704
  span,
693
705
  prompt_tokens,
@@ -701,99 +713,6 @@ def async_chat_completions_create(original_method, version, tracer):
701
713
  span.end()
702
714
  raise
703
715
 
704
- async def ahandle_streaming_response(
705
- result, span, prompt_tokens, function_call=False, tool_calls=False
706
- ):
707
- """Process and yield streaming response chunks."""
708
- result_content = []
709
- span.add_event(Event.STREAM_START.value)
710
- completion_tokens = 0
711
- try:
712
- content = []
713
- async for chunk in result:
714
- if hasattr(chunk, "model") and chunk.model is not None:
715
- span.set_attribute("llm.model", chunk.model)
716
- if hasattr(chunk, "choices") and chunk.choices is not None:
717
- if not function_call and not tool_calls:
718
- for choice in chunk.choices:
719
- if choice.delta and choice.delta.content is not None:
720
- token_counts = estimate_tokens(choice.delta.content)
721
- completion_tokens += token_counts
722
- content = [choice.delta.content]
723
- elif function_call:
724
- for choice in chunk.choices:
725
- if (
726
- choice.delta
727
- and choice.delta.function_call
728
- and choice.delta.function_call.arguments is not None
729
- ):
730
- token_counts = estimate_tokens(
731
- choice.delta.function_call.arguments
732
- )
733
- completion_tokens += token_counts
734
- content = [choice.delta.function_call.arguments]
735
- elif tool_calls:
736
- for choice in chunk.choices:
737
- tool_call = ""
738
- if choice.delta and choice.delta.tool_calls is not None:
739
- toolcalls = choice.delta.tool_calls
740
- content = []
741
- for tool_call in toolcalls:
742
- if (
743
- tool_call
744
- and tool_call.function is not None
745
- and tool_call.function.arguments is not None
746
- ):
747
- token_counts = estimate_tokens(
748
- tool_call.function.arguments
749
- )
750
- completion_tokens += token_counts
751
- content = content + [
752
- tool_call.function.arguments
753
- ]
754
- else:
755
- content = content + []
756
- else:
757
- content = []
758
- span.add_event(
759
- Event.STREAM_OUTPUT.value,
760
- {
761
- "response": (
762
- "".join(content)
763
- if len(content) > 0 and content[0] is not None
764
- else ""
765
- )
766
- },
767
- )
768
- result_content.append(content[0] if len(content) > 0 else "")
769
- yield chunk
770
- finally:
771
- # Finalize span after processing all chunks
772
- span.add_event(Event.STREAM_END.value)
773
- span.set_attribute(
774
- "llm.token.counts",
775
- json.dumps(
776
- {
777
- "input_tokens": prompt_tokens,
778
- "output_tokens": completion_tokens,
779
- "total_tokens": prompt_tokens + completion_tokens,
780
- }
781
- ),
782
- )
783
- span.set_attribute(
784
- "llm.responses",
785
- json.dumps(
786
- [
787
- {
788
- "role": "assistant",
789
- "content": "".join(result_content),
790
- }
791
- ]
792
- ),
793
- )
794
- span.set_status(StatusCode.OK)
795
- span.end()
796
-
797
716
  # return the wrapped method
798
717
  return traced_method
799
718
 
@@ -40,6 +40,7 @@ from langtrace_python_sdk.instrumentation import (
40
40
  AnthropicInstrumentation,
41
41
  ChromaInstrumentation,
42
42
  CohereInstrumentation,
43
+ CrewAIInstrumentation,
43
44
  GroqInstrumentation,
44
45
  LangchainInstrumentation,
45
46
  LangchainCommunityInstrumentation,
@@ -51,7 +52,7 @@ from langtrace_python_sdk.instrumentation import (
51
52
  QdrantInstrumentation,
52
53
  WeaviateInstrumentation,
53
54
  OllamaInstrumentor,
54
- DspyInstrumentor,
55
+ DspyInstrumentation,
55
56
  )
56
57
  from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
57
58
  from colorama import Fore
@@ -109,7 +110,8 @@ def init(
109
110
  "weaviate": WeaviateInstrumentation(),
110
111
  "sqlalchemy": SQLAlchemyInstrumentor(),
111
112
  "ollama": OllamaInstrumentor(),
112
- "dspy": DspyInstrumentor(),
113
+ "dspy": DspyInstrumentation(),
114
+ "crewai": CrewAIInstrumentation(),
113
115
  }
114
116
 
115
117
  init_instrumentations(disable_instrumentations, all_instrumentations)
@@ -1 +1 @@
1
- __version__ = "2.1.21"
1
+ __version__ = "2.1.22"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langtrace-python-sdk
3
- Version: 2.1.21
3
+ Version: 2.1.22
4
4
  Summary: Python SDK for LangTrace
5
5
  Project-URL: Homepage, https://github.com/Scale3-Labs/langtrace-python-sdk
6
6
  Author-email: Scale3 Labs <engineering@scale3labs.com>
@@ -9,6 +9,7 @@ examples/cohere_example/chat_stream.py,sha256=daWGrj2yucN9sWKGiPzTw18clh23Grv-MI
9
9
  examples/cohere_example/embed.py,sha256=p9BJvOg09JVb8BfTCb63v3uh_wOsi_OyrCAJdXXrE6E,496
10
10
  examples/cohere_example/rerank.py,sha256=e7OU0A2FzfiQDuOmCy3Kg5LLNYGRmRIK5LqeLnTWlP4,1118
11
11
  examples/cohere_example/tools.py,sha256=a5uvS058tcwU6PJbF9EDO6LPVmPj2LoW4Vn8Web3Iq8,1656
12
+ examples/crewai_example/basic.py,sha256=PBu4f8yQfZO1L_22UDm_ReU9lnEcycjZcGuy5UpgDJM,1948
12
13
  examples/dspy_example/math_problems_cot.py,sha256=Z98nB6myt8WJse2dWS6Ap7CFUhC27lBNb37R1Gg80VQ,1282
13
14
  examples/dspy_example/program_of_thought_basic.py,sha256=oEbtJdeKENMUbex25-zyStWwurRWW6OdP0KDs-jUkko,984
14
15
  examples/dspy_example/quiz_gen.py,sha256=OyGhepeX8meKOtLdmlYUjMD2ECk-ZQuQXUZif1hFQY4,3371
@@ -49,15 +50,15 @@ examples/qdrant_example/basic.py,sha256=DCMjHSuBZKkhEjCkwy5d5La9WMyW0lCWqtcZWiFC
49
50
  examples/weaviate_example/__init__.py,sha256=8JMDBsRSEV10HfTd-YC7xb4txBjD3la56snk-Bbg2Kw,618
50
51
  examples/weaviate_example/query_text.py,sha256=qz9o-fTDzX5AW5m8BJF-TfmBdokxh492NfnmnPUMU3s,64814
51
52
  langtrace_python_sdk/__init__.py,sha256=VZM6i71NR7pBQK6XvJWRelknuTYUhqwqE7PlicKa5Wg,1166
52
- langtrace_python_sdk/langtrace.py,sha256=enOnfpDPoFibbUzUEY2vV8S3No0dySdXxo3E76rZ8mA,7410
53
- langtrace_python_sdk/version.py,sha256=msdDHhj5VY39vNBuQYgH15DYZ-b9Z3koedzF3TN1kW8,23
53
+ langtrace_python_sdk/langtrace.py,sha256=pG_dWzzQxUP8r5SAMUwRScb6IopINcda1CZvJERjXBo,7486
54
+ langtrace_python_sdk/version.py,sha256=8X_w-VRCyU1lQMWejIqkf5tmhnrgO7csLi27xV88LJs,23
54
55
  langtrace_python_sdk/constants/__init__.py,sha256=P8QvYwt5czUNDZsKS64vxm9Dc41ptGbuF1TFtAF6nv4,44
55
56
  langtrace_python_sdk/constants/exporter/langtrace_exporter.py,sha256=5MNjnAOg-4am78J3gVMH6FSwq5N8TOj72ugkhsw4vi0,46
56
57
  langtrace_python_sdk/constants/instrumentation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
58
  langtrace_python_sdk/constants/instrumentation/anthropic.py,sha256=YX3llt3zwDY6XrYk3CB8WEVqgrzRXEw_ffyk56JoF3k,126
58
59
  langtrace_python_sdk/constants/instrumentation/chroma.py,sha256=hiPGYdHS0Yj4Kh3eaYBbuCAl_swqIygu80yFqkOgdak,955
59
60
  langtrace_python_sdk/constants/instrumentation/cohere.py,sha256=tf9sDfb5K3qOAHChEE5o8eYWPZ1io58VsOjZDCZPxfw,577
60
- langtrace_python_sdk/constants/instrumentation/common.py,sha256=kqs3ST0RTcXpQKPSQ6R6hoBabQsLGPNB_rvHlQXsn80,826
61
+ langtrace_python_sdk/constants/instrumentation/common.py,sha256=KodH_uGGjWGGP8rqTi7Ua-osjUwtPKslx69DJRbDiT4,850
61
62
  langtrace_python_sdk/constants/instrumentation/groq.py,sha256=VFXmIl4aqGY_fS0PAmjPj_Qm7Tibxbx7Ur_e7rQpqXc,134
62
63
  langtrace_python_sdk/constants/instrumentation/ollama.py,sha256=zFfSUKX5v1c612doKSxsmIwQeeQxSPkFp_ZzjWQSPNE,142
63
64
  langtrace_python_sdk/constants/instrumentation/openai.py,sha256=uEOH5UXapU2DSf2AdgXTRhhJEHGWXUNFkUGD5QafflM,1164
@@ -67,7 +68,7 @@ langtrace_python_sdk/constants/instrumentation/weaviate.py,sha256=Iytf2OpB_irZYE
67
68
  langtrace_python_sdk/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
69
  langtrace_python_sdk/extensions/langtrace_exporter.py,sha256=gWVRU2DlB4xjZ4ww7M63DaLiAN5zQ2k1HPrythmjEdo,4202
69
70
  langtrace_python_sdk/extensions/langtrace_filesystem.py,sha256=qpnkpkuTZ2yhGgpBK64QJLt0T1iL-1zpEMPz4quJ_ng,6925
70
- langtrace_python_sdk/instrumentation/__init__.py,sha256=y5ftlcrAjSHvoQ_v8ffDlsP5la3SG-B4PKpPZIeoTds,1166
71
+ langtrace_python_sdk/instrumentation/__init__.py,sha256=BYTzQSmHDbKAbtV98UDjxzV1UoNq_wtsWV7V4PZPzbc,1243
71
72
  langtrace_python_sdk/instrumentation/anthropic/__init__.py,sha256=donrurJAGYlxrSRA3BIf76jGeUcAx9Tq8CVpah68S0Y,101
72
73
  langtrace_python_sdk/instrumentation/anthropic/instrumentation.py,sha256=-srgE8qumAn0ulQYZxMa8ch-9IBH0XgBW_rfEnGk6LI,1684
73
74
  langtrace_python_sdk/instrumentation/anthropic/patch.py,sha256=52GRkW7fhF2VB9JvYGMaPwOvWH3Q5rsIEaVlnFm_Zz0,8548
@@ -77,9 +78,12 @@ langtrace_python_sdk/instrumentation/chroma/patch.py,sha256=HhIkXGbVfzpEB4edxzlG
77
78
  langtrace_python_sdk/instrumentation/cohere/__init__.py,sha256=sGUSLdTUyYf36Tm6L5jQflhzCqvmWrhnBOMYHjvp6Hs,95
78
79
  langtrace_python_sdk/instrumentation/cohere/instrumentation.py,sha256=YQFHZIBd7SSPD4b6Va-ZR0thf_AuBCqj5yzHLHJVWnM,2121
79
80
  langtrace_python_sdk/instrumentation/cohere/patch.py,sha256=uGjZm_c1-xpZilHD8ljGr4AqQB3UNU04SKl9s2alGzs,26726
80
- langtrace_python_sdk/instrumentation/dspy/__init__.py,sha256=qTdAqLOuLX7dsiD_pdqe23pz9Ehit4aC5771WJe2Q8I,78
81
- langtrace_python_sdk/instrumentation/dspy/instrumentation.py,sha256=fvnTpDoMlCzpUyhlZefUgUO1yd50dH27UPDQ6EBcrJA,2920
82
- langtrace_python_sdk/instrumentation/dspy/patch.py,sha256=8VrY3Et0zQRagYkSU6jIv6tvY6PpvVTDRRcWtgz3QEw,8662
81
+ langtrace_python_sdk/instrumentation/crewai/__init__.py,sha256=_UBKfvQv7l0g2_wnmA5F6CdSAFH0atNOVPd49zsN3aM,88
82
+ langtrace_python_sdk/instrumentation/crewai/instrumentation.py,sha256=W8PLTLzgEdrEx1DCo79wNs9xcuWK0YuxEICLavOESDw,1715
83
+ langtrace_python_sdk/instrumentation/crewai/patch.py,sha256=Vnpip9Pbk4UFbTFHoUrHtAnDgsaihwSvZBgtUeOtLr8,6109
84
+ langtrace_python_sdk/instrumentation/dspy/__init__.py,sha256=tM1srfi_QgyCzrde4izojMrRq2Wm7Dj5QUvVQXIJzkk,84
85
+ langtrace_python_sdk/instrumentation/dspy/instrumentation.py,sha256=Y-_qcH5jCT4TbGvci6Iw0_OWUUlCmovMxnysj6NCnXI,2923
86
+ langtrace_python_sdk/instrumentation/dspy/patch.py,sha256=0zBlrzvGmQEhJpb44L8fkTIhMH5i5pWOkYoSH9e8D6U,9216
83
87
  langtrace_python_sdk/instrumentation/groq/__init__.py,sha256=ZXeq_nrej6Lm_uoMFEg8wbSejhjB2UJ5IoHQBPc2-C0,91
84
88
  langtrace_python_sdk/instrumentation/groq/instrumentation.py,sha256=Ttf07XVKhdYY1_fqJc7QWiSdmgEhEVyQB_3Az2_wqYo,1832
85
89
  langtrace_python_sdk/instrumentation/groq/patch.py,sha256=EKH9tjoDbwWavIAwUadaR5tFiy-S69h1IxxzH_1SSRg,26354
@@ -103,7 +107,7 @@ langtrace_python_sdk/instrumentation/ollama/instrumentation.py,sha256=jdsvkqUJAA
103
107
  langtrace_python_sdk/instrumentation/ollama/patch.py,sha256=CGLgt0qZg3WDr6XLn32qqs4D9USdMmeTmJxctbAsDeM,7908
104
108
  langtrace_python_sdk/instrumentation/openai/__init__.py,sha256=VPHRNCQEdkizIVP2d0Uw_a7t8XOTSTprEIB8oboJFbs,95
105
109
  langtrace_python_sdk/instrumentation/openai/instrumentation.py,sha256=A0BJHRLcZ74TNVg6I0I9M5YWvSpAtXwMmME6N5CEQ_M,2945
106
- langtrace_python_sdk/instrumentation/openai/patch.py,sha256=uUoeeZMAlH8PTZyIObjAI8kU2H5iHuZbPF09Gk70IKo,41103
110
+ langtrace_python_sdk/instrumentation/openai/patch.py,sha256=-eKopJp6m56mg4553wrDr0ibGoCPpYH2iyNMh1lX9yc,36526
107
111
  langtrace_python_sdk/instrumentation/pinecone/__init__.py,sha256=DzXyGh9_MGWveJvXULkFwdkf7PbG2s3bAWtT1Dmz7Ok,99
108
112
  langtrace_python_sdk/instrumentation/pinecone/instrumentation.py,sha256=HDXkRITrVPwdQEoOYJOfMzZE_2-vDDvuqHTlD8W1lQw,1845
109
113
  langtrace_python_sdk/instrumentation/pinecone/patch.py,sha256=KiIRRz8kk47FllFT746Cb_w6F6M60AN_pcsguD979E4,5172
@@ -159,8 +163,8 @@ tests/pinecone/cassettes/test_query.yaml,sha256=b5v9G3ssUy00oG63PlFUR3JErF2Js-5A
159
163
  tests/pinecone/cassettes/test_upsert.yaml,sha256=neWmQ1v3d03V8WoLl8FoFeeCYImb8pxlJBWnFd_lITU,38607
160
164
  tests/qdrant/conftest.py,sha256=9n0uHxxIjWk9fbYc4bx-uP8lSAgLBVx-cV9UjnsyCHM,381
161
165
  tests/qdrant/test_qdrant.py,sha256=pzjAjVY2kmsmGfrI2Gs2xrolfuaNHz7l1fqGQCjp5_o,3353
162
- langtrace_python_sdk-2.1.21.dist-info/METADATA,sha256=y6OnKJCGYcnpezk9jVMO1Y-EBummvLJ92jRUOa1M4hE,13161
163
- langtrace_python_sdk-2.1.21.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
164
- langtrace_python_sdk-2.1.21.dist-info/entry_points.txt,sha256=1_b9-qvf2fE7uQNZcbUei9vLpFZBbbh9LrtGw95ssAo,70
165
- langtrace_python_sdk-2.1.21.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
166
- langtrace_python_sdk-2.1.21.dist-info/RECORD,,
166
+ langtrace_python_sdk-2.1.22.dist-info/METADATA,sha256=OBp1W_aK6Om6TkkaFfhhyo-7tSDZ3TC49Hdbs6KdFR0,13161
167
+ langtrace_python_sdk-2.1.22.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
168
+ langtrace_python_sdk-2.1.22.dist-info/entry_points.txt,sha256=1_b9-qvf2fE7uQNZcbUei9vLpFZBbbh9LrtGw95ssAo,70
169
+ langtrace_python_sdk-2.1.22.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
170
+ langtrace_python_sdk-2.1.22.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.24.2
2
+ Generator: hatchling 1.25.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any