langtrace-python-sdk 2.1.29__py3-none-any.whl → 2.2.2__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.
Files changed (58) hide show
  1. examples/cohere_example/chat.py +1 -0
  2. examples/cohere_example/chat_stream.py +3 -0
  3. examples/dspy_example/math_problems_cot_parallel.py +59 -0
  4. examples/gemini_example/__init__.py +6 -0
  5. examples/gemini_example/function_tools.py +62 -0
  6. examples/gemini_example/main.py +91 -0
  7. examples/langchain_example/__init__.py +8 -0
  8. examples/langchain_example/groq_example.py +28 -15
  9. examples/ollama_example/basic.py +1 -0
  10. examples/openai_example/__init__.py +1 -0
  11. examples/openai_example/async_tool_calling_nonstreaming.py +1 -1
  12. examples/openai_example/chat_completion.py +1 -1
  13. examples/openai_example/embeddings_create.py +1 -0
  14. examples/openai_example/images_edit.py +2 -2
  15. examples/vertexai_example/__init__.py +6 -0
  16. examples/vertexai_example/main.py +214 -0
  17. langtrace_python_sdk/constants/instrumentation/common.py +2 -0
  18. langtrace_python_sdk/constants/instrumentation/gemini.py +12 -0
  19. langtrace_python_sdk/constants/instrumentation/vertexai.py +42 -0
  20. langtrace_python_sdk/instrumentation/__init__.py +4 -0
  21. langtrace_python_sdk/instrumentation/anthropic/patch.py +68 -96
  22. langtrace_python_sdk/instrumentation/chroma/patch.py +29 -29
  23. langtrace_python_sdk/instrumentation/cohere/patch.py +143 -242
  24. langtrace_python_sdk/instrumentation/dspy/instrumentation.py +2 -2
  25. langtrace_python_sdk/instrumentation/dspy/patch.py +36 -36
  26. langtrace_python_sdk/instrumentation/gemini/__init__.py +3 -0
  27. langtrace_python_sdk/instrumentation/gemini/instrumentation.py +36 -0
  28. langtrace_python_sdk/instrumentation/gemini/patch.py +186 -0
  29. langtrace_python_sdk/instrumentation/groq/patch.py +82 -125
  30. langtrace_python_sdk/instrumentation/ollama/patch.py +62 -65
  31. langtrace_python_sdk/instrumentation/openai/patch.py +190 -494
  32. langtrace_python_sdk/instrumentation/qdrant/patch.py +6 -6
  33. langtrace_python_sdk/instrumentation/vertexai/__init__.py +3 -0
  34. langtrace_python_sdk/instrumentation/vertexai/instrumentation.py +33 -0
  35. langtrace_python_sdk/instrumentation/vertexai/patch.py +131 -0
  36. langtrace_python_sdk/langtrace.py +5 -0
  37. langtrace_python_sdk/utils/__init__.py +14 -3
  38. langtrace_python_sdk/utils/llm.py +311 -6
  39. langtrace_python_sdk/version.py +1 -1
  40. {langtrace_python_sdk-2.1.29.dist-info → langtrace_python_sdk-2.2.2.dist-info}/METADATA +26 -19
  41. {langtrace_python_sdk-2.1.29.dist-info → langtrace_python_sdk-2.2.2.dist-info}/RECORD +58 -38
  42. tests/anthropic/test_anthropic.py +28 -27
  43. tests/cohere/test_cohere_chat.py +36 -36
  44. tests/cohere/test_cohere_embed.py +12 -9
  45. tests/cohere/test_cohere_rerank.py +18 -11
  46. tests/groq/cassettes/test_async_chat_completion.yaml +113 -0
  47. tests/groq/cassettes/test_async_chat_completion_streaming.yaml +2232 -0
  48. tests/groq/cassettes/test_chat_completion.yaml +114 -0
  49. tests/groq/cassettes/test_chat_completion_streaming.yaml +2512 -0
  50. tests/groq/conftest.py +33 -0
  51. tests/groq/test_groq.py +142 -0
  52. tests/openai/cassettes/test_async_chat_completion_streaming.yaml +28 -28
  53. tests/openai/test_chat_completion.py +53 -67
  54. tests/openai/test_image_generation.py +47 -24
  55. tests/utils.py +40 -5
  56. {langtrace_python_sdk-2.1.29.dist-info → langtrace_python_sdk-2.2.2.dist-info}/WHEEL +0 -0
  57. {langtrace_python_sdk-2.1.29.dist-info → langtrace_python_sdk-2.2.2.dist-info}/entry_points.txt +0 -0
  58. {langtrace_python_sdk-2.1.29.dist-info → langtrace_python_sdk-2.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -17,7 +17,7 @@ limitations under the License.
17
17
  import json
18
18
  from langtrace.trace_attributes import DatabaseSpanAttributes
19
19
  from langtrace_python_sdk.utils.silently_fail import silently_fail
20
- from langtrace_python_sdk.utils.llm import set_span_attributes
20
+ from langtrace_python_sdk.utils import set_span_attribute
21
21
  from opentelemetry import baggage, trace
22
22
  from opentelemetry.trace import SpanKind
23
23
  from opentelemetry.trace.status import Status, StatusCode
@@ -64,7 +64,7 @@ def collection_patch(method, version, tracer):
64
64
  ) as span:
65
65
  collection_name = kwargs.get("collection_name") or args[0]
66
66
  operation = api["OPERATION"]
67
- set_span_attributes(span, "db.collection.name", collection_name)
67
+ set_span_attribute(span, "db.collection.name", collection_name)
68
68
 
69
69
  if operation == "add":
70
70
  _set_upload_attributes(span, args, kwargs, "documents")
@@ -111,7 +111,7 @@ def _set_upsert_attributes(span, args, kwargs):
111
111
  else:
112
112
  # In case of using Batch.
113
113
  length = len(points.ids)
114
- set_span_attributes(span, "db.upsert.points_count", length)
114
+ set_span_attribute(span, "db.upsert.points_count", length)
115
115
 
116
116
 
117
117
  @silently_fail
@@ -123,16 +123,16 @@ def _set_upload_attributes(span, args, kwargs, field):
123
123
  # In case of using Batch.
124
124
  length = len(docs.ids)
125
125
 
126
- set_span_attributes(span, f"db.upload.{field}_count", length)
126
+ set_span_attribute(span, f"db.upload.{field}_count", length)
127
127
 
128
128
 
129
129
  @silently_fail
130
130
  def _set_search_attributes(span, args, kwargs):
131
131
  limit = kwargs.get("limit") or 10
132
- set_span_attributes(span, "db.query.top_k", limit)
132
+ set_span_attribute(span, "db.query.top_k", limit)
133
133
 
134
134
 
135
135
  @silently_fail
136
136
  def _set_batch_search_attributes(span, args, kwargs, method):
137
137
  requests = kwargs.get("requests") or []
138
- set_span_attributes(span, f"db.{method}.requests_count", len(requests))
138
+ set_span_attribute(span, f"db.{method}.requests_count", len(requests))
@@ -0,0 +1,3 @@
1
+ from .instrumentation import VertexAIInstrumentation
2
+
3
+ __all__ = ["VertexAIInstrumentation"]
@@ -0,0 +1,33 @@
1
+ from typing import Collection
2
+ from importlib_metadata import version as v
3
+ from langtrace_python_sdk.constants.instrumentation.vertexai import APIS
4
+ from wrapt import wrap_function_wrapper as _W
5
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
6
+ from opentelemetry.trace import get_tracer
7
+ from .patch import patch_vertexai
8
+
9
+
10
+ class VertexAIInstrumentation(BaseInstrumentor):
11
+ def instrumentation_dependencies(self) -> Collection[str]:
12
+ return ["google-cloud-aiplatform >= 1.0.0"]
13
+
14
+ def _instrument(self, **kwargs):
15
+ trace_provider = kwargs.get("tracer_provider")
16
+ tracer = get_tracer(__name__, "", trace_provider)
17
+ version = v("google-cloud-aiplatform")
18
+
19
+ for _, api_config in APIS.items():
20
+
21
+ module = api_config.get("module")
22
+ operation = api_config.get("operation")
23
+ method = api_config.get("method")
24
+ name = f"{method}.{operation}"
25
+
26
+ _W(
27
+ module=module,
28
+ name=name,
29
+ wrapper=patch_vertexai(name, version, tracer),
30
+ )
31
+
32
+ def _uninstrument(self, **kwargs):
33
+ pass
@@ -0,0 +1,131 @@
1
+ import types
2
+ from langtrace_python_sdk.constants.instrumentation.common import SERVICE_PROVIDERS
3
+
4
+
5
+ from langtrace_python_sdk.utils.llm import (
6
+ calculate_prompt_tokens,
7
+ get_extra_attributes,
8
+ get_langtrace_attributes,
9
+ get_llm_request_attributes,
10
+ get_llm_url,
11
+ set_event_completion,
12
+ set_span_attributes,
13
+ set_usage_attributes,
14
+ StreamWrapper,
15
+ )
16
+ from langtrace.trace_attributes import LLMSpanAttributes, SpanAttributes
17
+ from langtrace_python_sdk.utils.silently_fail import silently_fail
18
+ from opentelemetry.trace import Tracer, SpanKind, Span
19
+ from opentelemetry import trace
20
+ from opentelemetry.trace.propagation import set_span_in_context
21
+ from opentelemetry.trace.status import Status, StatusCode
22
+ import json
23
+
24
+
25
+ def patch_vertexai(name, version, tracer: Tracer):
26
+ def traced_method(wrapped, instance, args, kwargs):
27
+ service_provider = SERVICE_PROVIDERS["VERTEXAI"]
28
+ prompts = serialize_prompts(args, kwargs)
29
+ span_attributes = {
30
+ **get_langtrace_attributes(version, service_provider),
31
+ **get_llm_request_attributes(
32
+ kwargs,
33
+ prompts=prompts,
34
+ model=get_llm_model(instance),
35
+ ),
36
+ **get_llm_url(instance),
37
+ SpanAttributes.LLM_PATH: "",
38
+ **get_extra_attributes(),
39
+ }
40
+ attributes = LLMSpanAttributes(**span_attributes)
41
+ span = tracer.start_span(
42
+ name=name,
43
+ kind=SpanKind.CLIENT,
44
+ context=set_span_in_context(trace.get_current_span()),
45
+ )
46
+
47
+ try:
48
+ set_span_attributes(span, attributes)
49
+ result = wrapped(*args, **kwargs)
50
+ if is_streaming_response(result):
51
+ prompt_tokens = 0
52
+ for message in kwargs.get("message", {}):
53
+ prompt_tokens += calculate_prompt_tokens(
54
+ json.dumps(message), kwargs.get("model")
55
+ )
56
+ return StreamWrapper(
57
+ stream=result, span=span, prompt_tokens=prompt_tokens
58
+ )
59
+ else:
60
+ set_response_attributes(span, result)
61
+ span.set_status(StatusCode.OK)
62
+ span.end()
63
+ return result
64
+ except Exception as error:
65
+ span.record_exception(error)
66
+ span.set_status(Status(StatusCode.ERROR, str(error)))
67
+ span.end()
68
+ raise
69
+
70
+ return traced_method
71
+
72
+
73
+ @silently_fail
74
+ def set_response_attributes(span: Span, result):
75
+
76
+ if hasattr(result, "text"):
77
+ set_event_completion(span, [{"role": "assistant", "content": result.text}])
78
+
79
+ if hasattr(result, "usage_metadata"):
80
+ usage = result.usage_metadata
81
+ input_tokens = usage.prompt_token_count
82
+ output_tokens = usage.candidates_token_count
83
+
84
+ set_usage_attributes(
85
+ span, {"input_tokens": input_tokens, "output_tokens": output_tokens}
86
+ )
87
+
88
+ if hasattr(result, "_prediction_response"):
89
+ usage = result._prediction_response.metadata.get("tokenMetadata")
90
+ input_tokens = usage.get("inputTokenCount").get("totalTokens")
91
+ output_tokens = usage.get("outputTokenCount").get("totalTokens")
92
+ set_usage_attributes(
93
+ span, {"input_tokens": input_tokens, "output_tokens": output_tokens}
94
+ )
95
+
96
+
97
+ def is_streaming_response(response):
98
+ return isinstance(response, types.GeneratorType) or isinstance(
99
+ response, types.AsyncGeneratorType
100
+ )
101
+
102
+
103
+ def get_llm_model(instance):
104
+ llm_model = "unknown"
105
+ if hasattr(instance, "_model_id"):
106
+ llm_model = instance._model_id
107
+ if hasattr(instance, "_model_name"):
108
+ llm_model = instance._model_name.replace("publishers/google/models/", "")
109
+ return llm_model
110
+
111
+
112
+ def serialize_prompts(args, kwargs):
113
+ prompt = ""
114
+ if args is not None and len(args) > 0:
115
+ for arg in args:
116
+ if isinstance(arg, str):
117
+ prompt = f"{prompt}{arg}\n"
118
+ elif isinstance(arg, list):
119
+ for subarg in arg:
120
+ if type(subarg).__name__ == "Part":
121
+ prompt = f"{prompt}{json.dumps(subarg.to_dict())}\n"
122
+ else:
123
+ prompt = f"{prompt}{subarg}\n"
124
+ else:
125
+ prompt = [
126
+ {
127
+ "role": "user",
128
+ "content": kwargs.get("prompt") or kwargs.get("message"),
129
+ }
130
+ ]
131
+ return prompt
@@ -53,6 +53,8 @@ from langtrace_python_sdk.instrumentation import (
53
53
  WeaviateInstrumentation,
54
54
  OllamaInstrumentor,
55
55
  DspyInstrumentation,
56
+ VertexAIInstrumentation,
57
+ GeminiInstrumentation,
56
58
  )
