lmnr 0.6.16__py3-none-any.whl → 0.7.26__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 +6 -15
- lmnr/cli/__init__.py +270 -0
- lmnr/cli/datasets.py +371 -0
- lmnr/{cli.py → cli/evals.py} +20 -102
- lmnr/cli/rules.py +42 -0
- lmnr/opentelemetry_lib/__init__.py +9 -2
- lmnr/opentelemetry_lib/decorators/__init__.py +274 -168
- lmnr/opentelemetry_lib/litellm/__init__.py +352 -38
- lmnr/opentelemetry_lib/litellm/utils.py +82 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +849 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +401 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +425 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +332 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/__init__.py +451 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/proxy.py +144 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +476 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +12 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +191 -129
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +26 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +126 -41
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +488 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/__init__.py +381 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/utils.py +36 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +16 -16
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +61 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +472 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1185 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +305 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +16 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +312 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +197 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +368 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +325 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +135 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +786 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py +388 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +69 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +59 -61
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +197 -0
- lmnr/opentelemetry_lib/tracing/__init__.py +119 -18
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +124 -25
- lmnr/opentelemetry_lib/tracing/attributes.py +4 -0
- lmnr/opentelemetry_lib/tracing/context.py +200 -0
- lmnr/opentelemetry_lib/tracing/exporter.py +109 -15
- lmnr/opentelemetry_lib/tracing/instruments.py +22 -5
- lmnr/opentelemetry_lib/tracing/processor.py +128 -30
- lmnr/opentelemetry_lib/tracing/span.py +398 -0
- lmnr/opentelemetry_lib/tracing/tracer.py +40 -1
- lmnr/opentelemetry_lib/tracing/utils.py +62 -0
- lmnr/opentelemetry_lib/utils/package_check.py +9 -0
- lmnr/opentelemetry_lib/utils/wrappers.py +11 -0
- lmnr/sdk/browser/background_send_events.py +158 -0
- lmnr/sdk/browser/browser_use_cdp_otel.py +100 -0
- lmnr/sdk/browser/browser_use_otel.py +12 -12
- lmnr/sdk/browser/bubus_otel.py +71 -0
- lmnr/sdk/browser/cdp_utils.py +518 -0
- lmnr/sdk/browser/inject_script.js +514 -0
- lmnr/sdk/browser/patchright_otel.py +18 -44
- lmnr/sdk/browser/playwright_otel.py +104 -187
- lmnr/sdk/browser/pw_utils.py +249 -210
- lmnr/sdk/browser/recorder/record.umd.min.cjs +84 -0
- lmnr/sdk/browser/utils.py +1 -1
- lmnr/sdk/client/asynchronous/async_client.py +47 -15
- lmnr/sdk/client/asynchronous/resources/__init__.py +2 -7
- lmnr/sdk/client/asynchronous/resources/browser_events.py +1 -0
- lmnr/sdk/client/asynchronous/resources/datasets.py +131 -0
- lmnr/sdk/client/asynchronous/resources/evals.py +122 -18
- lmnr/sdk/client/asynchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/asynchronous/resources/tags.py +4 -10
- lmnr/sdk/client/synchronous/resources/__init__.py +2 -2
- lmnr/sdk/client/synchronous/resources/datasets.py +131 -0
- lmnr/sdk/client/synchronous/resources/evals.py +83 -17
- lmnr/sdk/client/synchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/synchronous/resources/tags.py +4 -10
- lmnr/sdk/client/synchronous/sync_client.py +47 -15
- lmnr/sdk/datasets/__init__.py +94 -0
- lmnr/sdk/datasets/file_utils.py +91 -0
- lmnr/sdk/decorators.py +103 -23
- lmnr/sdk/evaluations.py +122 -33
- lmnr/sdk/laminar.py +816 -333
- lmnr/sdk/log.py +7 -2
- lmnr/sdk/types.py +124 -143
- lmnr/sdk/utils.py +115 -2
- lmnr/version.py +1 -1
- {lmnr-0.6.16.dist-info → lmnr-0.7.26.dist-info}/METADATA +71 -78
- lmnr-0.7.26.dist-info/RECORD +116 -0
- lmnr-0.7.26.dist-info/WHEEL +4 -0
- lmnr-0.7.26.dist-info/entry_points.txt +3 -0
- lmnr/opentelemetry_lib/tracing/context_properties.py +0 -65
- lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +0 -98
- lmnr/sdk/client/asynchronous/resources/agent.py +0 -329
- lmnr/sdk/client/synchronous/resources/agent.py +0 -323
- lmnr/sdk/datasets.py +0 -60
- lmnr-0.6.16.dist-info/LICENSE +0 -75
- lmnr-0.6.16.dist-info/RECORD +0 -61
- lmnr-0.6.16.dist-info/WHEEL +0 -4
- lmnr-0.6.16.dist-info/entry_points.txt +0 -3
lmnr/sdk/laminar.py
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
from contextlib import contextmanager
|
|
2
2
|
from contextvars import Context
|
|
3
3
|
import warnings
|
|
4
|
-
from typing_extensions import deprecated
|
|
5
4
|
from lmnr.opentelemetry_lib import TracerManager
|
|
6
|
-
from lmnr.opentelemetry_lib.tracing
|
|
7
|
-
from lmnr.opentelemetry_lib.tracing.
|
|
5
|
+
from lmnr.opentelemetry_lib.tracing import TracerWrapper, get_current_context
|
|
6
|
+
from lmnr.opentelemetry_lib.tracing.context import (
|
|
7
|
+
CONTEXT_METADATA_KEY,
|
|
8
|
+
CONTEXT_SESSION_ID_KEY,
|
|
9
|
+
CONTEXT_TRACE_TYPE_KEY,
|
|
10
|
+
CONTEXT_USER_ID_KEY,
|
|
11
|
+
attach_context,
|
|
12
|
+
detach_context,
|
|
13
|
+
get_event_attributes_from_context,
|
|
14
|
+
push_span_context,
|
|
15
|
+
set_association_prop_context,
|
|
16
|
+
)
|
|
17
|
+
from opentelemetry.context import get_value
|
|
8
18
|
from lmnr.opentelemetry_lib.tracing.attributes import (
|
|
9
19
|
ASSOCIATION_PROPERTIES,
|
|
20
|
+
PARENT_SPAN_IDS_PATH,
|
|
21
|
+
PARENT_SPAN_PATH,
|
|
10
22
|
USER_ID,
|
|
11
23
|
Attributes,
|
|
12
24
|
SPAN_TYPE,
|
|
13
25
|
)
|
|
14
|
-
from lmnr.opentelemetry_lib import
|
|
15
|
-
from lmnr.opentelemetry_lib.
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
26
|
+
from lmnr.opentelemetry_lib.tracing.instruments import Instruments
|
|
27
|
+
from lmnr.opentelemetry_lib.tracing.processor import LaminarSpanProcessor
|
|
28
|
+
from lmnr.opentelemetry_lib.tracing.span import LaminarSpan
|
|
29
|
+
from lmnr.opentelemetry_lib.tracing.tracer import get_tracer_with_context
|
|
30
|
+
from lmnr.opentelemetry_lib.tracing.utils import set_association_props_in_context
|
|
31
|
+
from lmnr.sdk.utils import get_otel_env_var
|
|
32
|
+
|
|
33
|
+
from opentelemetry import trace
|
|
34
|
+
from opentelemetry import context as context_api
|
|
35
|
+
from opentelemetry.trace import INVALID_TRACE_ID, Span, Status, StatusCode, use_span
|
|
19
36
|
from opentelemetry.sdk.trace.id_generator import RandomIdGenerator
|
|
20
37
|
from opentelemetry.util.types import AttributeValue
|
|
21
38
|
|
|
22
|
-
from typing import Any, Literal
|
|
39
|
+
from typing import Any, Iterator, Literal
|
|
40
|
+
from typing_extensions import TypedDict
|
|
23
41
|
|
|
24
42
|
import datetime
|
|
25
43
|
import logging
|
|
@@ -27,48 +45,150 @@ import os
|
|
|
27
45
|
import re
|
|
28
46
|
import uuid
|
|
29
47
|
|
|
30
|
-
from lmnr.opentelemetry_lib.tracing.attributes import
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
SPAN_OUTPUT,
|
|
34
|
-
TRACE_TYPE,
|
|
35
|
-
)
|
|
36
|
-
from lmnr.opentelemetry_lib.tracing.context_properties import (
|
|
37
|
-
get_association_properties,
|
|
38
|
-
remove_association_properties,
|
|
39
|
-
set_association_properties,
|
|
40
|
-
update_association_properties,
|
|
41
|
-
)
|
|
42
|
-
from lmnr.sdk.utils import from_env, is_otel_attribute_value_type
|
|
48
|
+
from lmnr.opentelemetry_lib.tracing.attributes import SESSION_ID, TRACE_TYPE
|
|
49
|
+
|
|
50
|
+
from lmnr.sdk.utils import from_env, is_otel_attribute_value_type, json_dumps
|
|
43
51
|
|
|
44
52
|
from .log import VerboseColorfulFormatter
|
|
45
53
|
|
|
46
54
|
from .types import (
|
|
47
55
|
LaminarSpanContext,
|
|
56
|
+
SessionRecordingOptions,
|
|
48
57
|
TraceType,
|
|
49
|
-
TracingLevel,
|
|
50
58
|
)
|
|
51
59
|
|
|
52
60
|
|
|
61
|
+
class ParsedParentSpanContext(TypedDict):
|
|
62
|
+
"""Parsed information from a parent span context."""
|
|
63
|
+
|
|
64
|
+
otel_span_context: trace.SpanContext | None
|
|
65
|
+
path: list[str]
|
|
66
|
+
span_ids_path: list[str]
|
|
67
|
+
user_id: str | None
|
|
68
|
+
session_id: str | None
|
|
69
|
+
trace_type: TraceType | None
|
|
70
|
+
metadata: dict[str, Any] | None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _parse_parent_span_context(
|
|
74
|
+
parent_span_context: LaminarSpanContext | dict | str | None,
|
|
75
|
+
logger: logging.Logger,
|
|
76
|
+
) -> ParsedParentSpanContext:
|
|
77
|
+
"""Parse parent_span_context and extract all relevant information.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
parent_span_context: Parent span context to parse
|
|
81
|
+
logger: Logger for warnings
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
ParsedParentSpanContext with otel_span_context, path, span_ids_path,
|
|
85
|
+
user_id, session_id, trace_type, and metadata
|
|
86
|
+
"""
|
|
87
|
+
if parent_span_context is None:
|
|
88
|
+
return ParsedParentSpanContext(
|
|
89
|
+
otel_span_context=None,
|
|
90
|
+
path=[],
|
|
91
|
+
span_ids_path=[],
|
|
92
|
+
user_id=None,
|
|
93
|
+
session_id=None,
|
|
94
|
+
trace_type=None,
|
|
95
|
+
metadata=None,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
path = []
|
|
99
|
+
span_ids_path = []
|
|
100
|
+
user_id = None
|
|
101
|
+
session_id = None
|
|
102
|
+
trace_type = None
|
|
103
|
+
metadata = None
|
|
104
|
+
laminar_span_context = None
|
|
105
|
+
|
|
106
|
+
# Try to deserialize if dict or str
|
|
107
|
+
if isinstance(parent_span_context, (dict, str)):
|
|
108
|
+
try:
|
|
109
|
+
laminar_span_context = LaminarSpanContext.deserialize(parent_span_context)
|
|
110
|
+
except Exception:
|
|
111
|
+
logger.warning(
|
|
112
|
+
f"Could not deserialize parent_span_context: {parent_span_context}. "
|
|
113
|
+
"Will use it as is."
|
|
114
|
+
)
|
|
115
|
+
laminar_span_context = parent_span_context
|
|
116
|
+
else:
|
|
117
|
+
laminar_span_context = parent_span_context
|
|
118
|
+
|
|
119
|
+
# Extract path and association props from LaminarSpanContext
|
|
120
|
+
if isinstance(laminar_span_context, LaminarSpanContext):
|
|
121
|
+
path = laminar_span_context.span_path
|
|
122
|
+
span_ids_path = laminar_span_context.span_ids_path
|
|
123
|
+
user_id = laminar_span_context.user_id
|
|
124
|
+
session_id = laminar_span_context.session_id
|
|
125
|
+
if laminar_span_context.trace_type is not None:
|
|
126
|
+
try:
|
|
127
|
+
trace_type = (
|
|
128
|
+
TraceType(laminar_span_context.trace_type)
|
|
129
|
+
if isinstance(laminar_span_context.trace_type, str)
|
|
130
|
+
else laminar_span_context.trace_type
|
|
131
|
+
)
|
|
132
|
+
except (ValueError, TypeError):
|
|
133
|
+
pass
|
|
134
|
+
metadata = laminar_span_context.metadata
|
|
135
|
+
|
|
136
|
+
# Convert to OTEL span context
|
|
137
|
+
try:
|
|
138
|
+
otel_span_context = LaminarSpanContext.try_to_otel_span_context(
|
|
139
|
+
laminar_span_context, logger
|
|
140
|
+
)
|
|
141
|
+
except ValueError as exc:
|
|
142
|
+
logger.warning(f"Invalid span context provided: {exc}")
|
|
143
|
+
return ParsedParentSpanContext(
|
|
144
|
+
otel_span_context=None,
|
|
145
|
+
path=path,
|
|
146
|
+
span_ids_path=span_ids_path,
|
|
147
|
+
user_id=user_id,
|
|
148
|
+
session_id=session_id,
|
|
149
|
+
trace_type=trace_type,
|
|
150
|
+
metadata=metadata,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return ParsedParentSpanContext(
|
|
154
|
+
otel_span_context=otel_span_context,
|
|
155
|
+
path=path,
|
|
156
|
+
span_ids_path=span_ids_path,
|
|
157
|
+
user_id=user_id,
|
|
158
|
+
session_id=session_id,
|
|
159
|
+
trace_type=trace_type,
|
|
160
|
+
metadata=metadata,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
53
164
|
class Laminar:
|
|
54
165
|
__project_api_key: str | None = None
|
|
55
166
|
__initialized: bool = False
|
|
56
167
|
__base_http_url: str | None = None
|
|
168
|
+
__global_metadata: dict[str, AttributeValue] = {}
|
|
57
169
|
|
|
58
170
|
@classmethod
|
|
59
171
|
def initialize(
|
|
60
172
|
cls,
|
|
61
173
|
project_api_key: str | None = None,
|
|
62
174
|
base_url: str | None = None,
|
|
175
|
+
base_http_url: str | None = None,
|
|
63
176
|
http_port: int | None = None,
|
|
64
177
|
grpc_port: int | None = None,
|
|
65
|
-
instruments:
|
|
66
|
-
|
|
178
|
+
instruments: (
|
|
179
|
+
list[Instruments] | set[Instruments] | tuple[Instruments] | None
|
|
180
|
+
) = None,
|
|
181
|
+
disabled_instruments: (
|
|
182
|
+
list[Instruments] | set[Instruments] | tuple[Instruments] | None
|
|
183
|
+
) = None,
|
|
67
184
|
disable_batch: bool = False,
|
|
68
185
|
max_export_batch_size: int | None = None,
|
|
69
186
|
export_timeout_seconds: int | None = None,
|
|
70
187
|
set_global_tracer_provider: bool = True,
|
|
71
188
|
otel_logger_level: int = logging.ERROR,
|
|
189
|
+
session_recording_options: SessionRecordingOptions | None = None,
|
|
190
|
+
force_http: bool = False,
|
|
191
|
+
metadata: dict[str, AttributeValue] | None = None,
|
|
72
192
|
):
|
|
73
193
|
"""Initialize Laminar context across the application.
|
|
74
194
|
This method must be called before using any other Laminar methods or
|
|
@@ -76,41 +196,51 @@ class Laminar:
|
|
|
76
196
|
|
|
77
197
|
Args:
|
|
78
198
|
project_api_key (str | None, optional): Laminar project api key.\
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
in os.environ or in .env file.
|
|
84
|
-
Defaults to None.
|
|
199
|
+
You can generate one by going to the projects settings page on\
|
|
200
|
+
the Laminar dashboard. If not specified, we will try to read\
|
|
201
|
+
from the LMNR_PROJECT_API_KEY environment variable in os.environ\
|
|
202
|
+
or in .env file. Defaults to None.
|
|
85
203
|
base_url (str | None, optional): Laminar API url. Do NOT include\
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
204
|
+
the port number, use `http_port` and `grpc_port`. If not\
|
|
205
|
+
specified, defaults to https://api.lmnr.ai.
|
|
206
|
+
base_http_url (str | None, optional): Laminar API http url. Only\
|
|
207
|
+
set this if your Laminar backend HTTP is proxied through a\
|
|
208
|
+
different host. If not specified, defaults to\
|
|
209
|
+
https://api.lmnr.ai.
|
|
210
|
+
http_port (int | None, optional): Laminar API http port. If not\
|
|
211
|
+
specified, defaults to 443.
|
|
212
|
+
grpc_port (int | None, optional): Laminar API grpc port. If not\
|
|
213
|
+
specified, defaults to 8443.
|
|
214
|
+
instruments (set[Instruments] | list[Instruments] | tuple[Instruments] | None, optional):
|
|
215
|
+
Instruments to enable. Defaults to all instruments. You can pass\
|
|
216
|
+
an empty set to disable all instruments. Read more:\
|
|
217
|
+
https://docs.lmnr.ai/tracing/automatic-instrumentation
|
|
218
|
+
disabled_instruments (set[Instruments] | list[Instruments] | tuple[Instruments] | None, optional):
|
|
219
|
+
Instruments to disable. Defaults to None.
|
|
98
220
|
disable_batch (bool, optional): If set to True, spans will be sent\
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
221
|
+
immediately to the backend. Useful for debugging, but may cause\
|
|
222
|
+
performance overhead in production. Defaults to False.
|
|
223
|
+
max_export_batch_size (int | None, optional): Maximum number of spans\
|
|
224
|
+
to export in a single batch. If not specified, defaults to 64\
|
|
225
|
+
(lower than the OpenTelemetry default of 512). If you see\
|
|
226
|
+
`DEADLINE_EXCEEDED` errors, try reducing this value.
|
|
102
227
|
export_timeout_seconds (int | None, optional): Timeout for the OTLP\
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Defaults to None.
|
|
228
|
+
exporter. Defaults to 30 seconds (unlike the OpenTelemetry\
|
|
229
|
+
default of 10 seconds). Defaults to None.
|
|
106
230
|
set_global_tracer_provider (bool, optional): If set to True, the\
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
231
|
+
Laminar tracer provider will be set as the global tracer provider.\
|
|
232
|
+
OpenTelemetry allows only one tracer provider per app, so set this\
|
|
233
|
+
to False, if you are using another tracing library. Setting this to\
|
|
234
|
+
False may break some external instrumentations, e.g. LiteLLM.\
|
|
235
|
+
Defaults to True.
|
|
236
|
+
otel_logger_level (int, optional): OpenTelemetry logger level. Defaults\
|
|
237
|
+
to logging.ERROR.
|
|
238
|
+
session_recording_options (SessionRecordingOptions | None, optional): Options\
|
|
239
|
+
for browser session recording. Currently supports 'mask_input'\
|
|
240
|
+
(bool) to control whether input fields are masked during recording.\
|
|
241
|
+
Defaults to None (uses default masking behavior).
|
|
242
|
+
force_http (bool, optional): If set to True, the HTTP OTEL exporter will be\
|
|
243
|
+
used instead of the gRPC OTEL exporter. Defaults to False.
|
|
114
244
|
Raises:
|
|
115
245
|
ValueError: If project API key is not set
|
|
116
246
|
"""
|
|
@@ -121,7 +251,12 @@ class Laminar:
|
|
|
121
251
|
return
|
|
122
252
|
|
|
123
253
|
cls.__project_api_key = project_api_key or from_env("LMNR_PROJECT_API_KEY")
|
|
124
|
-
|
|
254
|
+
|
|
255
|
+
if (
|
|
256
|
+
not cls.__project_api_key
|
|
257
|
+
and not get_otel_env_var("ENDPOINT")
|
|
258
|
+
and not get_otel_env_var("HEADERS")
|
|
259
|
+
):
|
|
125
260
|
raise ValueError(
|
|
126
261
|
"Please initialize the Laminar object with"
|
|
127
262
|
" your project API key or set the LMNR_PROJECT_API_KEY"
|
|
@@ -130,12 +265,19 @@ class Laminar:
|
|
|
130
265
|
|
|
131
266
|
cls._initialize_logger()
|
|
132
267
|
|
|
133
|
-
url = base_url or from_env("LMNR_BASE_URL")
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
url
|
|
137
|
-
|
|
138
|
-
|
|
268
|
+
url = base_url or from_env("LMNR_BASE_URL")
|
|
269
|
+
if url:
|
|
270
|
+
url = url.rstrip("/")
|
|
271
|
+
if not url.startswith("http:") and not url.startswith("https:"):
|
|
272
|
+
url = f"https://{url}"
|
|
273
|
+
if match := re.search(r":(\d{1,5})$", url):
|
|
274
|
+
url = url[: -len(match.group(0))]
|
|
275
|
+
cls.__logger.info(f"Ignoring port in base URL: {match.group(1)}")
|
|
276
|
+
http_url = base_http_url or url or "https://api.lmnr.ai"
|
|
277
|
+
if not http_url.startswith("http:") and not http_url.startswith("https:"):
|
|
278
|
+
http_url = f"https://{http_url}"
|
|
279
|
+
if match := re.search(r":(\d{1,5})$", http_url):
|
|
280
|
+
http_url = http_url[: -len(match.group(0))]
|
|
139
281
|
if http_port is None:
|
|
140
282
|
cls.__logger.info(f"Using HTTP port from base URL: {match.group(1)}")
|
|
141
283
|
http_port = int(match.group(1))
|
|
@@ -143,7 +285,8 @@ class Laminar:
|
|
|
143
285
|
cls.__logger.info(f"Using HTTP port passed as an argument: {http_port}")
|
|
144
286
|
|
|
145
287
|
cls.__initialized = True
|
|
146
|
-
cls.__base_http_url = f"{
|
|
288
|
+
cls.__base_http_url = f"{http_url}:{http_port or 443}"
|
|
289
|
+
cls.__global_metadata = metadata or {}
|
|
147
290
|
|
|
148
291
|
if not os.getenv("OTEL_ATTRIBUTE_COUNT_LIMIT"):
|
|
149
292
|
# each message is at least 2 attributes: role and content,
|
|
@@ -155,14 +298,63 @@ class Laminar:
|
|
|
155
298
|
http_port=http_port or 443,
|
|
156
299
|
port=grpc_port or 8443,
|
|
157
300
|
project_api_key=cls.__project_api_key,
|
|
158
|
-
instruments=instruments,
|
|
159
|
-
block_instruments=
|
|
301
|
+
instruments=set(instruments) if instruments is not None else None,
|
|
302
|
+
block_instruments=(
|
|
303
|
+
set(disabled_instruments) if disabled_instruments is not None else None
|
|
304
|
+
),
|
|
160
305
|
disable_batch=disable_batch,
|
|
161
306
|
max_export_batch_size=max_export_batch_size,
|
|
162
307
|
timeout_seconds=export_timeout_seconds,
|
|
163
308
|
set_global_tracer_provider=set_global_tracer_provider,
|
|
164
309
|
otel_logger_level=otel_logger_level,
|
|
310
|
+
session_recording_options=session_recording_options,
|
|
311
|
+
force_http=force_http,
|
|
165
312
|
)
|
|
313
|
+
with get_tracer_with_context() as (tracer, isolated_context):
|
|
314
|
+
new_ctx = context_api.set_value(
|
|
315
|
+
CONTEXT_METADATA_KEY, cls.__global_metadata, isolated_context
|
|
316
|
+
)
|
|
317
|
+
attach_context(new_ctx)
|
|
318
|
+
|
|
319
|
+
cls._initialize_context_from_env()
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def _initialize_context_from_env(cls) -> None:
|
|
323
|
+
"""Attach upstream Laminar context from the environment, if provided."""
|
|
324
|
+
env_context = os.getenv("LMNR_SPAN_CONTEXT")
|
|
325
|
+
if not env_context:
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
laminar_context = LaminarSpanContext.deserialize(env_context)
|
|
330
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
331
|
+
cls.__logger.warning(
|
|
332
|
+
"LMNR_SPAN_CONTEXT is set but could not be deserialized: %s", exc
|
|
333
|
+
)
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
otel_span_context = LaminarSpanContext.try_to_otel_span_context(
|
|
338
|
+
laminar_context, cls.__logger
|
|
339
|
+
)
|
|
340
|
+
except ValueError as exc:
|
|
341
|
+
cls.__logger.warning(
|
|
342
|
+
"LMNR_SPAN_CONTEXT is set but invalid span context provided: %s", exc
|
|
343
|
+
)
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
base_context = trace.set_span_in_context(
|
|
347
|
+
trace.NonRecordingSpan(otel_span_context), get_current_context()
|
|
348
|
+
)
|
|
349
|
+
processor = TracerWrapper.instance._span_processor
|
|
350
|
+
if isinstance(processor, LaminarSpanProcessor):
|
|
351
|
+
processor.set_parent_path_info(
|
|
352
|
+
otel_span_context.span_id,
|
|
353
|
+
laminar_context.span_path,
|
|
354
|
+
laminar_context.span_ids_path,
|
|
355
|
+
)
|
|
356
|
+
push_span_context(base_context)
|
|
357
|
+
cls.__logger.debug("Initialized Laminar parent context from LMNR_SPAN_CONTEXT.")
|
|
166
358
|
|
|
167
359
|
@classmethod
|
|
168
360
|
def is_initialized(cls):
|
|
@@ -185,42 +377,48 @@ class Laminar:
|
|
|
185
377
|
def event(
|
|
186
378
|
cls,
|
|
187
379
|
name: str,
|
|
188
|
-
|
|
380
|
+
attributes: dict[str, AttributeValue] | None = None,
|
|
189
381
|
timestamp: datetime.datetime | int | None = None,
|
|
382
|
+
*,
|
|
383
|
+
user_id: str | None = None,
|
|
384
|
+
session_id: str | None = None,
|
|
190
385
|
):
|
|
191
|
-
"""Associate an event with the current span.
|
|
192
|
-
|
|
193
|
-
`value` will be saved as a `lmnr.event.value` attribute.
|
|
386
|
+
"""Associate an event with the current span. This is a wrapper around
|
|
387
|
+
`span.add_event()` that adds the event to the current span.
|
|
194
388
|
|
|
195
389
|
Args:
|
|
196
390
|
name (str): event name
|
|
197
|
-
|
|
198
|
-
primitive type. Boolean `True` is assumed in the backend if\
|
|
199
|
-
`value` is None.
|
|
391
|
+
attributes (dict[str, AttributeValue] | None, optional): event attributes.
|
|
200
392
|
Defaults to None.
|
|
201
393
|
timestamp (datetime.datetime | int | None, optional): If int, must\
|
|
202
394
|
be epoch nanoseconds. If not specified, relies on the underlying\
|
|
203
395
|
OpenTelemetry implementation. Defaults to None.
|
|
204
396
|
"""
|
|
397
|
+
if not cls.is_initialized():
|
|
398
|
+
return
|
|
399
|
+
|
|
205
400
|
if timestamp and isinstance(timestamp, datetime.datetime):
|
|
206
401
|
timestamp = int(timestamp.timestamp() * 1e9)
|
|
207
402
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
403
|
+
extra_attributes = get_event_attributes_from_context()
|
|
404
|
+
|
|
405
|
+
# override the user_id and session_id from the context with the ones
|
|
406
|
+
# passed as arguments
|
|
407
|
+
if user_id is not None:
|
|
408
|
+
extra_attributes["lmnr.event.user_id"] = user_id
|
|
409
|
+
if session_id is not None:
|
|
410
|
+
extra_attributes["lmnr.event.session_id"] = session_id
|
|
213
411
|
|
|
214
|
-
current_span = trace.get_current_span()
|
|
412
|
+
current_span = trace.get_current_span(context=get_current_context())
|
|
215
413
|
if current_span == trace.INVALID_SPAN:
|
|
216
|
-
cls.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"Make sure to annotate the function with a decorator"
|
|
220
|
-
)
|
|
414
|
+
span = cls.start_span(name)
|
|
415
|
+
span.add_event(name, {**(attributes or {}), **extra_attributes}, timestamp)
|
|
416
|
+
span.end()
|
|
221
417
|
return
|
|
222
418
|
|
|
223
|
-
current_span.add_event(
|
|
419
|
+
current_span.add_event(
|
|
420
|
+
name, {**(attributes or {}), **extra_attributes}, timestamp
|
|
421
|
+
)
|
|
224
422
|
|
|
225
423
|
@classmethod
|
|
226
424
|
@contextmanager
|
|
@@ -233,7 +431,10 @@ class Laminar:
|
|
|
233
431
|
labels: list[str] | None = None,
|
|
234
432
|
parent_span_context: LaminarSpanContext | None = None,
|
|
235
433
|
tags: list[str] | None = None,
|
|
236
|
-
|
|
434
|
+
user_id: str | None = None,
|
|
435
|
+
session_id: str | None = None,
|
|
436
|
+
metadata: dict[str, AttributeValue] | None = None,
|
|
437
|
+
) -> Iterator[LaminarSpan]:
|
|
237
438
|
"""Start a new span as the current span. Useful for manual
|
|
238
439
|
instrumentation. If `span_type` is set to `"LLM"`, you should report
|
|
239
440
|
usage and response attributes manually. See `Laminar.set_span_attributes`
|
|
@@ -262,13 +463,18 @@ class Laminar:
|
|
|
262
463
|
obtained from `Laminar.get_laminar_span_context_dict()` or\
|
|
263
464
|
`Laminar.get_laminar_span_context_str()` respectively, it will be\
|
|
264
465
|
converted to a `LaminarSpanContext` if possible. See also\
|
|
265
|
-
`Laminar.
|
|
266
|
-
`Laminar.get_span_context_str` for more information.
|
|
466
|
+
`Laminar.serialize_span_context` for more information.
|
|
267
467
|
Defaults to None.
|
|
268
468
|
labels (list[str] | None, optional): [DEPRECATED] Use tags\
|
|
269
469
|
instead. Labels to set for the span. Defaults to None.
|
|
270
470
|
tags (list[str] | None, optional): tags to set for the span.
|
|
271
471
|
Defaults to None.
|
|
472
|
+
user_id (str | None, optional): user id to set for the trace.
|
|
473
|
+
Defaults to None.
|
|
474
|
+
session_id (str | None, optional): session id to set for the trace.
|
|
475
|
+
Defaults to None.
|
|
476
|
+
metadata (dict[str, AttributeValue] | None, optional): metadata to\
|
|
477
|
+
set for the trace. Defaults to None.
|
|
272
478
|
"""
|
|
273
479
|
|
|
274
480
|
if not cls.is_initialized():
|
|
@@ -281,16 +487,69 @@ class Laminar:
|
|
|
281
487
|
)
|
|
282
488
|
return
|
|
283
489
|
|
|
284
|
-
with
|
|
285
|
-
ctx = context or
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
490
|
+
with get_tracer_with_context() as (tracer, isolated_context):
|
|
491
|
+
ctx = context or isolated_context
|
|
492
|
+
|
|
493
|
+
# Parse parent_span_context and extract all info
|
|
494
|
+
parsed = _parse_parent_span_context(parent_span_context, cls.__logger)
|
|
495
|
+
|
|
496
|
+
# Set parent span in context if present
|
|
497
|
+
if parsed["otel_span_context"] is not None:
|
|
290
498
|
ctx = trace.set_span_in_context(
|
|
291
|
-
trace.NonRecordingSpan(
|
|
499
|
+
trace.NonRecordingSpan(parsed["otel_span_context"]), ctx
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Determine trace_type with proper priority
|
|
503
|
+
trace_type = None
|
|
504
|
+
if span_type in ["EVALUATION", "EXECUTOR", "EVALUATOR"]:
|
|
505
|
+
trace_type = TraceType.EVALUATION
|
|
506
|
+
elif parsed["trace_type"] is not None:
|
|
507
|
+
trace_type = parsed["trace_type"]
|
|
508
|
+
|
|
509
|
+
# Merge metadata: context (inherited) + global + parent + explicit (explicit wins)
|
|
510
|
+
# Get metadata from context if it exists
|
|
511
|
+
ctx_metadata = get_value(CONTEXT_METADATA_KEY, ctx) or {}
|
|
512
|
+
# Merge with priority: global < context < parent < explicit
|
|
513
|
+
merged_metadata = {
|
|
514
|
+
**(cls.__global_metadata or {}),
|
|
515
|
+
**(ctx_metadata or {}),
|
|
516
|
+
**(parsed["metadata"] or {}),
|
|
517
|
+
**(metadata or {}),
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
# Get association props from context (fallback values)
|
|
521
|
+
ctx_user_id = get_value(CONTEXT_USER_ID_KEY, ctx)
|
|
522
|
+
ctx_session_id = get_value(CONTEXT_SESSION_ID_KEY, ctx)
|
|
523
|
+
|
|
524
|
+
# Merge user_id and session_id with priority: context < parent < explicit
|
|
525
|
+
final_user_id = (
|
|
526
|
+
user_id
|
|
527
|
+
if user_id is not None
|
|
528
|
+
else (
|
|
529
|
+
parsed["user_id"] if parsed["user_id"] is not None else ctx_user_id
|
|
292
530
|
)
|
|
293
|
-
|
|
531
|
+
)
|
|
532
|
+
final_session_id = (
|
|
533
|
+
session_id
|
|
534
|
+
if session_id is not None
|
|
535
|
+
else (
|
|
536
|
+
parsed["session_id"]
|
|
537
|
+
if parsed["session_id"] is not None
|
|
538
|
+
else ctx_session_id
|
|
539
|
+
)
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
ctx = set_association_prop_context(
|
|
543
|
+
trace_type=trace_type,
|
|
544
|
+
user_id=final_user_id,
|
|
545
|
+
session_id=final_session_id,
|
|
546
|
+
metadata=merged_metadata if merged_metadata else None,
|
|
547
|
+
context=ctx,
|
|
548
|
+
# we need a token separately, so we manually attach the context
|
|
549
|
+
attach=False,
|
|
550
|
+
)
|
|
551
|
+
ctx_token = context_api.attach(ctx)
|
|
552
|
+
isolated_context_token = attach_context(ctx)
|
|
294
553
|
label_props = {}
|
|
295
554
|
try:
|
|
296
555
|
if labels:
|
|
@@ -313,82 +572,31 @@ class Laminar:
|
|
|
313
572
|
f"`start_as_current_span` Could not set tags: {tags}. Tags must be a list of strings. "
|
|
314
573
|
"Tags will be ignored."
|
|
315
574
|
)
|
|
316
|
-
with tracer.start_as_current_span(
|
|
317
|
-
name,
|
|
318
|
-
context=ctx,
|
|
319
|
-
attributes={
|
|
320
|
-
SPAN_TYPE: span_type,
|
|
321
|
-
**(label_props),
|
|
322
|
-
**(tag_props),
|
|
323
|
-
},
|
|
324
|
-
) as span:
|
|
325
|
-
if input is not None:
|
|
326
|
-
serialized_input = json_dumps(input)
|
|
327
|
-
if len(serialized_input) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
|
|
328
|
-
span.set_attribute(
|
|
329
|
-
SPAN_INPUT,
|
|
330
|
-
"Laminar: input too large to record",
|
|
331
|
-
)
|
|
332
|
-
else:
|
|
333
|
-
span.set_attribute(
|
|
334
|
-
SPAN_INPUT,
|
|
335
|
-
serialized_input,
|
|
336
|
-
)
|
|
337
|
-
yield span
|
|
338
|
-
|
|
339
|
-
# TODO: Figure out if this is necessary
|
|
340
|
-
try:
|
|
341
|
-
detach(ctx_token)
|
|
342
|
-
except Exception:
|
|
343
|
-
pass
|
|
344
|
-
|
|
345
|
-
@classmethod
|
|
346
|
-
@contextmanager
|
|
347
|
-
@deprecated(
|
|
348
|
-
"Use `Laminar.set_span_tags` or the `tags` argument of "
|
|
349
|
-
"`Laminar.start_as_current_span` or `Laminar.start_span` instead"
|
|
350
|
-
)
|
|
351
|
-
def with_labels(cls, labels: list[str], context: Context | None = None):
|
|
352
|
-
"""Set labels for spans within this `with` context. This is useful for
|
|
353
|
-
adding labels to the spans created in the auto-instrumentations.
|
|
354
|
-
|
|
355
|
-
Requirements:
|
|
356
|
-
- Labels must be created in your project in advance.
|
|
357
|
-
- Keys must be strings from your label names.
|
|
358
|
-
- Values must be strings matching the label's allowed values.
|
|
359
|
-
|
|
360
|
-
Usage example:
|
|
361
|
-
```python
|
|
362
|
-
with Laminar.with_labels({"sentiment": "positive"}):
|
|
363
|
-
openai_client.chat.completions.create()
|
|
364
|
-
```
|
|
365
|
-
"""
|
|
366
|
-
warnings.warn(
|
|
367
|
-
"`Laminar.with_labels` is deprecated. Use `Laminar.set_span_tags` or the `tags` argument of "
|
|
368
|
-
"`Laminar.start_as_current_span` or `Laminar.start_span` instead",
|
|
369
|
-
DeprecationWarning,
|
|
370
|
-
)
|
|
371
|
-
if not cls.is_initialized():
|
|
372
|
-
yield
|
|
373
|
-
return
|
|
374
575
|
|
|
375
|
-
with get_tracer():
|
|
376
|
-
label_props = labels.copy()
|
|
377
|
-
prev_labels = get_association_properties(context).get("labels", [])
|
|
378
|
-
update_association_properties(
|
|
379
|
-
{"labels": prev_labels + label_props},
|
|
380
|
-
set_on_current_span=False,
|
|
381
|
-
context=context,
|
|
382
|
-
)
|
|
383
|
-
yield
|
|
384
576
|
try:
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
577
|
+
with tracer.start_as_current_span(
|
|
578
|
+
name,
|
|
579
|
+
context=ctx,
|
|
580
|
+
attributes={
|
|
581
|
+
SPAN_TYPE: span_type,
|
|
582
|
+
PARENT_SPAN_PATH: parsed["path"],
|
|
583
|
+
PARENT_SPAN_IDS_PATH: parsed["span_ids_path"],
|
|
584
|
+
**(label_props),
|
|
585
|
+
**(tag_props),
|
|
586
|
+
# Association properties are attached to context above
|
|
587
|
+
# and the relevant attributes are populated in the processor
|
|
588
|
+
},
|
|
589
|
+
) as span:
|
|
590
|
+
if not isinstance(span, LaminarSpan):
|
|
591
|
+
span = LaminarSpan(span)
|
|
592
|
+
span.set_input(input)
|
|
593
|
+
yield span
|
|
594
|
+
finally:
|
|
595
|
+
try:
|
|
596
|
+
detach_context(isolated_context_token)
|
|
597
|
+
context_api.detach(ctx_token)
|
|
598
|
+
except Exception:
|
|
599
|
+
pass
|
|
392
600
|
|
|
393
601
|
@classmethod
|
|
394
602
|
def start_span(
|
|
@@ -400,30 +608,41 @@ class Laminar:
|
|
|
400
608
|
parent_span_context: LaminarSpanContext | None = None,
|
|
401
609
|
labels: dict[str, str] | None = None,
|
|
402
610
|
tags: list[str] | None = None,
|
|
403
|
-
|
|
611
|
+
user_id: str | None = None,
|
|
612
|
+
session_id: str | None = None,
|
|
613
|
+
metadata: dict[str, AttributeValue] | None = None,
|
|
614
|
+
) -> LaminarSpan | Span:
|
|
404
615
|
"""Start a new span. Useful for manual instrumentation.
|
|
405
616
|
If `span_type` is set to `"LLM"`, you should report usage and response
|
|
406
617
|
attributes manually. See `Laminar.set_span_attributes` for more
|
|
407
618
|
information.
|
|
408
619
|
|
|
620
|
+
Note that spans started with this method must be ended manually.
|
|
621
|
+
In addition, they must be ended in LIFO order, e.g.
|
|
622
|
+
span1 = Laminar.start_span("span1")
|
|
623
|
+
span2 = Laminar.start_span("span2")
|
|
624
|
+
span2.end()
|
|
625
|
+
span1.end()
|
|
626
|
+
Otherwise, the behavior is undefined.
|
|
627
|
+
|
|
409
628
|
Usage example:
|
|
410
629
|
```python
|
|
411
|
-
from src.lmnr import Laminar
|
|
630
|
+
from src.lmnr import Laminar
|
|
412
631
|
def foo(span):
|
|
413
|
-
with use_span(span):
|
|
632
|
+
with Laminar.use_span(span):
|
|
414
633
|
with Laminar.start_as_current_span("foo_inner"):
|
|
415
634
|
some_function()
|
|
416
|
-
|
|
635
|
+
|
|
417
636
|
def bar():
|
|
418
|
-
with use_span(span):
|
|
637
|
+
with Laminar.use_span(span):
|
|
419
638
|
openai_client.chat.completions.create()
|
|
420
|
-
|
|
639
|
+
|
|
421
640
|
span = Laminar.start_span("outer")
|
|
422
641
|
foo(span)
|
|
423
642
|
bar(span)
|
|
424
643
|
# IMPORTANT: End the span manually
|
|
425
644
|
span.end()
|
|
426
|
-
|
|
645
|
+
|
|
427
646
|
# Results in:
|
|
428
647
|
# | outer
|
|
429
648
|
# | | foo
|
|
@@ -455,6 +674,12 @@ class Laminar:
|
|
|
455
674
|
Defaults to None.
|
|
456
675
|
labels (dict[str, str] | None, optional): [DEPRECATED] Use tags\
|
|
457
676
|
instead. Labels to set for the span. Defaults to None.
|
|
677
|
+
user_id (str | None, optional): user id to set for the trace.
|
|
678
|
+
Defaults to None.
|
|
679
|
+
session_id (str | None, optional): session id to set for the trace.
|
|
680
|
+
Defaults to None.
|
|
681
|
+
metadata (dict[str, AttributeValue] | None, optional): metadata to\
|
|
682
|
+
set for the trace. Defaults to None.
|
|
458
683
|
"""
|
|
459
684
|
if not cls.is_initialized():
|
|
460
685
|
return trace.NonRecordingSpan(
|
|
@@ -465,15 +690,23 @@ class Laminar:
|
|
|
465
690
|
)
|
|
466
691
|
)
|
|
467
692
|
|
|
468
|
-
with
|
|
469
|
-
ctx = context or
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
693
|
+
with get_tracer_with_context() as (tracer, isolated_context):
|
|
694
|
+
ctx = context or isolated_context
|
|
695
|
+
|
|
696
|
+
# Parse parent_span_context and extract all info
|
|
697
|
+
parsed = _parse_parent_span_context(parent_span_context, cls.__logger)
|
|
698
|
+
|
|
699
|
+
# Set parent span in context if present
|
|
700
|
+
if parsed["otel_span_context"] is not None:
|
|
474
701
|
ctx = trace.set_span_in_context(
|
|
475
|
-
trace.NonRecordingSpan(
|
|
702
|
+
trace.NonRecordingSpan(parsed["otel_span_context"]), ctx
|
|
476
703
|
)
|
|
704
|
+
|
|
705
|
+
# Get association props from context (fallback values)
|
|
706
|
+
ctx_user_id = get_value(CONTEXT_USER_ID_KEY, ctx)
|
|
707
|
+
ctx_session_id = get_value(CONTEXT_SESSION_ID_KEY, ctx)
|
|
708
|
+
ctx_metadata = get_value(CONTEXT_METADATA_KEY, ctx)
|
|
709
|
+
|
|
477
710
|
label_props = {}
|
|
478
711
|
try:
|
|
479
712
|
if labels:
|
|
@@ -498,97 +731,312 @@ class Laminar:
|
|
|
498
731
|
f"`start_span` Could not set tags: {tags}. Tags must be a list of strings. "
|
|
499
732
|
+ "Tags will be ignored."
|
|
500
733
|
)
|
|
734
|
+
|
|
735
|
+
# Determine trace_type with proper priority: explicit > parent > context
|
|
736
|
+
trace_type = None
|
|
737
|
+
if span_type in ["EVALUATION", "EXECUTOR", "EVALUATOR"]:
|
|
738
|
+
trace_type = TraceType.EVALUATION
|
|
739
|
+
elif parsed["trace_type"] is not None:
|
|
740
|
+
trace_type = parsed["trace_type"]
|
|
741
|
+
else:
|
|
742
|
+
# Get trace_type from context if not set explicitly or from parent
|
|
743
|
+
ctx_trace_type = get_value(CONTEXT_TRACE_TYPE_KEY, ctx)
|
|
744
|
+
if ctx_trace_type:
|
|
745
|
+
try:
|
|
746
|
+
trace_type = TraceType(ctx_trace_type)
|
|
747
|
+
except (ValueError, TypeError):
|
|
748
|
+
pass
|
|
749
|
+
|
|
750
|
+
# Merge with priority: global < context < parent < explicit
|
|
751
|
+
merged_metadata = {
|
|
752
|
+
**(cls.__global_metadata or {}),
|
|
753
|
+
**(ctx_metadata or {}),
|
|
754
|
+
**(parsed["metadata"] or {}),
|
|
755
|
+
**(metadata or {}),
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
# Merge user_id and session_id with priority: context < parent < explicit
|
|
759
|
+
final_user_id = (
|
|
760
|
+
user_id
|
|
761
|
+
if user_id is not None
|
|
762
|
+
else (
|
|
763
|
+
parsed["user_id"] if parsed["user_id"] is not None else ctx_user_id
|
|
764
|
+
)
|
|
765
|
+
)
|
|
766
|
+
final_session_id = (
|
|
767
|
+
session_id
|
|
768
|
+
if session_id is not None
|
|
769
|
+
else (
|
|
770
|
+
parsed["session_id"]
|
|
771
|
+
if parsed["session_id"] is not None
|
|
772
|
+
else ctx_session_id
|
|
773
|
+
)
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Build association_props using merged values
|
|
777
|
+
association_props = cls._get_association_prop_attributes(
|
|
778
|
+
user_id=final_user_id,
|
|
779
|
+
session_id=final_session_id,
|
|
780
|
+
metadata=merged_metadata if merged_metadata else None,
|
|
781
|
+
trace_type=trace_type,
|
|
782
|
+
)
|
|
783
|
+
|
|
501
784
|
span = tracer.start_span(
|
|
502
785
|
name,
|
|
503
786
|
context=ctx,
|
|
504
787
|
attributes={
|
|
505
788
|
SPAN_TYPE: span_type,
|
|
789
|
+
PARENT_SPAN_PATH: parsed["path"],
|
|
790
|
+
PARENT_SPAN_IDS_PATH: parsed["span_ids_path"],
|
|
506
791
|
**(label_props),
|
|
507
792
|
**(tag_props),
|
|
793
|
+
**(association_props),
|
|
508
794
|
},
|
|
509
795
|
)
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
SPAN_INPUT,
|
|
515
|
-
"Laminar: input too large to record",
|
|
516
|
-
)
|
|
517
|
-
else:
|
|
518
|
-
span.set_attribute(
|
|
519
|
-
SPAN_INPUT,
|
|
520
|
-
serialized_input,
|
|
521
|
-
)
|
|
796
|
+
|
|
797
|
+
if not isinstance(span, LaminarSpan):
|
|
798
|
+
span = LaminarSpan(span)
|
|
799
|
+
span.set_input(input)
|
|
522
800
|
return span
|
|
523
801
|
|
|
524
802
|
@classmethod
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
803
|
+
@contextmanager
|
|
804
|
+
def use_span(
|
|
805
|
+
cls,
|
|
806
|
+
span: Span,
|
|
807
|
+
end_on_exit: bool = False,
|
|
808
|
+
record_exception: bool = True,
|
|
809
|
+
set_status_on_exception: bool = True,
|
|
810
|
+
) -> Iterator[LaminarSpan | Span]:
|
|
811
|
+
"""Use a span as the current span. Useful for manual instrumentation.
|
|
812
|
+
|
|
813
|
+
Fully copies the implementation of `use_span` from opentelemetry.trace
|
|
814
|
+
and replaces the context API with Laminar's isolated context.
|
|
528
815
|
|
|
529
816
|
Args:
|
|
530
|
-
|
|
531
|
-
|
|
817
|
+
span: The span that should be activated in the current context.
|
|
818
|
+
end_on_exit: Whether to end the span automatically when leaving the
|
|
819
|
+
context manager scope.
|
|
820
|
+
record_exception: Whether to record any exceptions raised within the
|
|
821
|
+
context as error event on the span.
|
|
822
|
+
set_status_on_exception: Only relevant if the returned span is used
|
|
823
|
+
in a with/context manager. Defines whether the span status will
|
|
824
|
+
be automatically set to ERROR when an uncaught exception is
|
|
825
|
+
raised in the span with block. The span status won't be set by
|
|
826
|
+
this mechanism if it was previously set manually.
|
|
532
827
|
"""
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
828
|
+
if not cls.is_initialized():
|
|
829
|
+
with use_span(
|
|
830
|
+
span, end_on_exit, record_exception, set_status_on_exception
|
|
831
|
+
) as s:
|
|
832
|
+
yield s
|
|
833
|
+
return
|
|
834
|
+
|
|
835
|
+
wrapper = TracerWrapper()
|
|
836
|
+
|
|
837
|
+
try:
|
|
838
|
+
# Set association props in context before push_span_context
|
|
839
|
+
# so child spans inherit them
|
|
840
|
+
assoc_props_token = set_association_props_in_context(span)
|
|
841
|
+
if assoc_props_token and isinstance(span, LaminarSpan):
|
|
842
|
+
span._lmnr_assoc_props_token = assoc_props_token
|
|
843
|
+
|
|
844
|
+
context = wrapper.push_span_context(span)
|
|
845
|
+
# Some auto-instrumentations are not under our control, so they
|
|
846
|
+
# don't have access to our isolated context. We attach the context
|
|
847
|
+
# to the OTEL global context, so that spans know their parent
|
|
848
|
+
# span and trace_id.
|
|
849
|
+
isolated_context_token = attach_context(context)
|
|
850
|
+
context_token = context_api.attach(context)
|
|
851
|
+
if isinstance(span, LaminarSpan):
|
|
852
|
+
yield span
|
|
541
853
|
else:
|
|
542
|
-
span
|
|
854
|
+
yield LaminarSpan(span)
|
|
855
|
+
|
|
856
|
+
# Record only exceptions that inherit Exception class but not BaseException, because
|
|
857
|
+
# classes that directly inherit BaseException are not technically errors, e.g. GeneratorExit.
|
|
858
|
+
# See https://github.com/open-telemetry/opentelemetry-python/issues/4484
|
|
859
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
860
|
+
if isinstance(span, Span) and span.is_recording():
|
|
861
|
+
# Record the exception as an event
|
|
862
|
+
if record_exception:
|
|
863
|
+
span.record_exception(
|
|
864
|
+
exc, attributes=get_event_attributes_from_context()
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
# Set status in case exception was raised
|
|
868
|
+
if set_status_on_exception:
|
|
869
|
+
span.set_status(
|
|
870
|
+
Status(
|
|
871
|
+
status_code=StatusCode.ERROR,
|
|
872
|
+
description=f"{type(exc).__name__}: {exc}",
|
|
873
|
+
)
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
# This causes parent spans to set their status to ERROR and to record
|
|
877
|
+
# an exception as an event if a child span raises an exception even if
|
|
878
|
+
# such child span was started with both record_exception and
|
|
879
|
+
# set_status_on_exception attributes set to False.
|
|
880
|
+
raise
|
|
881
|
+
|
|
882
|
+
finally:
|
|
883
|
+
try:
|
|
884
|
+
context_api.detach(context_token)
|
|
885
|
+
detach_context(isolated_context_token)
|
|
886
|
+
wrapper.pop_span_context()
|
|
887
|
+
finally:
|
|
888
|
+
if end_on_exit:
|
|
889
|
+
span.end()
|
|
543
890
|
|
|
544
891
|
@classmethod
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
892
|
+
def start_active_span(
|
|
893
|
+
cls,
|
|
894
|
+
name: str,
|
|
895
|
+
input: Any = None,
|
|
896
|
+
span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
|
|
897
|
+
context: Context | None = None,
|
|
898
|
+
parent_span_context: LaminarSpanContext | None = None,
|
|
899
|
+
tags: list[str] | None = None,
|
|
900
|
+
user_id: str | None = None,
|
|
901
|
+
session_id: str | None = None,
|
|
902
|
+
metadata: dict[str, AttributeValue] | None = None,
|
|
903
|
+
) -> LaminarSpan | Span:
|
|
904
|
+
"""Start a span and mark it as active within the current context.
|
|
905
|
+
All spans started after this one will be children of this span.
|
|
906
|
+
Useful for manual instrumentation. Must be ended manually.
|
|
907
|
+
If `span_type` is set to `"LLM"`, you should report usage and response
|
|
908
|
+
attributes manually. See `Laminar.set_span_attributes` for more
|
|
909
|
+
information. Returns the span object.
|
|
910
|
+
|
|
911
|
+
Note that ending the started span in a different async context yields
|
|
912
|
+
unexpected results. When propagating spans across different async or
|
|
913
|
+
threading contexts, it is recommended to either:
|
|
914
|
+
- Make sure to start and end the span in the same async context or thread, or
|
|
915
|
+
- Use `Laminar.start_span` + `Laminar.use_span` where possible.
|
|
916
|
+
|
|
917
|
+
Note that spans started with this method must be ended manually.
|
|
918
|
+
In addition, they must be ended in LIFO order, e.g.
|
|
919
|
+
span1 = Laminar.start_active_span("span1")
|
|
920
|
+
span2 = Laminar.start_active_span("span2")
|
|
921
|
+
span2.end()
|
|
922
|
+
span1.end()
|
|
923
|
+
Otherwise, the behavior is undefined.
|
|
557
924
|
|
|
558
|
-
|
|
925
|
+
Usage example:
|
|
559
926
|
```python
|
|
560
|
-
from lmnr import Laminar,
|
|
927
|
+
from src.lmnr import Laminar, observe
|
|
928
|
+
|
|
929
|
+
@observe()
|
|
930
|
+
def foo():
|
|
931
|
+
with Laminar.start_as_current_span("foo_inner"):
|
|
932
|
+
some_function()
|
|
561
933
|
|
|
562
|
-
|
|
934
|
+
@observe()
|
|
935
|
+
def bar():
|
|
563
936
|
openai_client.chat.completions.create()
|
|
937
|
+
|
|
938
|
+
span = Laminar.start_active_span("outer")
|
|
939
|
+
foo()
|
|
940
|
+
bar()
|
|
941
|
+
# IMPORTANT: End the span manually
|
|
942
|
+
span.end()
|
|
943
|
+
|
|
944
|
+
# Results in:
|
|
945
|
+
# | outer
|
|
946
|
+
# | | foo
|
|
947
|
+
# | | | foo_inner
|
|
948
|
+
# | | bar
|
|
949
|
+
# | | | openai.chat
|
|
564
950
|
```
|
|
951
|
+
|
|
952
|
+
Args:
|
|
953
|
+
name (str): name of the span
|
|
954
|
+
input (Any, optional): input to the span. Will be sent as an\
|
|
955
|
+
attribute, so must be json serializable. Defaults to None.
|
|
956
|
+
span_type (Literal["DEFAULT", "LLM", "TOOL"], optional):\
|
|
957
|
+
type of the span. If you use `"LLM"`, you should report usage\
|
|
958
|
+
and response attributes manually. Defaults to "DEFAULT".
|
|
959
|
+
context (Context | None, optional): raw OpenTelemetry context\
|
|
960
|
+
to attach the span to. Defaults to None.
|
|
961
|
+
parent_span_context (LaminarSpanContext | None, optional): parent\
|
|
962
|
+
span context to use for the span. Useful for continuing traces\
|
|
963
|
+
across services. If parent_span_context is a\
|
|
964
|
+
raw OpenTelemetry span context, or if it is a dictionary or string\
|
|
965
|
+
obtained from `Laminar.get_laminar_span_context_dict()` or\
|
|
966
|
+
`Laminar.get_laminar_span_context_str()` respectively, it will be\
|
|
967
|
+
converted to a `LaminarSpanContext` if possible. See also\
|
|
968
|
+
`Laminar.get_span_context`, `Laminar.get_span_context_dict` and\
|
|
969
|
+
`Laminar.get_span_context_str` for more information.
|
|
970
|
+
Defaults to None.
|
|
971
|
+
tags (list[str] | None, optional): tags to set for the span.
|
|
972
|
+
Defaults to None.
|
|
973
|
+
user_id (str | None, optional): user id to set for the trace.
|
|
974
|
+
Defaults to None.
|
|
975
|
+
session_id (str | None, optional): session id to set for the trace.
|
|
976
|
+
Defaults to None.
|
|
977
|
+
metadata (dict[str, AttributeValue] | None, optional): metadata to\
|
|
978
|
+
set for the trace. Defaults to None.
|
|
565
979
|
"""
|
|
566
|
-
|
|
567
|
-
|
|
980
|
+
span = cls.start_span(
|
|
981
|
+
name=name,
|
|
982
|
+
input=input,
|
|
983
|
+
span_type=span_type,
|
|
984
|
+
context=context,
|
|
985
|
+
parent_span_context=parent_span_context,
|
|
986
|
+
tags=tags,
|
|
987
|
+
user_id=user_id,
|
|
988
|
+
session_id=session_id,
|
|
989
|
+
metadata=metadata,
|
|
990
|
+
)
|
|
991
|
+
if not cls.is_initialized():
|
|
992
|
+
return span
|
|
993
|
+
wrapper = TracerWrapper()
|
|
994
|
+
|
|
995
|
+
# Set association props in context before push_span_context
|
|
996
|
+
# so child spans inherit them
|
|
997
|
+
assoc_props_token = set_association_props_in_context(span)
|
|
998
|
+
if assoc_props_token and isinstance(span, LaminarSpan):
|
|
999
|
+
span._lmnr_assoc_props_token = assoc_props_token
|
|
1000
|
+
|
|
1001
|
+
context = wrapper.push_span_context(span)
|
|
1002
|
+
context_token = context_api.attach(context)
|
|
1003
|
+
isolated_context_token = attach_context(context)
|
|
1004
|
+
span._lmnr_ctx_token = context_token
|
|
1005
|
+
span._lmnr_isolated_ctx_token = isolated_context_token
|
|
1006
|
+
if isinstance(span, LaminarSpan):
|
|
1007
|
+
return span
|
|
568
1008
|
else:
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
1009
|
+
return LaminarSpan(span)
|
|
1010
|
+
|
|
1011
|
+
@classmethod
|
|
1012
|
+
def set_span_output(cls, output: Any = None):
|
|
1013
|
+
"""Set the output of the current span. Useful for manual
|
|
1014
|
+
instrumentation.
|
|
1015
|
+
|
|
1016
|
+
Args:
|
|
1017
|
+
output (Any, optional): output of the span. Will be sent as an\
|
|
1018
|
+
attribute, so must be json serializable. Defaults to None.
|
|
1019
|
+
"""
|
|
1020
|
+
span = cls.get_current_span()
|
|
1021
|
+
if span is None:
|
|
1022
|
+
return
|
|
1023
|
+
span.set_output(output)
|
|
576
1024
|
|
|
577
1025
|
@classmethod
|
|
578
1026
|
def set_span_attributes(
|
|
579
1027
|
cls,
|
|
580
|
-
attributes: dict[Attributes, Any],
|
|
1028
|
+
attributes: dict[Attributes | str, Any],
|
|
581
1029
|
):
|
|
582
1030
|
"""Set attributes for the current span. Useful for manual
|
|
583
1031
|
instrumentation.
|
|
584
1032
|
Example:
|
|
585
1033
|
```python
|
|
586
|
-
with
|
|
1034
|
+
with Laminar.start_as_current_span(
|
|
587
1035
|
name="my_span_name", input=input["messages"], span_type="LLM"
|
|
588
1036
|
):
|
|
589
1037
|
response = await my_custom_call_to_openai(input)
|
|
590
|
-
|
|
591
|
-
|
|
1038
|
+
Laminar.set_span_output(response["choices"][0]["message"]["content"])
|
|
1039
|
+
Laminar.set_span_attributes({
|
|
592
1040
|
Attributes.PROVIDER: 'openai',
|
|
593
1041
|
Attributes.REQUEST_MODEL: input["model"],
|
|
594
1042
|
Attributes.RESPONSE_MODEL: response["model"],
|
|
@@ -599,24 +1047,19 @@ class Laminar:
|
|
|
599
1047
|
```
|
|
600
1048
|
|
|
601
1049
|
Args:
|
|
602
|
-
attributes (dict[
|
|
1050
|
+
attributes (dict[Attributes | str, Any]): attributes to set for the span
|
|
603
1051
|
"""
|
|
604
|
-
span =
|
|
605
|
-
if span == trace.INVALID_SPAN:
|
|
1052
|
+
span = cls.get_current_span()
|
|
1053
|
+
if span == trace.INVALID_SPAN or span is None:
|
|
606
1054
|
return
|
|
607
1055
|
|
|
608
1056
|
for key, value in attributes.items():
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
cls.__logger.warning(
|
|
614
|
-
f"Attribute {key} is not a valid Laminar attribute."
|
|
615
|
-
)
|
|
616
|
-
if not isinstance(value, (str, int, float, bool)):
|
|
617
|
-
span.set_attribute(key.value, json_dumps(value))
|
|
1057
|
+
if isinstance(key, Attributes):
|
|
1058
|
+
key = key.value
|
|
1059
|
+
if not is_otel_attribute_value_type(value):
|
|
1060
|
+
span.set_attribute(key, json_dumps(value))
|
|
618
1061
|
else:
|
|
619
|
-
span.set_attribute(key
|
|
1062
|
+
span.set_attribute(key, value)
|
|
620
1063
|
|
|
621
1064
|
@classmethod
|
|
622
1065
|
def get_laminar_span_context(
|
|
@@ -625,14 +1068,15 @@ class Laminar:
|
|
|
625
1068
|
"""Get the laminar span context for a given span.
|
|
626
1069
|
If no span is provided, the current active span will be used.
|
|
627
1070
|
"""
|
|
628
|
-
|
|
629
|
-
if span == trace.INVALID_SPAN:
|
|
1071
|
+
if not cls.is_initialized():
|
|
630
1072
|
return None
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
)
|
|
1073
|
+
|
|
1074
|
+
span = span or cls.get_current_span()
|
|
1075
|
+
if span == trace.INVALID_SPAN or span is None:
|
|
1076
|
+
return None
|
|
1077
|
+
if not isinstance(span, LaminarSpan):
|
|
1078
|
+
span = LaminarSpan(span)
|
|
1079
|
+
return span.get_laminar_span_context()
|
|
636
1080
|
|
|
637
1081
|
@classmethod
|
|
638
1082
|
def get_laminar_span_context_dict(
|
|
@@ -681,6 +1125,29 @@ class Laminar:
|
|
|
681
1125
|
def deserialize_span_context(cls, span_context: dict | str) -> LaminarSpanContext:
|
|
682
1126
|
return LaminarSpanContext.deserialize(span_context)
|
|
683
1127
|
|
|
1128
|
+
@classmethod
|
|
1129
|
+
def get_current_span(cls, context: Context | None = None) -> LaminarSpan | None:
|
|
1130
|
+
"""Get the current active span. If a context is provided, the span will
|
|
1131
|
+
be retrieved from that context.
|
|
1132
|
+
|
|
1133
|
+
Args:
|
|
1134
|
+
context (Context | None, optional): The context to get the span\
|
|
1135
|
+
from. If not provided, the current context will be used.
|
|
1136
|
+
Defaults to None.
|
|
1137
|
+
|
|
1138
|
+
Returns:
|
|
1139
|
+
LaminarSpan | None: The current active span, or None if there is no\
|
|
1140
|
+
active span.
|
|
1141
|
+
"""
|
|
1142
|
+
context = context or get_current_context()
|
|
1143
|
+
span = trace.get_current_span(context=context)
|
|
1144
|
+
if span == trace.INVALID_SPAN:
|
|
1145
|
+
return None
|
|
1146
|
+
if isinstance(span, LaminarSpan):
|
|
1147
|
+
return span
|
|
1148
|
+
else:
|
|
1149
|
+
return LaminarSpan(span)
|
|
1150
|
+
|
|
684
1151
|
@classmethod
|
|
685
1152
|
def flush(cls) -> bool:
|
|
686
1153
|
"""Flush the internal tracer.
|
|
@@ -693,6 +1160,22 @@ class Laminar:
|
|
|
693
1160
|
return False
|
|
694
1161
|
return TracerManager.flush()
|
|
695
1162
|
|
|
1163
|
+
@classmethod
|
|
1164
|
+
def force_flush(cls):
|
|
1165
|
+
"""Force flush the internal tracer. WARNING: Any active spans are
|
|
1166
|
+
removed from context; that is, spans started afterwards will start
|
|
1167
|
+
a new trace.
|
|
1168
|
+
|
|
1169
|
+
Actually shuts down the span processor and re-initializes it as long
|
|
1170
|
+
as it is a LaminarSpanProcessor. This is not recommended in production
|
|
1171
|
+
workflows, but is useful at the end of Lambda functions, where a regular
|
|
1172
|
+
flush might be killed by the Lambda runtime, because the actual export
|
|
1173
|
+
inside it runs in a background thread.
|
|
1174
|
+
"""
|
|
1175
|
+
if not cls.is_initialized():
|
|
1176
|
+
return
|
|
1177
|
+
TracerManager.force_reinit_processor()
|
|
1178
|
+
|
|
696
1179
|
@classmethod
|
|
697
1180
|
def shutdown(cls):
|
|
698
1181
|
if cls.is_initialized():
|
|
@@ -706,47 +1189,21 @@ class Laminar:
|
|
|
706
1189
|
Args:
|
|
707
1190
|
tags (list[str]): Tags to set for the span.
|
|
708
1191
|
"""
|
|
709
|
-
|
|
710
|
-
if span == trace.INVALID_SPAN:
|
|
711
|
-
cls.__logger.warning("No active span to set tags on")
|
|
1192
|
+
if not cls.is_initialized():
|
|
712
1193
|
return
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
)
|
|
1194
|
+
|
|
1195
|
+
span = cls.get_current_span()
|
|
1196
|
+
if span is None:
|
|
717
1197
|
return
|
|
718
|
-
span.
|
|
1198
|
+
span.set_tags(tags)
|
|
719
1199
|
|
|
720
1200
|
@classmethod
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
cls
|
|
724
|
-
|
|
725
|
-
):
|
|
726
|
-
"""Set the session id for the current span and the context
|
|
727
|
-
(i.e. any children spans created from the current span in the current
|
|
728
|
-
thread).
|
|
729
|
-
|
|
730
|
-
Args:
|
|
731
|
-
session_id (str | None, optional): Custom session id.\
|
|
732
|
-
Useful to debug and group long-running\
|
|
733
|
-
sessions/conversations.
|
|
734
|
-
Defaults to None.
|
|
735
|
-
"""
|
|
736
|
-
warnings.warn(
|
|
737
|
-
"`Laminar.set_session` is deprecated. Use `Laminar.set_trace_session_id` instead",
|
|
738
|
-
DeprecationWarning,
|
|
739
|
-
)
|
|
740
|
-
association_properties = {}
|
|
741
|
-
if session_id is not None:
|
|
742
|
-
association_properties[SESSION_ID] = session_id
|
|
743
|
-
# update_association_properties(association_properties)
|
|
744
|
-
span = trace.get_current_span()
|
|
745
|
-
if span == trace.INVALID_SPAN:
|
|
746
|
-
cls.__logger.warning("No active span to set session id on")
|
|
1201
|
+
def add_span_tags(cls, tags: list[str]):
|
|
1202
|
+
"""Add tags to the current span."""
|
|
1203
|
+
span = cls.get_current_span()
|
|
1204
|
+
if span is None:
|
|
747
1205
|
return
|
|
748
|
-
|
|
749
|
-
span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{SESSION_ID}", session_id)
|
|
1206
|
+
span.add_tags(tags)
|
|
750
1207
|
|
|
751
1208
|
@classmethod
|
|
752
1209
|
def set_trace_session_id(cls, session_id: str | None = None):
|
|
@@ -756,12 +1213,16 @@ class Laminar:
|
|
|
756
1213
|
Args:
|
|
757
1214
|
session_id (str | None, optional): Custom session id. Defaults to None.
|
|
758
1215
|
"""
|
|
759
|
-
|
|
760
|
-
|
|
1216
|
+
if not cls.is_initialized():
|
|
1217
|
+
return
|
|
1218
|
+
|
|
1219
|
+
context = set_association_prop_context(session_id=session_id, attach=True)
|
|
1220
|
+
|
|
1221
|
+
span = cls.get_current_span(context=context)
|
|
1222
|
+
if span is None:
|
|
761
1223
|
cls.__logger.warning("No active span to set session id on")
|
|
762
1224
|
return
|
|
763
|
-
|
|
764
|
-
span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{SESSION_ID}", session_id)
|
|
1225
|
+
span.set_trace_session_id(session_id)
|
|
765
1226
|
|
|
766
1227
|
@classmethod
|
|
767
1228
|
def set_trace_user_id(cls, user_id: str | None = None):
|
|
@@ -771,34 +1232,16 @@ class Laminar:
|
|
|
771
1232
|
Args:
|
|
772
1233
|
user_id (str | None, optional): Custom user id. Defaults to None.
|
|
773
1234
|
"""
|
|
774
|
-
|
|
775
|
-
if span == trace.INVALID_SPAN:
|
|
776
|
-
cls.__logger.warning("No active span to set user id on")
|
|
1235
|
+
if not cls.is_initialized():
|
|
777
1236
|
return
|
|
778
|
-
if user_id is not None:
|
|
779
|
-
span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{USER_ID}", user_id)
|
|
780
1237
|
|
|
781
|
-
|
|
782
|
-
@deprecated("Use `Laminar.set_trace_metadata` instead")
|
|
783
|
-
def set_metadata(cls, metadata: dict[str, str]):
|
|
784
|
-
"""Set the metadata for the current trace.
|
|
1238
|
+
context = set_association_prop_context(user_id=user_id, attach=True)
|
|
785
1239
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
"""
|
|
790
|
-
warnings.warn(
|
|
791
|
-
"`Laminar.set_metadata` is deprecated. Use `Laminar.set_trace_metadata` instead",
|
|
792
|
-
DeprecationWarning,
|
|
793
|
-
)
|
|
794
|
-
props = {f"metadata.{k}": json_dumps(v) for k, v in metadata.items()}
|
|
795
|
-
# update_association_properties(props)
|
|
796
|
-
span = trace.get_current_span()
|
|
797
|
-
if span == trace.INVALID_SPAN:
|
|
798
|
-
cls.__logger.warning("No active span to set metadata on")
|
|
1240
|
+
span = cls.get_current_span(context=context)
|
|
1241
|
+
if span is None:
|
|
1242
|
+
cls.__logger.warning("No active span to set user id on")
|
|
799
1243
|
return
|
|
800
|
-
|
|
801
|
-
span.set_attribute(key, value)
|
|
1244
|
+
span.set_trace_user_id(user_id)
|
|
802
1245
|
|
|
803
1246
|
@classmethod
|
|
804
1247
|
def set_trace_metadata(cls, metadata: dict[str, AttributeValue]):
|
|
@@ -807,17 +1250,16 @@ class Laminar:
|
|
|
807
1250
|
Args:
|
|
808
1251
|
metadata (dict[str, AttributeValue]): Metadata to set for the trace.
|
|
809
1252
|
"""
|
|
810
|
-
|
|
811
|
-
|
|
1253
|
+
if not cls.is_initialized():
|
|
1254
|
+
return
|
|
1255
|
+
|
|
1256
|
+
merged_metadata = {**cls.__global_metadata, **(metadata or {})}
|
|
1257
|
+
|
|
1258
|
+
span = cls.get_current_span()
|
|
1259
|
+
if span is None:
|
|
812
1260
|
cls.__logger.warning("No active span to set metadata on")
|
|
813
1261
|
return
|
|
814
|
-
|
|
815
|
-
if is_otel_attribute_value_type(value):
|
|
816
|
-
span.set_attribute(f"{ASSOCIATION_PROPERTIES}.metadata.{key}", value)
|
|
817
|
-
else:
|
|
818
|
-
span.set_attribute(
|
|
819
|
-
f"{ASSOCIATION_PROPERTIES}.metadata.{key}", json_dumps(value)
|
|
820
|
-
)
|
|
1262
|
+
span.set_trace_metadata(merged_metadata)
|
|
821
1263
|
|
|
822
1264
|
@classmethod
|
|
823
1265
|
def get_base_http_url(cls):
|
|
@@ -836,7 +1278,11 @@ class Laminar:
|
|
|
836
1278
|
uuid.UUID | None: The trace id for the current span, or None if\
|
|
837
1279
|
there is no active span.
|
|
838
1280
|
"""
|
|
839
|
-
trace_id =
|
|
1281
|
+
trace_id = (
|
|
1282
|
+
trace.get_current_span(context=get_current_context())
|
|
1283
|
+
.get_span_context()
|
|
1284
|
+
.trace_id
|
|
1285
|
+
)
|
|
840
1286
|
if trace_id == INVALID_TRACE_ID:
|
|
841
1287
|
return None
|
|
842
1288
|
return uuid.UUID(int=trace_id)
|
|
@@ -858,8 +1304,45 @@ class Laminar:
|
|
|
858
1304
|
Args:
|
|
859
1305
|
trace_type (TraceType): Type of the trace
|
|
860
1306
|
"""
|
|
861
|
-
|
|
1307
|
+
if not cls.is_initialized():
|
|
1308
|
+
return
|
|
1309
|
+
|
|
1310
|
+
span = trace.get_current_span(context=get_current_context())
|
|
862
1311
|
if span == trace.INVALID_SPAN:
|
|
863
1312
|
cls.__logger.warning("No active span to set trace type on")
|
|
864
1313
|
return
|
|
865
1314
|
span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{TRACE_TYPE}", trace_type.value)
|
|
1315
|
+
|
|
1316
|
+
@classmethod
|
|
1317
|
+
def _get_association_prop_attributes(
|
|
1318
|
+
cls,
|
|
1319
|
+
user_id: str | None = None,
|
|
1320
|
+
session_id: str | None = None,
|
|
1321
|
+
trace_type: TraceType | None = None,
|
|
1322
|
+
metadata: dict[str, AttributeValue] | None = None,
|
|
1323
|
+
) -> dict[str, AttributeValue]:
|
|
1324
|
+
association_properties = {}
|
|
1325
|
+
if user_id is not None:
|
|
1326
|
+
association_properties[f"{ASSOCIATION_PROPERTIES}.{USER_ID}"] = user_id
|
|
1327
|
+
if session_id is not None:
|
|
1328
|
+
association_properties[f"{ASSOCIATION_PROPERTIES}.{SESSION_ID}"] = (
|
|
1329
|
+
session_id
|
|
1330
|
+
)
|
|
1331
|
+
if trace_type is not None:
|
|
1332
|
+
trace_type_val = (
|
|
1333
|
+
trace_type.value if isinstance(trace_type, TraceType) else trace_type
|
|
1334
|
+
)
|
|
1335
|
+
association_properties[f"{ASSOCIATION_PROPERTIES}.{TRACE_TYPE}"] = (
|
|
1336
|
+
trace_type_val
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
merged_metadata = {**cls.__global_metadata, **(metadata or {})}
|
|
1340
|
+
association_properties.update(
|
|
1341
|
+
{
|
|
1342
|
+
f"{ASSOCIATION_PROPERTIES}.metadata.{k}": (
|
|
1343
|
+
v if is_otel_attribute_value_type(v) else json_dumps(v)
|
|
1344
|
+
)
|
|
1345
|
+
for k, v in merged_metadata.items()
|
|
1346
|
+
}
|
|
1347
|
+
)
|
|
1348
|
+
return association_properties
|