lmnr 0.4.4__tar.gz → 0.4.6__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lmnr
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Python SDK for Laminar AI
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -89,6 +89,44 @@ def poem_writer(topic="turbulence"):
89
89
  print(poem_writer(topic="laminar flow"))
90
90
  ```
91
91
 
92
+ ### Manual instrumentation
93
+
94
+ Our manual instrumentation is a very thin wrapper around OpenTelemetry's
95
+ `trace.start_span`. Our wrapper sets the span into the active context.
96
+ You don't have to explicitly pass the spans around, it is enough to
97
+ just call `L.start_span`, and OpenTelemetry will handle the context management
98
+
99
+ ```python
100
+ from lmnr import observe, Laminar as L
101
+ L.initialize(project_api_key="<LMNR_PROJECT_API_KEY>")
102
+
103
+ def poem_writer(topic="turbulence"):
104
+
105
+ span = L.start_span("poem_writer", topic) # start a span
106
+
107
+ prompt = f"write a poem about {topic}"
108
+
109
+ # OpenAI calls are still automatically instrumented with OpenLLMetry
110
+ response = client.chat.completions.create(
111
+ model="gpt-4o",
112
+ messages=[
113
+ {"role": "system", "content": "You are a helpful assistant."},
114
+ {"role": "user", "content": prompt},
115
+ ],
116
+ )
117
+ poem = response.choices[0].message.content
118
+ # while within the span, you can attach laminar events to it
119
+ L.event("event_name", "event_value")
120
+
121
+ L.set_span_output(span, poem) # set an output
122
+
123
+ # IMPORTANT: don't forget to end all the spans (usually in `finally` blocks)
124
+ # Otherwise, the trace may not be sent/displayed correctly
125
+ span.end()
126
+
127
+ return poem
128
+ ```
129
+
92
130
 
93
131
  ## Sending events
94
132
 
@@ -68,6 +68,44 @@ def poem_writer(topic="turbulence"):
68
68
  print(poem_writer(topic="laminar flow"))
69
69
  ```
70
70
 
71
+ ### Manual instrumentation
72
+
73
+ Our manual instrumentation is a very thin wrapper around OpenTelemetry's
74
+ `trace.start_span`. Our wrapper sets the span into the active context.
75
+ You don't have to explicitly pass the spans around, it is enough to
76
+ just call `L.start_span`, and OpenTelemetry will handle the context management
77
+
78
+ ```python
79
+ from lmnr import observe, Laminar as L
80
+ L.initialize(project_api_key="<LMNR_PROJECT_API_KEY>")
81
+
82
+ def poem_writer(topic="turbulence"):
83
+
84
+ span = L.start_span("poem_writer", topic) # start a span
85
+
86
+ prompt = f"write a poem about {topic}"
87
+
88
+ # OpenAI calls are still automatically instrumented with OpenLLMetry
89
+ response = client.chat.completions.create(
90
+ model="gpt-4o",
91
+ messages=[
92
+ {"role": "system", "content": "You are a helpful assistant."},
93
+ {"role": "user", "content": prompt},
94
+ ],
95
+ )
96
+ poem = response.choices[0].message.content
97
+ # while within the span, you can attach laminar events to it
98
+ L.event("event_name", "event_value")
99
+
100
+ L.set_span_output(span, poem) # set an output
101
+
102
+ # IMPORTANT: don't forget to end all the spans (usually in `finally` blocks)
103
+ # Otherwise, the trace may not be sent/displayed correctly
104
+ span.end()
105
+
106
+ return poem
107
+ ```
108
+
71
109
 
72
110
  ## Sending events
73
111
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lmnr"
3
- version = "0.4.4"
3
+ version = "0.4.6"
4
4
  description = "Python SDK for Laminar AI"