57
59
  from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
58
60
  from colorama import Fore
@@ -113,6 +115,8 @@ def init(
113
115
  "ollama": OllamaInstrumentor(),
114
116
  "dspy": DspyInstrumentation(),
115
117
  "crewai": CrewAIInstrumentation(),
118
+ "vertexai": VertexAIInstrumentation(),
119
+ "gemini": GeminiInstrumentation(),
116
120
  }
117
121
 
118
122
  init_instrumentations(disable_instrumentations, all_instrumentations)
@@ -143,6 +147,7 @@ def init_instrumentations(
143
147
  ):
144
148
  if disable_instrumentations is None:
145
149
  for idx, (name, v) in enumerate(all_instrumentations.items()):
150
+
146
151
  v.instrument()
147
152
  else:
148
153
 
@@ -1,10 +1,21 @@
1
+ from openai import NOT_GIVEN
1
2
  from .sdk_version_checker import SDKVersionChecker
3
+ from opentelemetry.trace import Span
4
+ from langtrace.trace_attributes import SpanAttributes
2
5
 
3
6
 
4
- def set_span_attribute(span, name, value):
7
+ def set_span_attribute(span: Span, name, value):
5
8
  if value is not None:
6
- if value != "":
7
- span.set_attribute(name, value)
9
+ if value != "" or value != NOT_GIVEN:
10
+ if name == SpanAttributes.LLM_PROMPTS:
11
+ span.add_event(
12
+ name=SpanAttributes.LLM_CONTENT_PROMPT,
13
+ attributes={
14
+ SpanAttributes.LLM_PROMPTS: value,
15
+ },
16
+ )
17
+ else:
18
+ span.set_attribute(name, value)
8
19
  return
9
20
 
10
21
 
@@ -14,10 +14,22 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
+ from langtrace_python_sdk.constants import LANGTRACE_SDK_NAME
18
+ from langtrace_python_sdk.utils import set_span_attribute
19
+ from openai import NOT_GIVEN
17
20
  from tiktoken import get_encoding
18
21
 
19
- from langtrace_python_sdk.constants.instrumentation.common import TIKTOKEN_MODEL_MAPPING
22
+ from langtrace_python_sdk.constants.instrumentation.common import (
23
+ LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY,
24
+ TIKTOKEN_MODEL_MAPPING,
25
+ )
20
26
  from langtrace_python_sdk.constants.instrumentation.openai import OPENAI_COST_TABLE
27
+ from langtrace.trace_attributes import SpanAttributes, Event
28
+ from importlib_metadata import version as v
29
+ import json
30
+ from opentelemetry import baggage
31
+ from opentelemetry.trace import Span
32
+ from opentelemetry.trace.status import StatusCode
21
33
 
22
34
 
23
35
  def estimate_tokens(prompt):
@@ -29,6 +41,15 @@ def estimate_tokens(prompt):
29
41
  return 0
30
42
 
31
43
 
44
+ def set_event_completion_chunk(span: Span, chunk):
45
+ span.add_event(
46
+ name=SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK,
47
+ attributes={
48
+ SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK: json.dumps(chunk),
49
+ },
50
+ )
51
+
52
+
32
53
  def estimate_tokens_using_tiktoken(prompt, model):
33
54
  """
34
55
  Estimate the number of tokens in a prompt using tiktoken."""
@@ -60,11 +81,110 @@ def calculate_price_from_usage(model, usage):
60
81
  return 0
61
82
 
62
83
 
63
- def set_span_attributes(span, name, value):
64
- if value is not None:
65
- if value != "":
66
- span.set_attribute(name, value)
67
- return
84
+ def get_langtrace_attributes(version, service_provider, vendor_type="llm"):
85
+ return {
86
+ SpanAttributes.LANGTRACE_SDK_NAME: LANGTRACE_SDK_NAME,
87
+ SpanAttributes.LANGTRACE_VERSION: v(LANGTRACE_SDK_NAME),
88
+ SpanAttributes.LANGTRACE_SERVICE_VERSION: version,
89
+ SpanAttributes.LANGTRACE_SERVICE_NAME: service_provider,
90
+ SpanAttributes.LANGTRACE_SERVICE_TYPE: vendor_type,
91
+ }
92
+
93
+
94
+ def get_llm_request_attributes(kwargs, prompts=None, model=None):
95
+
96
+ user = kwargs.get("user", None)
97
+ if prompts is None:
98
+ prompts = (
99
+ [{"role": user or "user", "content": kwargs.get("prompt")}]
100
+ if "prompt" in kwargs
101
+ else None
102
+ )
103
+ top_k = (
104
+ kwargs.get("n", None)
105
+ or kwargs.get("k", None)
106
+ or kwargs.get("top_k", None)
107
+ or kwargs.get("top_n", None)
108
+ )
109
+
110
+ top_p = kwargs.get("p", None) or kwargs.get("top_p", None)
111
+ tools = kwargs.get("tools", None)
112
+ return {
113
+ SpanAttributes.LLM_REQUEST_MODEL: model or kwargs.get("model"),
114
+ SpanAttributes.LLM_IS_STREAMING: kwargs.get("stream"),
115
+ SpanAttributes.LLM_REQUEST_TEMPERATURE: kwargs.get("temperature"),
116
+ SpanAttributes.LLM_TOP_K: top_k,
117
+ SpanAttributes.LLM_PROMPTS: json.dumps(prompts) if prompts else None,
118
+ SpanAttributes.LLM_USER: user,
119
+ SpanAttributes.LLM_REQUEST_TOP_P: top_p,
120
+ SpanAttributes.LLM_REQUEST_MAX_TOKENS: kwargs.get("max_tokens"),
121
+ SpanAttributes.LLM_SYSTEM_FINGERPRINT: kwargs.get("system_fingerprint"),
122
+ SpanAttributes.LLM_PRESENCE_PENALTY: kwargs.get("presence_penalty"),
123
+ SpanAttributes.LLM_FREQUENCY_PENALTY: kwargs.get("frequency_penalty"),
124
+ SpanAttributes.LLM_REQUEST_SEED: kwargs.get("seed"),
125
+ SpanAttributes.LLM_TOOLS: json.dumps(tools) if tools else None,
126
+ SpanAttributes.LLM_REQUEST_LOGPROPS: kwargs.get("logprobs"),
127
+ SpanAttributes.LLM_REQUEST_LOGITBIAS: kwargs.get("logit_bias"),
128
+ SpanAttributes.LLM_REQUEST_TOP_LOGPROPS: kwargs.get("top_logprobs"),
129
+ }
130
+
131
+
132
+ def get_extra_attributes():
133
+ extra_attributes = baggage.get_baggage(LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY)
134
+ return extra_attributes or {}
135
+
136
+
137
+ def get_llm_url(instance):
138
+ return {
139
+ SpanAttributes.LLM_URL: get_base_url(instance),
140
+ }
141
+
142
+
143
+ def get_base_url(instance):
144
+ return (
145
+ str(instance._client._base_url)
146
+ if hasattr(instance, "_client") and hasattr(instance._client, "_base_url")
147
+ else ""
148
+ )
149
+
150
+
151
+ def is_streaming(kwargs):
152
+ return not (
153
+ kwargs.get("stream") is False
154
+ or kwargs.get("stream") is None
155
+ or kwargs.get("stream") == NOT_GIVEN
156
+ )
157
+
158
+
159
+ def set_usage_attributes(span, usage):
160
+ if usage is None:
161
+ return
162
+
163
+ input_tokens = usage.get("input_tokens") or usage.get("prompt_tokens") or 0
164
+ output_tokens = usage.get("output_tokens") or usage.get("completion_tokens") or 0
165
+
166
+ set_span_attribute(
167
+ span,
168
+ SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
169
+ input_tokens,
170
+ )
171
+
172
+ set_span_attribute(
173
+ span,
174
+ SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
175
+ output_tokens,
176
+ )
177
+
178
+ set_span_attribute(
179
+ span,
180
+ SpanAttributes.LLM_USAGE_TOTAL_TOKENS,
181
+ input_tokens + output_tokens,
182
+ )
183
+
184
+ if "search_units" in usage:
185
+ set_span_attribute(
186
+ span, SpanAttributes.LLM_USAGE_SEARCH_UNITS, usage["search_units"]
187
+ )
68
188
 
