langtrace-python-sdk 2.3.3__py3-none-any.whl → 2.3.4__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.
@@ -7,7 +7,7 @@ from langtrace_python_sdk import langtrace, with_langtrace_root_span
7
7
 
8
8
  _ = load_dotenv(find_dotenv())
9
9
 
10
- langtrace.init()
10
+ langtrace.init(write_spans_to_console=True)
11
11
 
12
12
 
13
13
  @with_langtrace_root_span("messages_create")
@@ -20,3 +20,4 @@ class OpenAIRunner:
20
20
  chat_completion_example()
21
21
  embeddings_create_example()
22
22
  function_example()
23
+ image_edit()
@@ -16,38 +16,39 @@ limitations under the License.
16
16
 
17
17
  import importlib.metadata
18
18
  import logging
19
- from typing import Collection
19
+ from typing import Collection, Any
20
20
 
21
21
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
22
+ from opentelemetry.trace import TracerProvider
22
23
  from opentelemetry.trace import get_tracer
23
24
  from wrapt import wrap_function_wrapper
24
-
25
+ from typing import Any
25
26
  from langtrace_python_sdk.instrumentation.anthropic.patch import messages_create
26
27
 
27
28
  logging.basicConfig(level=logging.FATAL)
28
29
 
29
30
 
30
- class AnthropicInstrumentation(BaseInstrumentor):
31
+ class AnthropicInstrumentation(BaseInstrumentor): # type: ignore[misc]
31
32
  """
32
- The AnthropicInstrumentation class represents the Anthropic instrumentation
33
+ The AnthropicInstrumentation class represents the Anthropic instrumentation.
33
34
  """
34
35
 
35
36
  def instrumentation_dependencies(self) -> Collection[str]:
36
37
  return ["anthropic >= 0.19.1"]
37
38
 
38
- def _instrument(self, **kwargs):
39
- tracer_provider = kwargs.get("tracer_provider")
39
+ def _instrument(self, **kwargs: dict[str, Any]) -> None:
40
+ tracer_provider: TracerProvider = kwargs.get("tracer_provider") # type: ignore
40
41
  tracer = get_tracer(__name__, "", tracer_provider)
41
42
  version = importlib.metadata.version("anthropic")
42
43
 
43
44
  wrap_function_wrapper(
44
45
  "anthropic.resources.messages",
45
46
  "Messages.create",
46
- messages_create("anthropic.messages.create", version, tracer),
47
+ messages_create(version, tracer),
47
48
  )
48
49
 
49
- def _instrument_module(self, module_name):
50
+ def _instrument_module(self, module_name: str) -> None:
50
51
  pass
51
52
 
52
- def _uninstrument(self, **kwargs):
53
+ def _uninstrument(self, **kwargs: dict[str, Any]) -> None:
53
54
  pass
@@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
- import json
18
-
19
- from langtrace.trace_attributes import Event, LLMSpanAttributes
17
+ from typing import Any, Callable, Dict, List, Optional, Iterator, TypedDict, Union
18
+ from langtrace.trace_attributes import Event, SpanAttributes, LLMSpanAttributes
20
19
  from langtrace_python_sdk.utils import set_span_attribute
21
20
  from langtrace_python_sdk.utils.silently_fail import silently_fail
22
21
 
@@ -27,41 +26,48 @@ from langtrace_python_sdk.utils.llm import (
27
26
  get_llm_request_attributes,
28
27
  get_llm_url,
29
28
  get_span_name,
30
- is_streaming,
31
29
  set_event_completion,
32
30
  set_usage_attributes,
31
+ set_span_attribute,
33
32
  )
34
- from opentelemetry.trace import SpanKind
35
- from opentelemetry.trace.status import Status, StatusCode
36
- from langtrace.trace_attributes import SpanAttributes
37
-
33
+ from opentelemetry.trace import Span, Tracer, SpanKind
34
+ from opentelemetry.trace.status import StatusCode
38
35
  from langtrace_python_sdk.constants.instrumentation.anthropic import APIS
