lmnr 0.5.1a0__py3-none-any.whl → 0.5.2__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.
Files changed (33) hide show
  1. lmnr/__init__.py +0 -8
  2. lmnr/openllmetry_sdk/__init__.py +5 -33
  3. lmnr/openllmetry_sdk/decorators/base.py +24 -17
  4. lmnr/openllmetry_sdk/instruments.py +1 -0
  5. lmnr/openllmetry_sdk/opentelemetry/instrumentation/google_genai/__init__.py +454 -0
  6. lmnr/openllmetry_sdk/opentelemetry/instrumentation/google_genai/config.py +9 -0
  7. lmnr/openllmetry_sdk/opentelemetry/instrumentation/google_genai/utils.py +216 -0
  8. lmnr/openllmetry_sdk/tracing/__init__.py +1 -0
  9. lmnr/openllmetry_sdk/tracing/context_manager.py +13 -0
  10. lmnr/openllmetry_sdk/tracing/tracing.py +230 -252
  11. lmnr/sdk/browser/playwright_otel.py +42 -58
  12. lmnr/sdk/browser/pw_utils.py +8 -40
  13. lmnr/sdk/client/asynchronous/async_client.py +0 -34
  14. lmnr/sdk/client/asynchronous/resources/__init__.py +0 -4
  15. lmnr/sdk/client/asynchronous/resources/agent.py +96 -6
  16. lmnr/sdk/client/synchronous/resources/__init__.py +1 -3
  17. lmnr/sdk/client/synchronous/resources/agent.py +94 -8
  18. lmnr/sdk/client/synchronous/sync_client.py +0 -36
  19. lmnr/sdk/decorators.py +16 -2
  20. lmnr/sdk/laminar.py +3 -3
  21. lmnr/sdk/types.py +84 -170
  22. lmnr/sdk/utils.py +8 -1
  23. lmnr/version.py +1 -1
  24. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/METADATA +57 -57
  25. lmnr-0.5.2.dist-info/RECORD +54 -0
  26. lmnr/sdk/client/asynchronous/resources/pipeline.py +0 -89
  27. lmnr/sdk/client/asynchronous/resources/semantic_search.py +0 -60
  28. lmnr/sdk/client/synchronous/resources/pipeline.py +0 -89
  29. lmnr/sdk/client/synchronous/resources/semantic_search.py +0 -60
  30. lmnr-0.5.1a0.dist-info/RECORD +0 -54
  31. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/LICENSE +0 -0
  32. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/WHEEL +0 -0
  33. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,216 @@
