lmnr 0.4.29b3__py3-none-any.whl → 0.4.30__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.
lmnr/__init__.py CHANGED
@@ -11,3 +11,4 @@ from .sdk.types import (
11
11
  from .sdk.decorators import observe
12
12
  from .traceloop_sdk import Instruments
13
13
  from .traceloop_sdk.tracing.attributes import Attributes
14
+ from opentelemetry.trace import use_span
lmnr/sdk/laminar.py CHANGED
@@ -1,28 +1,31 @@
1
- import re
2
- from lmnr.traceloop_sdk.instruments import Instruments
3
- from opentelemetry import context
4
- from opentelemetry.trace import (
5
- INVALID_SPAN,
6
- get_current_span,
7
- )
1
+ from contextlib import contextmanager
2
+ from contextvars import Context
3
+ from opentelemetry import context, trace
8
4
  from opentelemetry.util.types import AttributeValue
9
5
  from opentelemetry.context import set_value, attach, detach
10
6
  from lmnr.traceloop_sdk import Traceloop
7
+ from lmnr.traceloop_sdk.instruments import Instruments
11
8
  from lmnr.traceloop_sdk.tracing import get_tracer
12
- from lmnr.traceloop_sdk.tracing.attributes import Attributes, SPAN_TYPE
9
+ from lmnr.traceloop_sdk.tracing.attributes import (
10
+ Attributes,
11
+ SPAN_TYPE,
12
+ OVERRIDE_PARENT_SPAN,
13
+ )
13
14
  from lmnr.traceloop_sdk.decorators.base import json_dumps
14
- from contextlib import contextmanager
15
15
  from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
16
16
 
17
17
  from pydantic.alias_generators import to_snake
18
18
  from typing import Any, Literal, Optional, Set, Union
19
19
 
20
+ import aiohttp
20
21
  import copy
21
22
  import datetime
22
23
  import dotenv
23
24
  import json
24
25
  import logging
25
26
  import os
27
+ import random
28
+ import re
26
29
  import requests
27
30
  import urllib.parse
28
31
  import uuid
@@ -196,8 +199,8 @@ class Laminar:
196
199
  "API key or set the LMNR_PROJECT_API_KEY environment variable"
197
200
  )
198
201
  try:
199
- current_span = get_current_span()
200
- if current_span != INVALID_SPAN:
202
+ current_span = trace.get_current_span()
203
+ if current_span != trace.INVALID_SPAN:
201
204
  parent_span_id = parent_span_id or uuid.UUID(
202
205
  int=current_span.get_span_context().span_id
203
206
  )
@@ -276,8 +279,8 @@ class Laminar:
276
279
  if value is not None:
277
280
  event["lmnr.event.value"] = value
278
281
 
279
- current_span = get_current_span()
280
- if current_span == INVALID_SPAN:
282
+ current_span = trace.get_current_span()
283
+ if current_span == trace.INVALID_SPAN:
281
284
  cls.__logger.warning(
282
285
  "`Laminar().event()` called outside of span context. "
283
286
  f"Event '{name}' will not be recorded in the trace. "
@@ -294,6 +297,8 @@ class Laminar:
294
297
  name: str,
295
298
  input: Any = None,
296
299
  span_type: Union[Literal["DEFAULT"], Literal["LLM"]] = "DEFAULT",
300
+ context: Optional[Context] = None,
301
+ trace_id: Optional[uuid.UUID] = None,
297
302
  ):
298
303
  """Start a new span as the current span. Useful for manual
299
304
  instrumentation. If `span_type` is set to `"LLM"`, you should report
@@ -314,30 +319,139 @@ class Laminar:
314
319
  span_type (Union[Literal["DEFAULT"], Literal["LLM"]], optional):\
315
320
  type of the span. If you use `"LLM"`, you should report usage\
316
321
  and response attributes manually. Defaults to "DEFAULT".
322
+ context (Optional[Context], optional): raw OpenTelemetry context\
323
+ to attach the span to. Defaults to None.
324
+ trace_id (Optional[uuid.UUID], optional): [EXPERIMENTAL] override\
325
+ the trace id for the span. If not provided, use the current\
326
+ trace id. Defaults to None.
317
327
  """
328
+
318
329
  with get_tracer() as tracer:
319
330
  span_path = get_span_path(name)
320
- ctx = set_value("span_path", span_path)
331
+ ctx = set_value("span_path", span_path, context)
332
+ if trace_id is not None:
333
+ if isinstance(trace_id, uuid.UUID):
334
+ span_context = trace.SpanContext(
335
+ trace_id=int(trace_id),
336
+ span_id=random.getrandbits(64),
337
+ is_remote=False,
338
+ trace_flags=trace.TraceFlags(trace.TraceFlags.SAMPLED),
339
+ )
340
+ ctx = trace.set_span_in_context(
341
+ trace.NonRecordingSpan(span_context), ctx
342
+ )
343
+ else:
344
+ cls.__logger.warning(
345
+ "trace_id provided to `Laminar.start_as_current_span`"
346
+ " is not a valid UUID"
347
+ )
321
348
  ctx_token = attach(ctx)
322
349
  with tracer.start_as_current_span(
323
350
  name,
324
351
  context=ctx,
325
- attributes={SPAN_PATH: span_path},
352
+ attributes={SPAN_PATH: span_path, SPAN_TYPE: span_type},
326
353
  ) as span:
354
+ if trace_id is not None and isinstance(trace_id, uuid.UUID):
355
+ span.set_attribute(OVERRIDE_PARENT_SPAN, True)
327
356
  if input is not None:
328
357
  span.set_attribute(
329
358
  SPAN_INPUT,
330
359
  json_dumps(input),
331
360
  )
332
- span.set_attribute(SPAN_TYPE, span_type)
333
361
  yield span
334
362
 
335
- # TODO: Figure out if this is necessary
363
+ # # TODO: Figure out if this is necessary
336
364
  try:
337
365
  detach(ctx_token)
338
366
  except Exception:
339
367
  pass
340
368
 
369
+ @classmethod
370
+ def start_span(
371
+ cls,
372
+ name: str,
373
+ input: Any = None,
374
+ span_type: Union[Literal["DEFAULT"], Literal["LLM"]] = "DEFAULT",
375
+ context: Optional[Context] = None,
376
+ trace_id: Optional[uuid.UUID] = None,
377
+ ):
378
+ """Start a new span. Useful for manual instrumentation.
379
+ If `span_type` is set to `"LLM"`, you should report usage and response
380
+ attributes manually. See `Laminar.set_span_attributes` for more
381
+ information.
382
+
383
+ Usage example:
384
+ ```python
385
+ from src.lmnr import Laminar, use_span
386
+ def foo(span):
387
+ with use_span(span):
388
+ with Laminar.start_as_current_span("foo_inner"):
389
+ some_function()
390
+
391
+ def bar():
392
+ with use_span(span):
393
+ openai_client.chat.completions.create()
394
+
395
+ span = Laminar.start_span("outer")
396
+ foo(span)
397
+ bar(span)
398
+ # IMPORTANT: End the span manually
399
+ span.end()
400
+
401
+ # Results in:
402
+ # | outer
403
+ # | | foo
404
+ # | | | foo_inner
405
+ # | | bar
406
+ # | | | openai.chat
407
+ ```
408
+
409
+ Args:
410
+ name (str): name of the span
411
+ input (Any, optional): input to the span. Will be sent as an\
412
+ attribute, so must be json serializable. Defaults to None.
413
+ span_type (Union[Literal["DEFAULT"], Literal["LLM"]], optional):\
414
+ type of the span. If you use `"LLM"`, you should report usage\
415
+ and response attributes manually. Defaults to "DEFAULT".
416
+ context (Optional[Context], optional): raw OpenTelemetry context\
417
+ to attach the span to. Defaults to None.
418
+ trace_id (Optional[uuid.UUID], optional): [EXPERIMENTAL] override\
419
+ the trace id for the span. If not provided, use the current\
420
+ trace id. Defaults to None.
421
+ """
422
+ with get_tracer() as tracer:
423
+ span_path = get_span_path(name)
424
+ ctx = set_value("span_path", span_path, context)
425
+ if trace_id is not None:
426
+ if isinstance(trace_id, uuid.UUID):
427
+ span_context = trace.SpanContext(
428
+ trace_id=int(trace_id),
429
+ span_id=random.getrandbits(64),
430
+ is_remote=False,
431
+ trace_flags=trace.TraceFlags(trace.TraceFlags.SAMPLED),
432
+ )
433
+ ctx = trace.set_span_in_context(
434
+ trace.NonRecordingSpan(span_context), ctx
435
+ )
436
+ else:
437
+ cls.__logger.warning(
438
+ "trace_id provided to `Laminar.start_span`"
439
+ " is not a valid UUID"
440
+ )
441
+ span = tracer.start_span(
442
+ name,
443
+ context=ctx,
444
+ attributes={SPAN_PATH: span_path, SPAN_TYPE: span_type},
445
+ )
446
+ if trace_id is not None and isinstance(trace_id, uuid.UUID):
447
+ span.set_attribute(OVERRIDE_PARENT_SPAN, True)
448
+ if input is not None:
449
+ span.set_attribute(
450
+ SPAN_INPUT,
451
+ json_dumps(input),
452
+ )
453
+ return span
454
+
341
455
  @classmethod