39
- from langtrace_python_sdk.constants.instrumentation.common import (
40
- SERVICE_PROVIDERS,
36
+ from langtrace_python_sdk.constants.instrumentation.common import SERVICE_PROVIDERS
37
+ from langtrace_python_sdk.instrumentation.anthropic.types import (
38
+ StreamingResult,
39
+ ResultType,
40
+ MessagesCreateKwargs,
41
+ ContentItem,
42
+ Usage,
41
43
  )
42
44
 
43
45
 
44
- def messages_create(original_method, version, tracer):
46
+ def messages_create(version: str, tracer: Tracer) -> Callable[..., Any]:
45
47
  """Wrap the `messages_create` method."""
46
48
 
47
- def traced_method(wrapped, instance, args, kwargs):
49
+ def traced_method(
50
+ wrapped: Callable[..., Any],
51
+ instance: Any,
52
+ args: List[Any],
53
+ kwargs: MessagesCreateKwargs,
54
+ ) -> Any:
48
55
  service_provider = SERVICE_PROVIDERS["ANTHROPIC"]
49
56
 
50
- # extract system from kwargs and attach as a role to the prompts
51
- # we do this to keep it consistent with the openai
57
+ # Extract system from kwargs and attach as a role to the prompts
52
58
  prompts = kwargs.get("messages", [])
53
59
  system = kwargs.get("system")
54
60
  if system:
55
61
  prompts = [{"role": "system", "content": system}] + kwargs.get(
56
62
  "messages", []
57
63
  )
58
-
64
+ extraAttributes = get_extra_attributes()
59
65
  span_attributes = {
60
66
  **get_langtrace_attributes(version, service_provider),
61
67
  **get_llm_request_attributes(kwargs, prompts=prompts),
62
68
  **get_llm_url(instance),
63
69
  SpanAttributes.LLM_PATH: APIS["MESSAGES_CREATE"]["ENDPOINT"],
64
- **get_extra_attributes(),
70
+ **extraAttributes, # type: ignore
65
71
  }
66
72
 
67
73
  attributes = LLMSpanAttributes(**span_attributes)
@@ -74,37 +80,35 @@ def messages_create(original_method, version, tracer):
74
80
  try:
75
81
  # Attempt to call the original method
76
82
  result = wrapped(*args, **kwargs)
77
- return set_response_attributes(result, span, kwargs)
83
+ return set_response_attributes(result, span)
78
84
 
79
85
  except Exception as err:
80
86
  # Record the exception in the span
81
87
  span.record_exception(err)
82
88
  # Set the span status to indicate an error
83
- span.set_status(Status(StatusCode.ERROR, str(err)))
89
+ span.set_status(StatusCode.ERROR, str(err))
84
90
  # Reraise the exception to ensure it's not swallowed
85
91
  span.end()
86
92
  raise
87
93
 
88
- @silently_fail
89
- def set_response_attributes(result, span, kwargs):
90
- if not is_streaming(kwargs):
94
+ def set_response_attributes(
95
+ result: Union[ResultType, StreamingResult], span: Span
96
+ ) -> Any:
97
+ if not isinstance(result, Iterator):
91
98
  if hasattr(result, "content") and result.content is not None:
92
99
  set_span_attribute(
93
100
  span, SpanAttributes.LLM_RESPONSE_MODEL, result.model
94
101
  )
102
+ content_item = result.content[0]
95
103
  completion = [
96
104
  {
97
- "role": result.role if result.role else "assistant",
98
- "content": result.content[0].text,
99
- "type": result.content[0].type,
105
+ "role": result.role or "assistant",
106
+ "content": content_item.text,
107
+ "type": content_item.type,
100
108
  }
101
109
  ]
102
110
  set_event_completion(span, completion)
103
111
 
104
- else:
105
- responses = []
106
- set_event_completion(span, responses)
107
-
108
112
  if (
109
113
  hasattr(result, "system_fingerprint")
110
114
  and result.system_fingerprint is not None
@@ -116,7 +120,7 @@ def messages_create(original_method, version, tracer):
116
120
  # Get the usage
117
121
  if hasattr(result, "usage") and result.usage is not None:
118
122
  usage = result.usage
119
- set_usage_attributes(span, dict(usage))
123
+ set_usage_attributes(span, vars(usage))
120
124
 
121
125
  span.set_status(StatusCode.OK)
122
126
  span.end()
@@ -0,0 +1,105 @@
1
+ """
2
+ Copyright (c) 2024 Scale3 Labs
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ """
13
+
14
+ from typing import Dict, List, Optional, Iterator, TypedDict
15
+
16
+
17
+ class MessagesCreateKwargs(TypedDict, total=False):
18
+ system: Optional[str]
19
+ messages: List[Dict[str, str]]
20
+
21
+
22
+ class Usage:
23
+ input_tokens: int
24
+ output_tokens: int
25
+
26
+ def __init__(self, input_tokens: int, output_tokens: int):
27
+ self.input_tokens = input_tokens
28
+ self.output_tokens = output_tokens
29
+
30
+
31
+ class Message:
32
+ def __init__(
33
+ self,
34
+ id: str,
35
+ model: Optional[str],
36
+ usage: Optional[Usage],
37
+ ):
38
+ self.id = id
39
+ self.model = model
40
+ self.usage = usage
41
+
42
+ model: Optional[str]
43
+ usage: Optional[Usage]
44
+
45
+
46
+ class Delta:
47
+ text: Optional[str]
48
+
49
+ def __init__(
50
+ self,
51
+ text: Optional[str],
52
+ ):
53
+ self.text = text
54
+
55
+
56
+ class Chunk:
57
+ message: Message
58
+ delta: Delta
59
+
60
+ def __init__(
61
+ self,
62
+ message: Message,
63
+ delta: Delta,
64
+ ):
65
+ self.message = message
66
+ self.delta = delta
67
+
68
+
69
+ class ContentItem:
70
+ role: str
71
+ content: str
72
+ text: str
73
+ type: str
74
+
75
+ def __init__(self, role: str, content: str, text: str, type: str):
76
+ self.role = role
77
+ self.content = content
78
+ self.text = text
79
+ self.type = type
80
+
81
+
82
+ class ResultType:
83
+ model: Optional[str]
84
+ role: Optional[str]
85
+ content: List[ContentItem]
86
+ system_fingerprint: Optional[str]
87
+ usage: Optional[Usage]
88
+
89
+ def __init__(
90
+ self,
91
+ model: Optional[str],
92
+ role: Optional[str],
93
+ content: Optional[List[ContentItem]],
94
+ system_fingerprint: Optional[str],
95
+ usage: Optional[Usage],
96
+ ):
97
+ self.model = model
98
+ self.role = role
99
+ self.content = content if content is not None else []
100
+ self.system_fingerprint = system_fingerprint
101
+ self.usage = usage
102
+
103
+
104
+ # The result would be an iterator that yields these Chunk objects
105
+ StreamingResult = Iterator[Chunk]
@@ -367,10 +367,7 @@ def chat_stream(original_method, version, tracer):
367
367
  }
368
368
  for item in chat_history
369
369
  ]
370
- if len(history) > 0:
371
- prompts = history + prompts
372
- if len(system_prompts) > 0:
373
- prompts = system_prompts + prompts
370
+ prompts = system_prompts + history + prompts
374
371
 
375
372
  span_attributes = {
376
373
  **get_langtrace_attributes(version, service_provider),
@@ -8,7 +8,9 @@ from opentelemetry.trace.status import Status, StatusCode
8
8
 
9
9
  from langtrace_python_sdk.constants import LANGTRACE_SDK_NAME
10
10
  from langtrace_python_sdk.constants.instrumentation.common import (
11
- LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY, SERVICE_PROVIDERS)
11
+ LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY,
12
+ SERVICE_PROVIDERS,
13
+ )
12
14
  from langtrace_python_sdk.utils import set_span_attribute
13
15
  from langtrace_python_sdk.utils.llm import get_span_name, set_span_attributes
14
16
  from langtrace_python_sdk.utils.misc import serialize_args, serialize_kwargs
@@ -44,7 +46,9 @@ def patch_memory(operation_name, version, tracer: Tracer):
44
46
  set_span_attributes(span, attributes)
45
47
  result = wrapped(*args, **kwargs)
46
48
  if result is not None and len(result) > 0:
47
- set_span_attribute(span, "crewai.memory.storage.rag_storage.outputs", str(result))
49
+ set_span_attribute(
50
+ span, "crewai.memory.storage.rag_storage.outputs", str(result)
51
+ )
48
52
  if result:
49
53
  span.set_status(Status(StatusCode.OK))
50
54
  span.end()
@@ -87,20 +91,17 @@ def patch_crew(operation_name, version, tracer: Tracer):
87
91
  CrewAISpanAttributes(span=span, instance=instance)