1
+ import logging
2
+ import traceback
3
+ import json
4
+
5
+ from .config import (
6
+ Config,
7
+ )
8
+ from google.genai import types
9
+ from google.genai._common import BaseModel
10
+ import pydantic
11
+ from opentelemetry.trace import Span
12
+ from typing import Any, Optional, Union
13
+
14
+
15
+ def set_span_attribute(span: Span, name: str, value: str):
16
+ if value is not None:
17
+ if value != "":
18
+ span.set_attribute(name, value)
19
+ return
20
+
21
+
22
+ def dont_throw(func):
23
+ """
24
+ A decorator that wraps the passed in function and logs exceptions instead of throwing them.
25
+
26
+ @param func: The function to wrap
27
+ @return: The wrapper function
28
+ """
29
+ # Obtain a logger specific to the function's module
30
+ logger = logging.getLogger(func.__module__)
31
+
32
+ def wrapper(*args, **kwargs):
33
+ try:
34
+ return func(*args, **kwargs)
35
+ except Exception as e:
36
+ logger.debug(
37
+ "OpenLLMetry failed to trace in %s, error: %s",
38
+ func.__name__,
39
+ traceback.format_exc(),
40
+ )
41
+ if Config.exception_logger:
42
+ Config.exception_logger(e)
43
+
44
+ return wrapper
45
+
46
+
47
+ def to_dict(obj: Union[BaseModel, pydantic.BaseModel, dict]) -> dict[str, Any]:
48
+ try:
49
+ if isinstance(obj, BaseModel):
50
+ return obj.model_dump()
51
+ elif isinstance(obj, pydantic.BaseModel):
52
+ return obj.model_dump()
53
+ elif isinstance(obj, dict):
54
+ return obj
55
+ else:
56
+ return dict(obj)
57
+ except Exception:
58
+ return dict(obj)
59
+
60
+
61
+ def process_content_union(
62
+ content: Union[types.ContentUnion, types.ContentUnionDict],
63
+ trace_id: Optional[str] = None,
64
+ span_id: Optional[str] = None,
65
+ message_index: int = 0,
66
+ ) -> Optional[str]:
67
+ parts = _process_content_union(content, trace_id, span_id, message_index)
68
+ if parts is None:
69
+ return None
70
+ if isinstance(parts, str):
71
+ return parts
72
+ elif isinstance(parts, list):
73
+ if len(parts) == 1 and isinstance(parts[0], str):
74
+ return parts[0]
75
+ return json.dumps(
76
+ [
77
+ {"type": "text", "text": part} if isinstance(part, str) else part
78
+ for part in parts
79
+ ]
80
+ )
81
+ else:
82
+ return None
83
+
84
+
85
+ def _process_content_union(
86
+ content: Union[types.ContentUnion, types.ContentUnionDict],
87
+ trace_id: Optional[str] = None,
88
+ span_id: Optional[str] = None,
89
+ message_index: int = 0,
90
+ ) -> Union[str, list[str], None]:
91
+ if isinstance(content, types.Content):
92
+ parts = to_dict(content).get("parts", [])
93
+ return [_process_part(part) for part in parts]
94
+ elif isinstance(content, list):
95
+ return [_process_part_union(item) for item in content]
96
+ elif isinstance(content, (types.Part, types.File, str)):
97
+ return _process_part_union(content)
98
+ elif isinstance(content, dict):
99
+ if "parts" in content:
100
+ return [
101
+ _process_part_union(
102
+ item, trace_id, span_id, message_index, content_index
103
+ )
104
+ for content_index, item in enumerate(content.get("parts", []))
105
+ ]
106
+ else:
107
+ # Assume it's PartDict
108
+ return _process_part_union(content, trace_id, span_id, message_index)
109
+ else:
110
+ return None
111
+
112
+
113
+ def _process_part_union(
114
+ content: Union[types.PartDict, types.File, types.Part, str],
115
+ trace_id: Optional[str] = None,
116
+ span_id: Optional[str] = None,
117
+ message_index: int = 0,
118
+ content_index: int = 0,
119
+ ) -> Optional[str]:
120
+ if isinstance(content, str):
121
+ return content
122
+ elif isinstance(content, types.File):
123
+ content_dict = to_dict(content)
124
+ name = (
125
+ content_dict.get("name")
126
+ or content_dict.get("display_name")
127
+ or content_dict.get("uri")
128
+ )
129
+ return f"files/{name}"
130
+ elif isinstance(content, (types.Part, dict)):
131
+ return _process_part(content, trace_id, span_id, message_index, content_index)
132
+ else:
133
+ return None
134
+
135
+
136
+ def _process_part(
137
+ content: types.Part,
138
+ trace_id: Optional[str] = None,
139
+ span_id: Optional[str] = None,
140
+ message_index: int = 0,
141
+ content_index: int = 0,
142
+ ) -> Optional[str]:
143
+ part_dict = to_dict(content)
144
+ if part_dict.get("text") is not None:
145
+ return part_dict.get("text")
146
+ elif part_dict.get("inline_data"):
147
+ blob = to_dict(part_dict.get("inline_data"))
148
+ if blob.get("mime_type").startswith("image/"):
149
+ return _process_image_item(
150
+ blob, trace_id, span_id, message_index, content_index
151
+ )
152
+ else:
153
+ # currently, only images are supported
154
+ return blob.get("mime_type") or "unknown_media"
155
+ else:
156
+ return None
157
+
158
+
159
+ def role_from_content_union(
160
+ content: Union[types.ContentUnion, types.ContentUnionDict],
161
+ ) -> Optional[str]:
162
+ if isinstance(content, types.Content):
163
+ return to_dict(content).get("role")
164
+ elif isinstance(content, list) and len(content) > 0:
165
+ return role_from_content_union(content[0])
166
+ else:
167
+ return None
168
+
169
+
170
+ def with_tracer_wrapper(func):
171
+ """Helper for providing tracer for wrapper functions."""
172
+
173
+ def _with_tracer(tracer, to_wrap):
174
+ def wrapper(wrapped, instance, args, kwargs):
175
+ return func(tracer, to_wrap, wrapped, instance, args, kwargs)
176
+
177
+ return wrapper
178
+
179
+ return _with_tracer
180
+
181
+
182
+ def _run_async(method):
183
+ import asyncio
184
+ import threading
185
+
186
+ try:
187
+ loop = asyncio.get_running_loop()
188
+ except RuntimeError:
189
+ loop = None
190
+
191
+ if loop and loop.is_running():
192
+ thread = threading.Thread(target=lambda: asyncio.run(method))
193
+ thread.start()
194
+ thread.join()
195
+ else:
196
+ asyncio.run(method)
197
+
198
+
199
+ def _process_image_item(
200
+ blob: dict[str, Any],
201
+ trace_id: str,
202
+ span_id: str,
203
+ message_index: int,
204
+ content_index: int,
205
+ ):
206
+ # Convert to openai format, so backends can handle it
207
+ return (
208
+ {
209
+ "type": "image_url",
210
+ "image_url": {
211
+ "url": f"data:image/{blob.get('mime_type').split('/')[1]};base64,{blob.get('data')}",
212
+ },
213
+ }
214
+ if Config.convert_image_to_openai_format
215
+ else blob
216
+ )
@@ -0,0 +1 @@
1
+ from lmnr.openllmetry_sdk.tracing.context_manager import get_tracer
@@ -0,0 +1,13 @@
1
+ from contextlib import contextmanager
2
+
3
+ from lmnr.openllmetry_sdk.tracing.tracing import TracerWrapper
4
+
5
+
6
+ @contextmanager
7
+ def get_tracer(flush_on_exit: bool = False):
8
+ wrapper = TracerWrapper()
9
+ try:
10
+ yield wrapper.get_tracer()
11
+ finally:
12
+ if flush_on_exit:
13
+ wrapper.flush()