lmnr 0.4.8__py3-none-any.whl → 0.4.10__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
@@ -2,3 +2,4 @@ from .sdk.evaluations import Evaluation
2
2
  from .sdk.laminar import Laminar
3
3
  from .sdk.types import ChatMessage, PipelineRunError, PipelineRunResponse, NodeInput
4
4
  from .sdk.decorators import observe
5
+ from .traceloop_sdk import Instruments
lmnr/sdk/decorators.py CHANGED
@@ -3,11 +3,11 @@ from lmnr.traceloop_sdk.decorators.base import (
3
3
  aentity_method,
4
4
  )
5
5
  from opentelemetry.trace import INVALID_SPAN, get_current_span
6
- from lmnr.traceloop_sdk import Traceloop
7
6
 
8
7
  from typing import Callable, Optional, ParamSpec, TypeVar, cast
9
8
 
10
- from .laminar import Laminar as L
9
+ from lmnr.traceloop_sdk.tracing.tracing import update_association_properties
10
+
11
11
  from .utils import is_async
12
12
 
13
13
  P = ParamSpec("P")
@@ -57,7 +57,7 @@ def observe(
57
57
  association_properties["session_id"] = session_id
58
58
  if user_id is not None:
59
59
  association_properties["user_id"] = user_id
60
- Traceloop.set_association_properties(association_properties)
60
+ update_association_properties(association_properties)
61
61
  return (
62
62
  aentity_method(name=name)(func)
63
63
  if is_async(func)
lmnr/sdk/laminar.py CHANGED
@@ -1,9 +1,8 @@
1
+ from lmnr.traceloop_sdk.instruments import Instruments
1
2
  from opentelemetry import context
2
3
  from opentelemetry.trace import (
3
4
  INVALID_SPAN,
4
5
  get_current_span,
5
- set_span_in_context,
6
- Span,
7
6
  SpanKind,
8
7
  )
9
8
  from opentelemetry.semconv_ai import SpanAttributes
@@ -16,7 +15,7 @@ from contextlib import contextmanager
16
15
  from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
17
16
 
18
17
  from pydantic.alias_generators import to_snake
19
- from typing import Any, Optional, Union
18
+ from typing import Any, Optional, Set, Union
20
19
 
21
20
  import copy
22
21
  import datetime
@@ -27,6 +26,8 @@ import os
27
26
  import requests
28
27
  import uuid
29
28
 
29
+ from lmnr.traceloop_sdk.tracing.tracing import set_association_properties, update_association_properties
30
+
30
31
  from .log import VerboseColorfulFormatter
31
32
 
32
33
  from .types import (
@@ -51,6 +52,7 @@ class Laminar:
51
52
  project_api_key: Optional[str] = None,
52
53
  env: dict[str, str] = {},
53
54
  base_url: Optional[str] = None,
55
+ instruments: Optional[Set[Instruments]] = None,
54
56
  ):
55
57
  """Initialize Laminar context across the application.
56
58
  This method must be called before using any other Laminar methods or
@@ -104,6 +106,7 @@ class Laminar:
104
106
  endpoint=cls.__base_url,
105
107
  headers={"authorization": f"Bearer {cls.__project_api_key}"},
106
108
  ),
109
+ instruments=instruments,
107
110
  )
108
111
 
109
112
  @classmethod
@@ -354,50 +357,15 @@ class Laminar:
354
357
  yield span
355
358
 
356
359
  @classmethod
357
- def start_span(
358
- cls,
359
- name: str,
360
- input: Any = None,
361
- ) -> Span:
362
- """Start a new span with the given name. Useful for manual
363
- instrumentation.
364
-
365
- Args:
366
- name (str): name of the span
367
- input (Any, optional): input to the span. Will be sent as an
368
- attribute, so must be json serializable. Defaults to None.
369
-
370
- Returns:
371
- Tuple[Span, object]: Span - the started span, object -
372
- context token
373
- that must be passed to `end_span` to end the span.
374
-
375
- """
376
- tracer = get_tracer().__enter__()
377
- span = tracer.start_span(name)
378
- # apparently, detaching from this context is not mandatory.
379
- # According to traceloop, and the github issue in opentelemetry,
380
- # the context is collected by the garbage collector.
381
- # https://github.com/open-telemetry/opentelemetry-python/issues/2606#issuecomment-2106320379
382
- context.attach(set_span_in_context(span))
383
-
384
- if input is not None:
385
- span.set_attribute(
386
- SpanAttributes.TRACELOOP_ENTITY_INPUT, json.dumps({"input": input})
387
- )
388
-
389
- return span
390
-
391
- @classmethod
392
- def set_span_output(cls, span: Span, output: Any = None):
393
- """Set the output of the span. Useful for manual instrumentation.
360
+ def set_span_output(cls, output: Any = None):
361
+ """Set the output of the current span. Useful for manual instrumentation.
394
362
 
395
363
  Args:
396
- span (Span): the span to set the output for
397
364
  output (Any, optional): output of the span. Will be sent as an
398
365
  attribute, so must be json serializable. Defaults to None.
399
366
  """
400
- if output is not None:
367
+ span = get_current_span()
368
+ if output is not None and span != INVALID_SPAN:
401
369
  span.set_attribute(
402
370
  SpanAttributes.TRACELOOP_ENTITY_OUTPUT, json.dumps(output)
403
371
  )
@@ -421,26 +389,12 @@ class Laminar:
421
389
  Useful for grouping spans or traces by user.
422
390
  Defaults to None.
423
391
  """
424
- current_span = get_current_span()
425
- if current_span != INVALID_SPAN:
426
- cls.__logger.debug(
427
- "Laminar().set_session() called inside a span context. Setting"
428
- " it manually in the current span."
429
- )
430
- if session_id is not None:
431
- current_span.set_attribute(
432
- "traceloop.association.properties.session_id", session_id
433
- )
434
- if user_id is not None:
435
- current_span.set_attribute(
436
- "traceloop.association.properties.user_id", user_id
437
- )
438
392
  association_properties = {}
439
393
  if session_id is not None:
440
394
  association_properties["session_id"] = session_id
441
395
  if user_id is not None:
442
396
  association_properties["user_id"] = user_id
443
- Traceloop.set_association_properties(association_properties)
397
+ update_association_properties(association_properties)
444
398
 
445
399
  @classmethod
446
400
  def clear_session(cls):
@@ -448,7 +402,7 @@ class Laminar:
448
402
  props: dict = copy.copy(context.get_value("association_properties"))
449
403
  props.pop("session_id", None)
450
404
  props.pop("user_id", None)
451
- Traceloop.set_association_properties(props)
405
+ set_association_properties(props)
452
406
 
453
407
  @classmethod
454
408
  def create_evaluation(cls, name: str) -> CreateEvaluationResponse:
lmnr/sdk/types.py CHANGED
@@ -2,7 +2,7 @@ import datetime
2
2
  import requests
3
3
  import pydantic
4
4
  import uuid
5
- from typing import Any, Awaitable, Callable, Literal, Optional, TypeAlias, Union
5
+ from typing import Any, Literal, Optional, TypeAlias, Union
6
6
 
7
7
  from .utils import to_dict
8
8
 
@@ -18,11 +18,7 @@ from lmnr.traceloop_sdk.config import (
18
18
  is_tracing_enabled,
19
19
  is_metrics_enabled,
20
20
  )
21
- from lmnr.traceloop_sdk.tracing.tracing import (
22
- TracerWrapper,
23
- set_association_properties,
24
- set_external_prompt_tracing_context,
25
- )
21
+ from lmnr.traceloop_sdk.tracing.tracing import TracerWrapper
26
22
  from typing import Dict
27
23
 
28
24
 
@@ -38,14 +34,14 @@ class Traceloop:
38
34
  def init(
39
35
  app_name: Optional[str] = sys.argv[0],
40
36
  api_endpoint: str = "https://api.lmnr.ai",
41
- api_key: str = None,
37
+ api_key: Optional[str] = None,
42
38
  headers: Dict[str, str] = {},
43
39
  disable_batch=False,
44
- exporter: SpanExporter = None,
45
- metrics_exporter: MetricExporter = None,
46
- metrics_headers: Dict[str, str] = None,
47
- processor: SpanProcessor = None,
48
- propagator: TextMapPropagator = None,
40
+ exporter: Optional[SpanExporter] = None,
41
+ metrics_exporter: Optional[MetricExporter] = None,
42
+ metrics_headers: Optional[Dict[str, str]] = None,
43
+ processor: Optional[SpanProcessor] = None,
44
+ propagator: Optional[TextMapPropagator] = None,
49
45
  should_enrich_metrics: bool = True,
50
46
  resource_attributes: dict = {},
51
47
  instruments: Optional[Set[Instruments]] = None,
@@ -130,9 +126,3 @@ class Traceloop:
130
126
  )
131
127
 
132
128
  Traceloop.__metrics_wrapper = MetricsWrapper(exporter=metrics_exporter)
133
-
134
- def set_association_properties(properties: dict) -> None:
135
- set_association_properties(properties)
136
-
137
- def set_prompt(template: str, variables: dict, version: int):
138
- set_external_prompt_tracing_context(template, variables, version)
@@ -1,131 +0,0 @@
1
- from typing import Optional
2
-
3
- from opentelemetry.semconv_ai import TraceloopSpanKindValues
4
-
5
- from lmnr.traceloop_sdk.decorators.base import (
6
- aentity_class,
7
- aentity_method,
8
- entity_class,
9
- entity_method,
10
- )
11
-
12
-
13
- def task(
14
- name: Optional[str] = None,
15
- version: Optional[int] = None,
16
- method_name: Optional[str] = None,
17
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.TASK,
18
- ):
19
- if method_name is None:
20
- return entity_method(name=name, version=version, tlp_span_kind=tlp_span_kind)
21
- else:
22
- return entity_class(
23
- name=name,
24
- version=version,
25
- method_name=method_name,
26
- tlp_span_kind=tlp_span_kind,
27
- )
28
-
29
-
30
- def workflow(
31
- name: Optional[str] = None,
32
- version: Optional[int] = None,
33
- method_name: Optional[str] = None,
34
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.WORKFLOW,
35
- ):
36
- if method_name is None:
37
- return entity_method(name=name, version=version, tlp_span_kind=tlp_span_kind)
38
- else:
39
- return entity_class(
40
- name=name,
41
- version=version,
42
- method_name=method_name,
43
- tlp_span_kind=tlp_span_kind,
44
- )
45
-
46
-
47
- def agent(
48
- name: Optional[str] = None,
49
- version: Optional[int] = None,
50
- method_name: Optional[str] = None,
51
- ):
52
- return workflow(
53
- name=name,
54
- version=version,
55
- method_name=method_name,
56
- tlp_span_kind=TraceloopSpanKindValues.AGENT,
57
- )
58
-
59
-
60
- def tool(
61
- name: Optional[str] = None,
62
- version: Optional[int] = None,
63
- method_name: Optional[str] = None,
64
- ):
65
- return task(
66
- name=name,
67
- version=version,
68
- method_name=method_name,
69
- tlp_span_kind=TraceloopSpanKindValues.TOOL,
70
- )
71
-
72
-
73
- # Async Decorators
74
- def atask(
75
- name: Optional[str] = None,
76
- version: Optional[int] = None,
77
- method_name: Optional[str] = None,
78
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.TASK,
79
- ):
80
- if method_name is None:
81
- return aentity_method(name=name, version=version, tlp_span_kind=tlp_span_kind)
82
- else:
83
- return aentity_class(
84
- name=name,
85
- version=version,
86
- method_name=method_name,
87
- tlp_span_kind=tlp_span_kind,
88
- )
89
-
90
-
91
- def aworkflow(
92
- name: Optional[str] = None,
93
- version: Optional[int] = None,
94
- method_name: Optional[str] = None,
95
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.WORKFLOW,
96
- ):
97
- if method_name is None:
98
- return aentity_method(name=name, version=version, tlp_span_kind=tlp_span_kind)
99
- else:
100
- return aentity_class(
101
- name=name,
102
- version=version,
103
- method_name=method_name,
104
- tlp_span_kind=tlp_span_kind,
105
- )
106
-
107
-
108
- def aagent(
109
- name: Optional[str] = None,
110
- version: Optional[int] = None,
111
- method_name: Optional[str] = None,
112
- ):
113
- return atask(
114
- name=name,
115
- version=version,
116
- method_name=method_name,
117
- tlp_span_kind=TraceloopSpanKindValues.AGENT,
118
- )
119
-
120
-
121
- def atool(
122
- name: Optional[str] = None,
123
- version: Optional[int] = None,
124
- method_name: Optional[str] = None,
125
- ):
126
- return atask(
127
- name=name,
128
- version=version,
129
- method_name=method_name,
130
- tlp_span_kind=TraceloopSpanKindValues.TOOL,
131
- )
@@ -7,15 +7,10 @@ import warnings
7
7
 
8
8
  from opentelemetry import trace
9
9
  from opentelemetry import context as context_api
10
- from opentelemetry.semconv_ai import SpanAttributes, TraceloopSpanKindValues
11
-
12
- from lmnr.traceloop_sdk.tracing import get_tracer, set_workflow_name
13
- from lmnr.traceloop_sdk.tracing.tracing import (
14
- TracerWrapper,
15
- set_entity_path,
16
- get_chained_entity_path,
17
- )
18
- from lmnr.traceloop_sdk.utils import camel_to_snake
10
+ from opentelemetry.semconv_ai import SpanAttributes
11
+
12
+ from lmnr.traceloop_sdk.tracing import get_tracer
13
+ from lmnr.traceloop_sdk.tracing.tracing import TracerWrapper
19
14
  from lmnr.traceloop_sdk.utils.json_encoder import JSONEncoder
20
15
 
21
16
 
@@ -40,8 +35,6 @@ def _json_dumps(data: dict) -> str:
40
35
 
41
36
  def entity_method(
42
37
  name: Optional[str] = None,
43
- version: Optional[int] = None,
44
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.TASK,
45
38
  ):
46
39
  def decorate(fn):
47
40
  @wraps(fn)
@@ -49,33 +42,13 @@ def entity_method(
49
42
  if not TracerWrapper.verify_initialized():
50
43
  return fn(*args, **kwargs)
51
44
 
52
- entity_name = name or fn.__name__
53
- if tlp_span_kind in [
54
- TraceloopSpanKindValues.WORKFLOW,
55
- TraceloopSpanKindValues.AGENT,
56
- ]:
57
- set_workflow_name(entity_name)
58
- span_name = f"{entity_name}.{tlp_span_kind.value}"
45
+ span_name = name or fn.__name__
59
46
 
60
47
  with get_tracer() as tracer:
61
48
  span = tracer.start_span(span_name)
62
49
  ctx = trace.set_span_in_context(span)
63
50
  ctx_token = context_api.attach(ctx)
64
51
 
65
- if tlp_span_kind in [
66
- TraceloopSpanKindValues.TASK,
67
- TraceloopSpanKindValues.TOOL,
68
- ]:
69
- entity_path = get_chained_entity_path(entity_name)
70
- set_entity_path(entity_path)
71
-
72
- span.set_attribute(
73
- SpanAttributes.TRACELOOP_SPAN_KIND, tlp_span_kind.value
74
- )
75
- span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_NAME, entity_name)
76
- if version:
77
- span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_VERSION, version)
78
-
79
52
  try:
80
53
  if _should_send_prompts():
81
54
  span.set_attribute(
@@ -110,69 +83,25 @@ def entity_method(
110
83
  return decorate
111
84
 
112
85
 
113
- def entity_class(
114
- name: Optional[str],
115
- version: Optional[int],
116
- method_name: str,
117
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.TASK,
118
- ):
119
- def decorator(cls):
120
- task_name = name if name else camel_to_snake(cls.__name__)
121
- method = getattr(cls, method_name)
122
- setattr(
123
- cls,
124
- method_name,
125
- entity_method(name=task_name, version=version, tlp_span_kind=tlp_span_kind)(
126
- method
127
- ),
128
- )
129
- return cls
130
-
131
- return decorator
132
-
133
-
134
86
  # Async Decorators
135
87
 
136
88
 
137
89
  def aentity_method(
138
90
  name: Optional[str] = None,
139
- version: Optional[int] = None,
140
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.TASK,
141
91
  ):
142
92
  def decorate(fn):
143
93
  @wraps(fn)
144
94
  async def wrap(*args, **kwargs):
145
95
  if not TracerWrapper.verify_initialized():
146
- print("Tracer not initialized")
147
96
  return await fn(*args, **kwargs)
148
97
 
149
- entity_name = name or fn.__name__
150
- if tlp_span_kind in [
151
- TraceloopSpanKindValues.WORKFLOW,
152
- TraceloopSpanKindValues.AGENT,
153
- ]:
154
- set_workflow_name(entity_name)
155
- span_name = f"{entity_name}.{tlp_span_kind.value}"
98
+ span_name = name or fn.__name__
156
99
 
157
100
  with get_tracer() as tracer:
158
101
  span = tracer.start_span(span_name)
159
102
  ctx = trace.set_span_in_context(span)
160
103
  ctx_token = context_api.attach(ctx)
161
104
 
162
- if tlp_span_kind in [
163
- TraceloopSpanKindValues.TASK,
164
- TraceloopSpanKindValues.TOOL,
165
- ]:
166
- entity_path = get_chained_entity_path(entity_name)
167
- set_entity_path(entity_path)
168
-
169
- span.set_attribute(
170
- SpanAttributes.TRACELOOP_SPAN_KIND, tlp_span_kind.value
171
- )
172
- span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_NAME, entity_name)
173
- if version:
174
- span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_VERSION, version)
175
-
176
105
  try:
177
106
  if _should_send_prompts():
178
107
  span.set_attribute(
@@ -206,27 +135,6 @@ def aentity_method(
206
135
  return decorate
207
136
 
208
137
 
209
- def aentity_class(
210
- name: Optional[str],
211
- version: Optional[int],
212
- method_name: str,
213
- tlp_span_kind: Optional[TraceloopSpanKindValues] = TraceloopSpanKindValues.TASK,
214
- ):
215
- def decorator(cls):
216
- task_name = name if name else camel_to_snake(cls.__name__)
217
- method = getattr(cls, method_name)
218
- setattr(
219
- cls,
220
- method_name,
221
- aentity_method(
222
- name=task_name, version=version, tlp_span_kind=tlp_span_kind
223
- )(method),
224
- )
225
- return cls
226
-
227
- return decorator
228
-
229
-
230
138
  def _handle_generator(span, res):
231
139
  # for some reason the SPAN_KEY is not being set in the context of the generator, so we re-set it
232
140
  context_api.attach(trace.set_span_in_context(span))
@@ -1 +1 @@
1
- """unit tests."""
1
+ # """unit tests."""