88
92
  result = wrapped(*args, **kwargs)
89
93
  if result:
94
+ class_name = instance.__class__.__name__
95
+ span.set_attribute(
96
+ f"crewai.{class_name.lower()}.result", str(result)
97
+ )
90
98
  span.set_status(Status(StatusCode.OK))
91
- if instance.__class__.__name__ == "Crew":
92
- span.set_attribute("crewai.crew.result", str(result))
93
- if hasattr(result, "tasks_output") and result.tasks_output is not None:
94
- span.set_attribute("crewai.crew.tasks_output", str((result.tasks_output)))
95
- if hasattr(result, "token_usage") and result.token_usage is not None:
96
- span.set_attribute("crewai.crew.token_usage", str((result.token_usage)))
97
- if hasattr(result, "usage_metrics") and result.usage_metrics is not None:
98
- span.set_attribute("crewai.crew.usage_metrics", str((result.usage_metrics)))
99
- elif instance.__class__.__name__ == "Agent":
100
- span.set_attribute("crewai.agent.result", str(result))
101
- elif instance.__class__.__name__ == "Task":
102
- span.set_attribute("crewai.task.result", str(result))
103
-
99
+ if class_name == "Crew":
100
+ for attr in ["tasks_output", "token_usage", "usage_metrics"]:
101
+ if hasattr(result, attr):
102
+ span.set_attribute(
103
+ f"crewai.crew.{attr}", str(getattr(result, attr))
104
+ )
104
105
  span.end()
105
106
  return result
106
107
 
@@ -100,12 +100,9 @@ def apatch_gemini(name, version, tracer: Tracer):
100
100
 
101
101
 
102
102
  def get_llm_model(instance):
103
- llm_model = "unknown"
104
- if hasattr(instance, "_model_id"):
105
- llm_model = instance._model_id
106
103
  if hasattr(instance, "_model_name"):
107
- llm_model = instance._model_name.replace("models/", "")
108
- return llm_model
104
+ return instance._model_name.replace("models/", "")
105
+ return getattr(instance, "_model_id", "unknown")
109
106
 
110
107
 
111
108
  def serialize_prompts(args, kwargs, instance):
@@ -158,7 +158,7 @@ def chat_completions_create(original_method, version, tracer):
158
158
  usage = result.usage
159
159
  set_usage_attributes(span, dict(usage))
160
160
 
161
- span.set_status(StatusCode.OK)
161
+ span.set_status(Status(StatusCode.OK))
162
162
  span.end()
163
163
  return result
164
164
  else:
@@ -257,7 +257,7 @@ def chat_completions_create(original_method, version, tracer):
257
257
  span, [{"role": "assistant", "content": "".join(result_content)}]
258
258
  )
259
259
 
260
- span.set_status(StatusCode.OK)
260
+ span.set_status(Status(StatusCode.OK))
261
261
  span.end()
262
262
 
263
263
  # return the wrapped method
@@ -282,21 +282,13 @@ def async_chat_completions_create(original_method, version, tracer):
282
282
  tool_calls = []
283
283
  for tool_call in item.tool_calls:
284
284
  tool_call_dict = {
285
- "id": tool_call.id if hasattr(tool_call, "id") else "",
286
- "type": tool_call.type if hasattr(tool_call, "type") else "",
285
+ "id": getattr(tool_call, "id", ""),
286
+ "type": getattr(tool_call, "type", ""),
287
287
  }
288
288
  if hasattr(tool_call, "function"):
289
289
  tool_call_dict["function"] = {
290
- "name": (
291
- tool_call.function.name
292
- if hasattr(tool_call.function, "name")
293
- else ""
294
- ),
295
- "arguments": (
296
- tool_call.function.arguments
297
- if hasattr(tool_call.function, "arguments")
298
- else ""
299
- ),
290
+ "name": getattr(tool_call.function, "name", ""),
291
+ "arguments": getattr(tool_call.function, "arguments", ""),
300
292
  }
301
293
  tool_calls.append(tool_call_dict)
302
294
  llm_prompts.append(tool_calls)
@@ -459,11 +451,7 @@ def async_chat_completions_create(original_method, version, tracer):
459
451
  tool_call.function.arguments
460
452
  )
461
453
  completion_tokens += token_counts
462
- content = content + [
463
- tool_call.function.arguments
464
- ]
465
- else:
466
- content = content + []
454
+ content += [tool_call.function.arguments]
467
455
  else:
468
456
  content = []
469
457
  span.add_event(
@@ -1,12 +1,9 @@
1
1
  """
2
2
  Copyright (c) 2024 Scale3 Labs
3
-
4
3
  Licensed under the Apache License, Version 2.0 (the "License");
5
4
  you may not use this file except in compliance with the License.
6
5
  You may obtain a copy of the License at
7
-
8
6
  http://www.apache.org/licenses/LICENSE-2.0
9
-
10
7
  Unless required by applicable law or agreed to in writing, software
11
8
  distributed under the License is distributed on an "AS IS" BASIS,
12
9
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,12 +11,12 @@ See the License for the specific language governing permissions and
14
11
  limitations under the License.
15
12
  """
16
13
 
14
+ from typing import Collection, Optional, Any
17
15
  import importlib.metadata
18
16
  import logging
19
- from typing import Collection
20
17
 
21
18
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
22
- from opentelemetry.trace import get_tracer
19
+ from opentelemetry.trace import get_tracer, TracerProvider
23
20
  from wrapt import wrap_function_wrapper
24
21
 
25
22
  from langtrace_python_sdk.instrumentation.openai.patch import (
@@ -35,59 +32,57 @@ from langtrace_python_sdk.instrumentation.openai.patch import (
35
32
  logging.basicConfig(level=logging.FATAL)
36
33
 
37
34
 
38
- class OpenAIInstrumentation(BaseInstrumentor):
35
+ class OpenAIInstrumentation(BaseInstrumentor): # type: ignore
39
36
 
40
37
  def instrumentation_dependencies(self) -> Collection[str]:
41
38
  return ["openai >= 0.27.0", "trace-attributes >= 4.0.5"]
42
39
 
43
- def _instrument(self, **kwargs):
44
- tracer_provider = kwargs.get("tracer_provider")
40
+ def _instrument(self, **kwargs: Any) -> None:
41
+ tracer_provider: Optional[TracerProvider] = kwargs.get("tracer_provider")
45
42
  tracer = get_tracer(__name__, "", tracer_provider)
46
- version = importlib.metadata.version("openai")
43
+ version: str = importlib.metadata.version("openai")
47
44
 
48
45
  wrap_function_wrapper(
49
46
  "openai.resources.chat.completions",
50
47
  "Completions.create",
51
- chat_completions_create("openai.chat.completions.create", version, tracer),
48
+ chat_completions_create(version, tracer),
52
49
  )
53
50
 
54
51
  wrap_function_wrapper(
55
52
  "openai.resources.chat.completions",
56
53
  "AsyncCompletions.create",
57
- async_chat_completions_create(
58
- "openai.chat.completions.create_stream", version, tracer
59
- ),
54
+ async_chat_completions_create(version, tracer),
60
55
  )
61
56
 
62
57
  wrap_function_wrapper(
63
58
  "openai.resources.images",
64
59
  "Images.generate",
65
- images_generate("openai.images.generate", version, tracer),
60
+ images_generate(version, tracer),
66
61
  )
67
62
 
68
63
  wrap_function_wrapper(
69
64
  "openai.resources.images",
70
65
  "AsyncImages.generate",
71
- async_images_generate("openai.images.generate", version, tracer),
66
+ async_images_generate(version, tracer),
72
67
  )
73
68
 
74
69
  wrap_function_wrapper(
75
70
  "openai.resources.images",
76
71
  "Images.edit",
77
- images_edit("openai.images.edit", version, tracer),
72
+ images_edit(version, tracer),
78
73
  )
79
74
 
80
75
  wrap_function_wrapper(
81
76
  "openai.resources.embeddings",
82
77
  "Embeddings.create",
83
- embeddings_create("openai.embeddings.create", version, tracer),
78
+ embeddings_create(version, tracer),
84
79
  )
85
80
 
86
81
  wrap_function_wrapper(
87
82
  "openai.resources.embeddings",
88
83
  "AsyncEmbeddings.create",
89
- async_embeddings_create("openai.embeddings.create", version, tracer),
84
+ async_embeddings_create(version, tracer),
90
85
  )
91
86
 
92
- def _uninstrument(self, **kwargs):
87
+ def _uninstrument(self, **kwargs: Any) -> None:
93
88
  pass