5
5
  authors = [
6
6
  { name = "lmnr.ai", email = "founders@lmnr.ai" }
@@ -11,7 +11,7 @@ license = "Apache-2.0"
11
11
 
12
12
  [tool.poetry]
13
13
  name = "lmnr"
14
- version = "0.4.4"
14
+ version = "0.4.6"
15
15
  description = "Python SDK for Laminar AI"
16
16
  authors = ["lmnr.ai"]
17
17
  readme = "README.md"
@@ -9,9 +9,10 @@ from opentelemetry.semconv_ai import SpanAttributes
9
9
  from opentelemetry.util.types import AttributeValue
10
10
  from traceloop.sdk import Traceloop
11
11
  from traceloop.sdk.tracing import get_tracer
12
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
12
13
 
13
14
  from pydantic.alias_generators import to_snake
14
- from typing import Any, Optional, Tuple, Union
15
+ from typing import Any, Optional, Union
15
16
 
16
17
  import copy
17
18
  import datetime
@@ -35,7 +36,7 @@ from .types import (
35
36
 
36
37
 
37
38
  class Laminar:
38
- __base_url: str = "https://api.lmnr.ai"
39
+ __base_url: str = "https://api.lmnr.ai:8443"
39
40
  __project_api_key: Optional[str] = None
40
41
  __env: dict[str, str] = {}
41
42
  __initialized: bool = False
@@ -67,9 +68,9 @@ class Laminar:
67
68
  base_url (Optional[str], optional): Url of Laminar endpoint,
68
69
  or the customopen telemetry ingester.
69
70
  If not specified, defaults to
70
- https://api.lmnr.ai.
71
+ https://api.lmnr.ai:8443.
71
72
  For locally hosted Laminar, default setting
72
- must be http://localhost:8000
73
+ must be http://localhost:8001
73
74
  Defaults to None.
74
75
 
75
76
  Raises:
@@ -97,6 +98,10 @@ class Laminar:
97
98
  Traceloop.init(
98
99
  api_endpoint=cls.__base_url,
99
100
  api_key=cls.__project_api_key,
101
+ exporter=OTLPSpanExporter(
102
+ endpoint=cls.__base_url,
103
+ headers={"authorization": f"Bearer {cls.__project_api_key}"},
104
+ ),
100
105
  )
101
106
 
102
107
  @classmethod
@@ -247,7 +252,7 @@ class Laminar:
247
252
  name: str,
248
253
  evaluator: str,
249
254
  data: dict[str, AttributeValue],
250
- env: Optional[dict[str, str]] = {},
255
+ env: Optional[dict[str, str]] = None,
251
256
  timestamp: Optional[Union[datetime.datetime, int]] = None,
252
257
  ):
253
258
  """Send an event for evaluation to the Laminar backend
@@ -289,7 +294,7 @@ class Laminar:
289
294
  cls,
290
295
  name: str,
291
296
  input: Any = None,
292
- ) -> Tuple[Span, object]:
297
+ ) -> Span:
293
298
  """Start a new span with the given name. Useful for manual
294
299
  instrumentation.
295
300
 
@@ -304,33 +309,34 @@ class Laminar:
304
309
  that must be passed to `end_span` to end the span.
305
310
 
306
311
  """
307
- with get_tracer() as tracer:
308
- span = tracer.start_span(name)
309
- ctx = set_span_in_context(span)
310
- token = context.attach(ctx)
311
- span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_NAME, name)
312
- if input is not None:
313
- span.set_attribute(
314
- SpanAttributes.TRACELOOP_ENTITY_INPUT, json.dumps({"input": input})
315
- )
316
- return (span, token)
312
+ tracer = get_tracer().__enter__()
313
+ span = tracer.start_span(name)
314
+ # apparently, detaching from this context is not mandatory.
315
+ # According to traceloop, and the github issue in opentelemetry,
316
+ # the context is collected by the garbage collector.
317
+ # https://github.com/open-telemetry/opentelemetry-python/issues/2606#issuecomment-2106320379
318
+ context.attach(set_span_in_context(span))
319
+
320
+ if input is not None:
321
+ span.set_attribute(
322
+ SpanAttributes.TRACELOOP_ENTITY_INPUT, json.dumps({"input": input})
323
+ )
324
+
325
+ return span
317
326
 
318
327
  @classmethod
319
- def end_span(cls, span: Span, token: object, output: Any = None):
320
- """End the span started with `start_span`
328
+ def set_span_output(cls, span: Span, output: Any = None):
329
+ """Set the output of the span. Useful for manual instrumentation.
321
330
 
322
331
  Args:
323
- span (Span): span returned by `start_span`
324
- token (object): context token returned by `start_span`
332
+ span (Span): the span to set the output for
325
333
  output (Any, optional): output of the span. Will be sent as an
326
334
  attribute, so must be json serializable. Defaults to None.
327
335
  """
328
336
  if output is not None:
329
337
  span.set_attribute(
330
- SpanAttributes.TRACELOOP_ENTITY_OUTPUT, json.dumps({"output": output})
338
+ SpanAttributes.TRACELOOP_ENTITY_OUTPUT, json.dumps(output)
331
339
  )
332
- span.end()
333
- context.detach(token)
334
340
 
335
341
  @classmethod
336
342
  def set_session(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes