lmnr 0.7.17__py3-none-any.whl → 0.7.18__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.

Potentially problematic release.


This version of lmnr might be problematic. Click here for more details.

@@ -206,15 +206,17 @@ def _set_request_attributes(span, args, kwargs):
206
206
  contents = [contents]
207
207
  for content in contents:
208
208
  processed_content = process_content_union(content)
209
- content_str = get_content(processed_content)
209
+ content_payload = get_content(processed_content)
210
+ if isinstance(content_payload, dict):
211
+ content_payload = [content_payload]
210
212
 
211
213
  set_span_attribute(
212
214
  span,
213
215
  f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.content",
214
216
  (
215
- content_str
216
- if isinstance(content_str, str)
217
- else json_dumps(content_str)
217
+ content_payload
218
+ if isinstance(content_payload, str)
219
+ else json_dumps(content_payload)
218
220
  ),
219
221
  )
220
222
  blocks = (
@@ -318,20 +320,22 @@ def _set_response_attributes(span, response: types.GenerateContentResponse):
318
320
  for candidate in candidates_list:
319
321
  has_content = False
320
322
  processed_content = process_content_union(candidate.content)
321
- content_str = get_content(processed_content)
323
+ content_payload = get_content(processed_content)
324
+ if isinstance(content_payload, dict):
325
+ content_payload = [content_payload]
322
326
 
323
327
  set_span_attribute(
324
328
  span, f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.role", "model"
325
329
  )
326
- if content_str:
330
+ if content_payload:
327
331
  has_content = True
328
332
  set_span_attribute(
329
333
  span,
330
334
  f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.content",
331
335
  (
332
- content_str
333
- if isinstance(content_str, str)
334
- else json_dumps(content_str)
336
+ content_payload
337
+ if isinstance(content_payload, str)
338
+ else json_dumps(content_payload)
335
339
  ),
336
340
  )
337
341
  blocks = (
@@ -380,6 +384,10 @@ def _build_from_streaming_response(
380
384
  aggregated_usage_metadata = defaultdict(int)
381
385
  model_version = None
382
386
  for chunk in response:
387
+ try:
388
+ span.add_event("llm.content.completion.chunk")
389
+ except Exception:
390
+ pass
383
391
  # Important: do all processing in a separate sync function, that is
384
392
  # wrapped in @dont_throw. If we did it here, the @dont_throw on top of
385
393
  # this function would not be able to catch the errors, as they are
@@ -430,6 +438,10 @@ async def _abuild_from_streaming_response(
430
438
  aggregated_usage_metadata = defaultdict(int)
431
439
  model_version = None
432
440
  async for chunk in response:
441
+ try:
442
+ span.add_event("llm.content.completion.chunk")
443
+ except Exception:
444
+ pass
433
445
  # Important: do all processing in a separate sync function, that is
434
446
  # wrapped in @dont_throw. If we did it here, the @dont_throw on top of
435
447
  # this function would not be able to catch the errors, as they are
lmnr/sdk/laminar.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from contextlib import contextmanager
2
- from contextvars import Context
2
+ from contextvars import Context, Token
3
3
  import warnings
4
4
  from lmnr.opentelemetry_lib import TracerManager
5
5
  from lmnr.opentelemetry_lib.tracing import TracerWrapper, get_current_context
@@ -434,17 +434,17 @@ class Laminar:
434
434
  with Laminar.use_span(span):
435
435
  with Laminar.start_as_current_span("foo_inner"):
436
436
  some_function()
437
-
437
+
438
438
  def bar():
439
439
  with Laminar.use_span(span):
440
440
  openai_client.chat.completions.create()
441
-
441
+
442
442
  span = Laminar.start_span("outer")
443
443
  foo(span)
444
444
  bar(span)
445
445
  # IMPORTANT: End the span manually
446
446
  span.end()
447
-
447
+
448
448
  # Results in:
449
449
  # | outer
450
450
  # | | foo
@@ -642,6 +642,92 @@ class Laminar:
642
642
  if end_on_exit:
643
643
  span.end()
644
644
 
645
+ @classmethod
646
+ def start_active_span(
647
+ cls,
648
+ name: str,
649
+ input: Any = None,
650
+ span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
651
+ context: Context | None = None,
652
+ parent_span_context: LaminarSpanContext | None = None,
653
+ tags: list[str] | None = None,
654
+ ) -> tuple[Span, Token[Context] | None]:
655
+ """Start a new span. Useful for manual instrumentation.
656
+ If `span_type` is set to `"LLM"`, you should report usage and response
657
+ attributes manually. See `Laminar.set_span_attributes` for more
658
+ information. Returns the span and a context token that can be used to
659
+ detach the context.
660
+
661
+ Usage example:
662
+ ```python
663
+ from src.lmnr import Laminar
664
+ def foo():
665
+ with Laminar.start_active_span("foo_inner"):
666
+ some_function()
667
+
668
+ def bar():
669
+ openai_client.chat.completions.create()
670
+
671
+ span, ctx_token = Laminar.start_active_span("outer")
672
+ foo()
673
+ bar()
674
+ # IMPORTANT: End the span manually
675
+ Laminar.end_active_span(span, ctx_token)
676
+
677
+ # Results in:
678
+ # | outer
679
+ # | | foo
680
+ # | | | foo_inner
681
+ # | | bar
682
+ # | | | openai.chat
683
+ ```
684
+
685
+ Args:
686
+ name (str): name of the span
687
+ input (Any, optional): input to the span. Will be sent as an\
688
+ attribute, so must be json serializable. Defaults to None.
689
+ span_type (Literal["DEFAULT", "LLM", "TOOL"], optional):\
690
+ type of the span. If you use `"LLM"`, you should report usage\
691
+ and response attributes manually. Defaults to "DEFAULT".
692
+ context (Context | None, optional): raw OpenTelemetry context\
693
+ to attach the span to. Defaults to None.
694
+ parent_span_context (LaminarSpanContext | None, optional): parent\
695
+ span context to use for the span. Useful for continuing traces\
696
+ across services. If parent_span_context is a\
697
+ raw OpenTelemetry span context, or if it is a dictionary or string\
698
+ obtained from `Laminar.get_laminar_span_context_dict()` or\
699
+ `Laminar.get_laminar_span_context_str()` respectively, it will be\
700
+ converted to a `LaminarSpanContext` if possible. See also\
701
+ `Laminar.get_span_context`, `Laminar.get_span_context_dict` and\
702
+ `Laminar.get_span_context_str` for more information.
703
+ Defaults to None.
704
+ tags (list[str] | None, optional): tags to set for the span.
705
+ Defaults to None.
706
+ """
707
+ span = cls.start_span(
708
+ name, input, span_type, context, parent_span_context, tags
709
+ )
710
+ if not cls.is_initialized():
711
+ return span, None
712
+ wrapper = TracerWrapper()
713
+ context = wrapper.push_span_context(span)
714
+ context_token = context_api.attach(context)
715
+ return span, context_token
716
+
717
+ @classmethod
718
+ def end_active_span(cls, span: Span, ctx_token: Token[Context]):
719
+ """End an active span."""
720
+ span.end()
721
+ if not cls.is_initialized():
722
+ return
723
+ wrapper = TracerWrapper()
724
+ try:
725
+ wrapper.pop_span_context()
726
+ if ctx_token is not None:
727
+ context_api.detach(ctx_token)
728
+ except Exception:
729
+ pass
730
+
645
731
  @classmethod
646
732
  def set_span_output(cls, output: Any = None):
647
733
  """Set the output of the current span. Useful for manual
@@ -671,12 +757,12 @@ class Laminar:
671
757
  instrumentation.
672
758
  Example:
673
759
  ```python
674
- with L.start_as_current_span(
760
+ with Laminar.start_as_current_span(
675
761
  name="my_span_name", input=input["messages"], span_type="LLM"
676
762
  ):
677
763
  response = await my_custom_call_to_openai(input)
678
- L.set_span_output(response["choices"][0]["message"]["content"])
679
- L.set_span_attributes({
764
+ Laminar.set_span_output(response["choices"][0]["message"]["content"])
765
+ Laminar.set_span_attributes({
680
766
  Attributes.PROVIDER: 'openai',
681
767
  Attributes.REQUEST_MODEL: input["model"],
682
768
  Attributes.RESPONSE_MODEL: response["model"],
lmnr/version.py CHANGED
@@ -3,7 +3,7 @@ import httpx
3
3
  from packaging import version
4
4
 
5
5
 
6
- __version__ = "0.7.17"
6
+ __version__ = "0.7.18"
7
7
  PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
8
8
 
9
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lmnr
3
- Version: 0.7.17
3
+ Version: 0.7.18
4
4
  Summary: Python SDK for Laminar
5
5
  Author: lmnr.ai
6
6
  Author-email: lmnr.ai <founders@lmnr.ai>
@@ -16,7 +16,7 @@ lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py,sha256
16
16
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py,sha256=48391d935883506fe1dc4f6ace6011ecaed76a8f82f8026ccb553b2180afdb8c,3455
17
17
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py,sha256=61d2681e99c3084d1bcc27f7ca551f44a70126df6c5f23320c1e9c1654e05c42,15037
18
18
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py,sha256=19090d4d9a0511645f66112ebe6f05a9993905b11d8ae3060dab2dcc4c1a5fb2,329
19
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py,sha256=02f426b0296b3537705bdaf306f91cc7e914812ecb8eb18a18e43a9f4ed2b659,20221
19
+ lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py,sha256=df9f06eda1f16f1ab99dbfdfae20ff55da1fb7842b53d51179b4fccdf9b0ac8f,20691
20
20
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py,sha256=db9cdebc9ee0dccb493ffe608eede3047efec20ed26c3924b72b2e50edbd92c2,245
21
21
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py,sha256=b10619e76e5893f8b891f92531d29dcf6651e8f9a7dcbf81c3f35341ce311f6e,753
22
22
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py,sha256=ac662208d336cb9f5eb086389cd77d73e1a5a8bcc22ce8fffccd12a828b4e5cd,11092
@@ -93,12 +93,12 @@ lmnr/sdk/datasets.py,sha256=3fd851c5f97bf88eaa84b1451a053eaff23b4497cbb45eac2f9e
93
93
  lmnr/sdk/decorators.py,sha256=c709b76a814e019c919fd811591850787a2f266b7b6f46123f66ddd92e1092d5,6920
94
94
  lmnr/sdk/eval_control.py,sha256=291394ac385c653ae9b5167e871bebeb4fe8fc6b7ff2ed38e636f87015dcba86,184
95
95
  lmnr/sdk/evaluations.py,sha256=7e55cbca77fa32cb64cb77aed8076a1994258a5b652c7f1d45231928e4aefe26,23885
96
- lmnr/sdk/laminar.py,sha256=24adfd64da01d7fd69ba9437cf9860a5c64aa6baab1bb92d8ba143db1be12e96,38313
96
+ lmnr/sdk/laminar.py,sha256=5fcb699577b19d60b0cf5c494eb366420d69d0ba17ac2cde979f0648ac38486e,41772
97
97
  lmnr/sdk/log.py,sha256=9edfd83263f0d4845b1b2d1beeae2b4ed3f8628de941f371a893d72b79c348d4,2213
98
98
  lmnr/sdk/types.py,sha256=d8061ca90dd582b408a893ebbbeb1586e8750ed30433ef4f6d63423a078511b0,14574
99
99
  lmnr/sdk/utils.py,sha256=4114559ba6ae57fcba2de2bfaa09339688ce5752c36f028a7b55e51eae624947,6307
100
- lmnr/version.py,sha256=e305a8be18cd8c4d3cd950cef8dc20ffba7c351cd92c344aff4358aa9b520316,1322
101
- lmnr-0.7.17.dist-info/WHEEL,sha256=ab6157bc637547491fb4567cd7ddf26b04d63382916ca16c29a5c8e94c9c9ef7,79
102
- lmnr-0.7.17.dist-info/entry_points.txt,sha256=abdf3411b7dd2d7329a241f2da6669bab4e314a747a586ecdb9f888f3035003c,39
103
- lmnr-0.7.17.dist-info/METADATA,sha256=e295f9fd97eda35dd4e7dcc564ced240043eeda521bff23fd3072b5347caf810,14258
104
- lmnr-0.7.17.dist-info/RECORD,,
100
+ lmnr/version.py,sha256=98f78bacb066c12efdabeed4190a9b7a2386c093d36e8d1ce91276954fcab806,1322
101
+ lmnr-0.7.18.dist-info/WHEEL,sha256=ab6157bc637547491fb4567cd7ddf26b04d63382916ca16c29a5c8e94c9c9ef7,79
102
+ lmnr-0.7.18.dist-info/entry_points.txt,sha256=abdf3411b7dd2d7329a241f2da6669bab4e314a747a586ecdb9f888f3035003c,39
103
+ lmnr-0.7.18.dist-info/METADATA,sha256=b69c7b58f2b88f8839580b89bf55a7e732f92e60f88588dfe68f406579899be3,14258
104
+ lmnr-0.7.18.dist-info/RECORD,,
File without changes