342
456
  def set_span_output(cls, output: Any = None):
343
457
  """Set the output of the current span. Useful for manual
@@ -347,8 +461,8 @@ class Laminar:
347
461
  output (Any, optional): output of the span. Will be sent as an\
348
462
  attribute, so must be json serializable. Defaults to None.
349
463
  """
350
- span = get_current_span()
351
- if output is not None and span != INVALID_SPAN:
464
+ span = trace.get_current_span()
465
+ if output is not None and span != trace.INVALID_SPAN:
352
466
  span.set_attribute(SPAN_OUTPUT, json_dumps(output))
353
467
 
354
468
  @classmethod
@@ -378,8 +492,8 @@ class Laminar:
378
492
  Args:
379
493
  attributes (dict[ATTRIBUTES, Any]): attributes to set for the span
380
494
  """
381
- span = get_current_span()
382
- if span == INVALID_SPAN:
495
+ span = trace.get_current_span()
496
+ if span == trace.INVALID_SPAN:
383
497
  return
384
498
 
385
499
  for key, value in attributes.items():
lmnr/sdk/types.py CHANGED
@@ -129,7 +129,7 @@ class EvaluationResultDatapoint(pydantic.BaseModel):
129
129
  target: EvaluationDatapointTarget
130
130
  executor_output: ExecutorFunctionReturnType
131
131
  scores: dict[str, Numeric]
132
- human_evaluators: dict[str, HumanEvaluator] = pydantic.Field(default_factory=dict)
132
+ human_evaluators: list[HumanEvaluator] = pydantic.Field(default_factory=list)
133
133
  trace_id: uuid.UUID
134
134
  executor_span_id: uuid.UUID
135
135
 
@@ -6,6 +6,7 @@ SPAN_OUTPUT = "lmnr.span.output"
6
6
  SPAN_TYPE = "lmnr.span.type"
7
7
  SPAN_PATH = "lmnr.span.path"
8
8
  SPAN_INSTRUMENTATION_SOURCE = "lmnr.span.instrumentation_source"
9
+ OVERRIDE_PARENT_SPAN = "lmnr.internal.override_parent_span"
9
10
 
10
11
  ASSOCIATION_PROPERTIES = "lmnr.association.properties"
11
12
  SESSION_ID = "session_id"
@@ -184,8 +184,6 @@ class TracerWrapper(object):
184
184
  def set_association_properties(properties: dict) -> None:
185
185
  attach(set_value("association_properties", properties))
186
186
 
187
- # TODO: When called inside observe decorator, this actually sets the properties on the parent span, not the current one
188
- # Then, processor's on_start will assign this to current span
189
187
  span = trace.get_current_span()
190
188
  _set_association_properties_attributes(span, properties)
191
189
 
@@ -197,8 +195,6 @@ def update_association_properties(properties: dict) -> None:
197
195
 
198
196
  attach(set_value("association_properties", association_properties))
199
197
 
200
- # TODO: When called inside observe decorator, this actually sets the properties on the parent span, not the current one
201
- # Then, processor's on_start will assign this to current span
202
198
  span = trace.get_current_span()
203
199
  _set_association_properties_attributes(span, properties)
204
200
 
@@ -229,15 +225,6 @@ def set_managed_prompt_tracing_context(
229
225
  attach(set_value("prompt_template_variables", template_variables))
230
226
 
231
227
 
232
- def set_external_prompt_tracing_context(
233
- template: str, variables: dict, version: int
234
- ) -> None:
235
- attach(set_value("managed_prompt", False))
236
- attach(set_value("prompt_version", version))
237
- attach(set_value("prompt_template", template))
238
- attach(set_value("prompt_template_variables", variables))
239
-
240
-
241
228
  def init_spans_exporter(api_endpoint: str, headers: Dict[str, str]) -> SpanExporter:
242
229
  if "http" in api_endpoint.lower() or "https" in api_endpoint.lower():
243
230
  return HTTPExporter(endpoint=f"{api_endpoint}/v1/traces", headers=headers)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lmnr
3
- Version: 0.4.29b3
3
+ Version: 0.4.30
4
4
  Summary: Python SDK for Laminar AI
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -1,12 +1,12 @@
1
- lmnr/__init__.py,sha256=qwI8S02jRm7QvXsyljuEurp-kUt8HOCAN_m9RKVQVtU,389
1
+ lmnr/__init__.py,sha256=bIkXw003GaigTxNfGE_gwVtTDGdSdAPUr6CS_nN51oA,430
2
2
  lmnr/cli.py,sha256=Ptvm5dsNLKUY5lwnN8XkT5GtCYjzpRNi2WvefknB3OQ,1079
3
3
  lmnr/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  lmnr/sdk/datasets.py,sha256=w8U9E6fvetAo65Cb2CbYzlfhY8CfXAR-VysrakG6-4I,1591
5
5
  lmnr/sdk/decorators.py,sha256=ZSDaEZyjo-RUzRCltsNbe6x0t9SKl2xRQ2q4uaKvXtk,2250
6
6
  lmnr/sdk/evaluations.py,sha256=5Vfyp0aIjuGpqfuM3cqsaaLpcoO7z6lcOOKxnyHCNHk,16264
7
- lmnr/sdk/laminar.py,sha256=H87fXSWb9shcPW4AeoYwvTXJ-jSTjzm2sI1A1U1Vkg8,18780
7
+ lmnr/sdk/laminar.py,sha256=e5N3CuPN8MjdAxKr4YNaGQP9sWiumKMTvCzmw8kY_e0,23477
8
8
  lmnr/sdk/log.py,sha256=cZBeUoSK39LMEV-X4-eEhTWOciULRfHaKfRK8YqIM8I,1532
9
- lmnr/sdk/types.py,sha256=CHbKYnEkiwsEU3Fcnoz4tDawrjII2RLYhP6hzc3-t_M,5593
9
+ lmnr/sdk/types.py,sha256=qGD1tkGszd-_sZJaZ_Zx9U_CdUYzoDkUeN2g-o48Gls,5588
10
10
  lmnr/sdk/utils.py,sha256=Uk8y15x-sd5tP2ERONahElLDJVEy_3dA_1_5g9A6auY,3358
11
11
  lmnr/traceloop_sdk/.flake8,sha256=bCxuDlGx3YQ55QHKPiGJkncHanh9qGjQJUujcFa3lAU,150
12
12
  lmnr/traceloop_sdk/.python-version,sha256=9OLQBQVbD4zE4cJsPePhnAfV_snrPSoqEQw-PXgPMOs,6
@@ -36,17 +36,17 @@ lmnr/traceloop_sdk/tests/test_sdk_initialization.py,sha256=fRaf6lrxFzJIN94P1Tav_
36
36
  lmnr/traceloop_sdk/tests/test_tasks.py,sha256=xlEx8BKp4yG83SCjK5WkPGfyC33JSrx4h8VyjVwGbgw,906
37
37
  lmnr/traceloop_sdk/tests/test_workflows.py,sha256=RVcfY3WAFIDZC15-aSua21aoQyYeWE7KypDyUsm-2EM,9372
38
38
  lmnr/traceloop_sdk/tracing/__init__.py,sha256=Ckq7zCM26VdJVB5tIZv0GTPyMZKyfso_KWD5yPHaqdo,66
39
- lmnr/traceloop_sdk/tracing/attributes.py,sha256=QeqItpCCwUipkwgXG7J7swJCD0yk9uuI28aepPhemtE,1201
39
+ lmnr/traceloop_sdk/tracing/attributes.py,sha256=h970zmb7yTszzf2oHBfOY3cDYhE6O7LhkiHLqa_7x1k,1261
40
40
  lmnr/traceloop_sdk/tracing/content_allow_list.py,sha256=3feztm6PBWNelc8pAZUcQyEGyeSpNiVKjOaDk65l2ps,846
41
41
  lmnr/traceloop_sdk/tracing/context_manager.py,sha256=csVlB6kDmbgSPsROHwnddvGGblx55v6lJMRj0wsSMQM,304
42
- lmnr/traceloop_sdk/tracing/tracing.py,sha256=hFqTp7OGMCZnfxVtrmRq3wP3Ph5YvHq3fz9p00_Yk-Q,29433
42
+ lmnr/traceloop_sdk/tracing/tracing.py,sha256=Wo3ooDmuUiiqIA7ULpDCjxP6Z5UFu7Ai5ViSFLwYV8Q,28741
43
43
  lmnr/traceloop_sdk/utils/__init__.py,sha256=pNhf0G3vTd5ccoc03i1MXDbricSaiqCbi1DLWhSekK8,604
44
44
  lmnr/traceloop_sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
45
45
  lmnr/traceloop_sdk/utils/json_encoder.py,sha256=dK6b_axr70IYL7Vv-bu4wntvDDuyntoqsHaddqX7P58,463
46
46
  lmnr/traceloop_sdk/utils/package_check.py,sha256=TZSngzJOpFhfUZLXIs38cpMxQiZSmp0D-sCrIyhz7BA,251
47
47
  lmnr/traceloop_sdk/version.py,sha256=OlatFEFA4ttqSSIiV8jdE-sq3KG5zu2hnC4B4mzWF3s,23
48
- lmnr-0.4.29b3.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
49
- lmnr-0.4.29b3.dist-info/METADATA,sha256=UQ97DYAQ772h0XegYk-od_sdoOTnD_hTM-M-wX5TWLQ,10690
50
- lmnr-0.4.29b3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
51
- lmnr-0.4.29b3.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
52
- lmnr-0.4.29b3.dist-info/RECORD,,
48
+ lmnr-0.4.30.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
49
+ lmnr-0.4.30.dist-info/METADATA,sha256=2IrMNi89yZnA1F4534jSQXD-YSOdHUUsJVeS3C_fRSY,10688
50
+ lmnr-0.4.30.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
51
+ lmnr-0.4.30.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
52
+ lmnr-0.4.30.dist-info/RECORD,,
File without changes