69
189
 
70
190
  def get_tool_calls(item):
@@ -77,3 +197,188 @@ def get_tool_calls(item):
77
197
  if hasattr(item, "tool_calls") and item.tool_calls is not None:
78
198
  return item.tool_calls
79
199
  return None
200
+
201
+
202
+ def set_event_completion(span: Span, result_content):
203
+
204
+ span.add_event(
205
+ name=SpanAttributes.LLM_CONTENT_COMPLETION,
206
+ attributes={
207
+ SpanAttributes.LLM_COMPLETIONS: json.dumps(result_content),
208
+ },
209
+ )
210
+
211
+
212
+ def set_span_attributes(span: Span, attributes: dict):
213
+ for field, value in attributes.model_dump(by_alias=True).items():
214
+ set_span_attribute(span, field, value)
215
+
216
+
217
+ class StreamWrapper:
218
+ span: Span
219
+
220
+ def __init__(
221
+ self, stream, span, prompt_tokens, function_call=False, tool_calls=False
222
+ ):
223
+ self.stream = stream
224
+ self.span = span
225
+ self.prompt_tokens = prompt_tokens
226
+ self.function_call = function_call
227
+ self.tool_calls = tool_calls
228
+ self.result_content = []
229
+ self.completion_tokens = 0
230
+ self._span_started = False
231
+ self.setup()
232
+
233
+ def setup(self):
234
+ if not self._span_started:
235
+ self.span.add_event(Event.STREAM_START.value)
236
+ self._span_started = True
237
+
238
+ def cleanup(self):
239
+ if self._span_started:
240
+ self.span.add_event(Event.STREAM_END.value)
241
+ set_span_attribute(
242
+ self.span,
243
+ SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
244
+ self.prompt_tokens,
245
+ )
246
+ set_span_attribute(
247
+ self.span,
248
+ SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
249
+ self.completion_tokens,
250
+ )
251
+ set_span_attribute(
252
+ self.span,
253
+ SpanAttributes.LLM_USAGE_TOTAL_TOKENS,
254
+ self.prompt_tokens + self.completion_tokens,
255
+ )
256
+ set_event_completion(
257
+ self.span,
258
+ [
259
+ {
260
+ "role": "assistant",
261
+ "content": "".join(self.result_content),
262
+ }
263
+ ],
264
+ )
265
+
266
+ self.span.set_status(StatusCode.OK)
267
+ self.span.end()
268
+ self._span_started = False
269
+
270
+ def __enter__(self):
271
+ self.setup()
272
+ return self
273
+
274
+ def __exit__(self, exc_type, exc_val, exc_tb):
275
+ self.cleanup()
276
+
277
+ async def __aenter__(self):
278
+ self.setup()
279
+ return self
280
+
281
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
282
+ self.cleanup()
283
+
284
+ def __iter__(self):
285
+ return self
286
+
287
+ def __next__(self):
288
+ try:
289
+ chunk = next(self.stream)
290
+ self.process_chunk(chunk)
291
+ return chunk
292
+ except StopIteration:
293
+ self.cleanup()
294
+ raise
295
+
296
+ def __aiter__(self):
297
+ return self
298
+
299
+ async def __anext__(self):
300
+ try:
301
+ chunk = await self.stream.__anext__()
302
+ self.process_chunk(chunk)
303
+ return chunk
304
+ except StopAsyncIteration:
305
+ self.cleanup()
306
+ raise StopAsyncIteration
307
+
308
+ def process_chunk(self, chunk):
309
+ if hasattr(chunk, "model") and chunk.model is not None:
310
+ set_span_attribute(
311
+ self.span,
312
+ SpanAttributes.LLM_RESPONSE_MODEL,
313
+ chunk.model,
314
+ )
315
+
316
+ if hasattr(chunk, "choices") and chunk.choices is not None:
317
+ content = []
318
+ if not self.function_call and not self.tool_calls:
319
+ for choice in chunk.choices:
320
+ if choice.delta and choice.delta.content is not None:
321
+ token_counts = estimate_tokens(choice.delta.content)
322
+ self.completion_tokens += token_counts
323
+ content = [choice.delta.content]
324
+ elif self.function_call:
325
+ for choice in chunk.choices:
326
+ if (
327
+ choice.delta
328
+ and choice.delta.function_call is not None
329
+ and choice.delta.function_call.arguments is not None
330
+ ):
331
+ token_counts = estimate_tokens(
332
+ choice.delta.function_call.arguments
333
+ )
334
+ self.completion_tokens += token_counts
335
+ content = [choice.delta.function_call.arguments]
336
+ elif self.tool_calls:
337
+ for choice in chunk.choices:
338
+ if choice.delta and choice.delta.tool_calls is not None:
339
+ toolcalls = choice.delta.tool_calls
340
+ content = []
341
+ for tool_call in toolcalls:
342
+ if (
343
+ tool_call
344
+ and tool_call.function is not None
345
+ and tool_call.function.arguments is not None
346
+ ):
347
+ token_counts = estimate_tokens(
348
+ tool_call.function.arguments
349
+ )
350
+ self.completion_tokens += token_counts
351
+ content.append(tool_call.function.arguments)
352
+ self.span.add_event(
353
+ Event.STREAM_OUTPUT.value,
354
+ {
355
+ SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK: (
356
+ "".join(content)
357
+ if len(content) > 0 and content[0] is not None
358
+ else ""
359
+ )
360
+ },
361
+ )
362
+ if content:
363
+ self.result_content.append(content[0])
364
+
365
+ if hasattr(chunk, "text"):
366
+ token_counts = estimate_tokens(chunk.text)
367
+ self.completion_tokens += token_counts
368
+ content = [chunk.text]
369
+ self.span.add_event(
370
+ Event.STREAM_OUTPUT.value,
371
+ {
372
+ SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK: (
373
+ "".join(content)
374
+ if len(content) > 0 and content[0] is not None
375
+ else ""
376
+ )
377
+ },
378
+ )
379
+ if content:
380
+ self.result_content.append(content[0])
381
+
382
+ if hasattr(chunk, "usage_metadata"):
383
+ self.completion_tokens = chunk.usage_metadata.candidates_token_count
384
+ self.prompt_tokens = chunk.usage_metadata.prompt_token_count
@@ -1 +1 @@
1
- __version__ = "2.1.29"
1
+ __version__ = "2.2.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langtrace-python-sdk
3
- Version: 2.1.29
3
+ Version: 2.2.2
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>
@@ -20,11 +20,14 @@ Requires-Dist: opentelemetry-instrumentation>=0.46b0
20
20
  Requires-Dist: opentelemetry-sdk>=1.25.0
21
21
  Requires-Dist: sqlalchemy
22
22
  Requires-Dist: tiktoken>=0.1.1
23
- Requires-Dist: trace-attributes<6.0.0,>=5.0.0
23
+ Requires-Dist: trace-attributes<7.0.0,>=6.0.0
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: anthropic; extra == 'dev'
26
26
  Requires-Dist: chromadb; extra == 'dev'
27
27
  Requires-Dist: cohere; extra == 'dev'
28
+ Requires-Dist: google-cloud-aiplatform; extra == 'dev'
29
+ Requires-Dist: google-generativeai; extra == 'dev'
30
+ Requires-Dist: groq; extra == 'dev'
28
31
  Requires-Dist: langchain; extra == 'dev'
29
32
  Requires-Dist: langchain-community; extra == 'dev'
30
33
  Requires-Dist: langchain-openai; extra == 'dev'
@@ -274,23 +277,27 @@ prompt = get_prompt_from_registry(<Registry ID>, options={"prompt_version": 1, "
274
277
 
275
278
  Langtrace automatically captures traces from the following vendors:
276
279
 
277
- | Vendor | Type | Typescript SDK | Python SDK |
278
- | ------------ | --------------- | ------------------ | ------------------ |
279
- | OpenAI | LLM | :white_check_mark: | :white_check_mark: |
280
- | Anthropic | LLM | :white_check_mark: | :white_check_mark: |
281
- | Azure OpenAI | LLM | :white_check_mark: | :white_check_mark: |
282
- | Cohere | LLM | :white_check_mark: | :white_check_mark: |
283
- | Groq | LLM | :x: | :white_check_mark: |
284
- | Langchain | Framework | :x: | :white_check_mark: |
285
- | Langgraph | Framework | :x: | :white_check_mark: |
286
- | LlamaIndex | Framework | :white_check_mark: | :white_check_mark: |
287
- | DSPy | Framework | :x: | :white_check_mark: |
288
- | CrewAI | Framework | :x: | :white_check_mark: |
289
- | Ollama | Framework | :x: | :white_check_mark: |
290
- | Pinecone | Vector Database | :white_check_mark: | :white_check_mark: |
291
- | ChromaDB | Vector Database | :white_check_mark: | :white_check_mark: |
292
- | Weaviate | Vector Database | :white_check_mark: | :white_check_mark: |
293
- | QDrant | Vector Database | :x: | :white_check_mark: |
280
+ | Vendor | Type | Typescript SDK | Python SDK |
281
+ | ------------ | --------------- | ------------------ | ------------------------------- |
282
+ | OpenAI | LLM | :white_check_mark: | :white_check_mark: |
283
+ | Anthropic | LLM | :white_check_mark: | :white_check_mark: |
284
+ | Azure OpenAI | LLM | :white_check_mark: | :white_check_mark: |
285
+ | Cohere | LLM | :white_check_mark: | :white_check_mark: |
286
+ | Groq | LLM | :x: | :white_check_mark: |
287
+ | Perplexity | LLM | :white_check_mark: | :white_check_mark: |
288
+ | Gemini | LLM | :x: | :white_check_mark: |
289
+ | Langchain | Framework | :x: | :white_check_mark: |
290
+ | LlamaIndex | Framework | :white_check_mark: | :white_check_mark: |
291
+ | Langgraph | Framework | :x: | :white_check_mark: |
292
+ | DSPy | Framework | :x: | :white_check_mark: |
293
+ | CrewAI | Framework | :x: | :white_check_mark: |
294
+ | Ollama | Framework | :x: | :white_check_mark: |
295
+ | VertexAI | Framework | :x: | :white_check_mark: |
296
+ | Pinecone | Vector Database | :white_check_mark: | :white_check_mark: |
297
+ | ChromaDB | Vector Database | :white_check_mark: | :white_check_mark: |
298
+ | QDrant | Vector Database | :white_check_mark: | :white_check_mark: |
299
+ | Weaviate | Vector Database | :white_check_mark: | :white_check_mark: |
300
+ | PGVector | Vector Database | :white_check_mark: | :white_check_mark: (SQLAlchemy) |
294
301
 
295
302
  ---
296
303