raindrop-ai 0.0.35__py3-none-any.whl → 0.0.37__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.
- raindrop/analytics.py +54 -0
- raindrop/interaction.py +143 -0
- raindrop/version.py +1 -1
- {raindrop_ai-0.0.35.dist-info → raindrop_ai-0.0.37.dist-info}/METADATA +2 -1
- raindrop_ai-0.0.37.dist-info/RECORD +10 -0
- raindrop_ai-0.0.35.dist-info/RECORD +0 -10
- {raindrop_ai-0.0.35.dist-info → raindrop_ai-0.0.37.dist-info}/WHEEL +0 -0
raindrop/analytics.py
CHANGED
|
@@ -66,6 +66,7 @@ __all__ = [
|
|
|
66
66
|
"start_span",
|
|
67
67
|
"ManualSpan",
|
|
68
68
|
"set_span_properties",
|
|
69
|
+
"set_llm_span_io",
|
|
69
70
|
"flush",
|
|
70
71
|
"shutdown",
|
|
71
72
|
]
|
|
@@ -347,6 +348,59 @@ def _should_send_prompts():
|
|
|
347
348
|
).lower() == "true" or context_api.get_value("override_enable_content_tracing")
|
|
348
349
|
|
|
349
350
|
|
|
351
|
+
def set_llm_span_io(
|
|
352
|
+
input: Any = None,
|
|
353
|
+
output: Any = None,
|
|
354
|
+
) -> None:
|
|
355
|
+
"""
|
|
356
|
+
Set LLM input/output content on the current span.
|
|
357
|
+
|
|
358
|
+
Use this to add prompt/completion content to auto-instrumented spans
|
|
359
|
+
that don't capture content automatically (e.g., Bedrock with aioboto3).
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
input: The input/prompt content (messages, text, etc.)
|
|
363
|
+
output: The output/completion content (response text, message, etc.)
|
|
364
|
+
|
|
365
|
+
Example:
|
|
366
|
+
response = await bedrock_client.converse(modelId=model, messages=messages)
|
|
367
|
+
raindrop.set_llm_span_io(
|
|
368
|
+
input=messages,
|
|
369
|
+
output=response["output"]["message"]["content"]
|
|
370
|
+
)
|
|
371
|
+
"""
|
|
372
|
+
if not _should_send_prompts():
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
span = get_current_span()
|
|
376
|
+
if not span or not span.is_recording():
|
|
377
|
+
logger.debug("[raindrop] set_llm_span_io called but no active span found")
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
if input is not None:
|
|
382
|
+
input_str = (
|
|
383
|
+
json.dumps(input, cls=JSONEncoder)
|
|
384
|
+
if not isinstance(input, str)
|
|
385
|
+
else input
|
|
386
|
+
)
|
|
387
|
+
input_str = _truncate_json_if_needed(input_str)
|
|
388
|
+
span.set_attribute("gen_ai.prompt.0.role", "user")
|
|
389
|
+
span.set_attribute("gen_ai.prompt.0.content", input_str)
|
|
390
|
+
|
|
391
|
+
if output is not None:
|
|
392
|
+
output_str = (
|
|
393
|
+
json.dumps(output, cls=JSONEncoder)
|
|
394
|
+
if not isinstance(output, str)
|
|
395
|
+
else output
|
|
396
|
+
)
|
|
397
|
+
output_str = _truncate_json_if_needed(output_str)
|
|
398
|
+
span.set_attribute("gen_ai.completion.0.role", "assistant")
|
|
399
|
+
span.set_attribute("gen_ai.completion.0.content", output_str)
|
|
400
|
+
except Exception as e:
|
|
401
|
+
logger.debug(f"[raindrop] Failed to record LLM content: {e}")
|
|
402
|
+
|
|
403
|
+
|
|
350
404
|
# Signal types - This is now defined in models.py
|
|
351
405
|
# SignalType = Literal["default", "feedback", "edit"]
|
|
352
406
|
|
raindrop/interaction.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
2
4
|
from typing import (
|
|
3
5
|
Any,
|
|
4
6
|
Dict,
|
|
@@ -9,6 +11,7 @@ from typing import (
|
|
|
9
11
|
Union,
|
|
10
12
|
Iterator,
|
|
11
13
|
)
|
|
14
|
+
from datetime import datetime, timezone
|
|
12
15
|
from uuid import uuid4
|
|
13
16
|
from dataclasses import dataclass
|
|
14
17
|
|
|
@@ -107,6 +110,146 @@ class Interaction:
|
|
|
107
110
|
convo_id=self._convo_id,
|
|
108
111
|
)
|
|
109
112
|
|
|
113
|
+
def track_tool(
|
|
114
|
+
self,
|
|
115
|
+
*,
|
|
116
|
+
name: str,
|
|
117
|
+
input: Any | None = None,
|
|
118
|
+
output: Any | None = None,
|
|
119
|
+
duration_ms: float | int | None = None,
|
|
120
|
+
start_time: datetime | int | float | None = None,
|
|
121
|
+
error: BaseException | str | None = None,
|
|
122
|
+
properties: Dict[str, Any] | None = None,
|
|
123
|
+
version: int | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Retroactively log a tool span tied to this interaction.
|
|
127
|
+
"""
|
|
128
|
+
if not _core._tracing_enabled or not _core.TracerWrapper.verify_initialized():
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
# Duration normalization
|
|
132
|
+
dur_ms = float(duration_ms) if duration_ms is not None else 0.0
|
|
133
|
+
if dur_ms < 0:
|
|
134
|
+
dur_ms = 0.0
|
|
135
|
+
duration_ns = int(round(dur_ms * 1_000_000))
|
|
136
|
+
|
|
137
|
+
# start_time normalization (epoch nanoseconds)
|
|
138
|
+
start_ns: int | None = None
|
|
139
|
+
if isinstance(start_time, datetime):
|
|
140
|
+
dt = start_time
|
|
141
|
+
if dt.tzinfo is None:
|
|
142
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
143
|
+
else:
|
|
144
|
+
dt = dt.astimezone(timezone.utc)
|
|
145
|
+
|
|
146
|
+
epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
|
147
|
+
delta = dt - epoch
|
|
148
|
+
total_us = (
|
|
149
|
+
delta.days * 86400 + delta.seconds
|
|
150
|
+
) * 1_000_000 + delta.microseconds
|
|
151
|
+
start_ns = total_us * 1_000
|
|
152
|
+
elif isinstance(start_time, (int, float)):
|
|
153
|
+
v = float(start_time)
|
|
154
|
+
# Heuristic: values smaller than ~1973 in ms are likely epoch-seconds
|
|
155
|
+
start_ns = (
|
|
156
|
+
int(round(v * 1_000_000_000))
|
|
157
|
+
if abs(v) < 1e11
|
|
158
|
+
else int(round(v * 1_000_000))
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if start_ns is None:
|
|
162
|
+
start_ns = time.time_ns() - duration_ns
|
|
163
|
+
|
|
164
|
+
end_ns = start_ns + duration_ns
|
|
165
|
+
|
|
166
|
+
tlp_kind = _core.TraceloopSpanKindValues.TOOL
|
|
167
|
+
span_name = f"{name}.{tlp_kind.value}"
|
|
168
|
+
|
|
169
|
+
tracer = _core.trace.get_tracer("traceloop.tracer")
|
|
170
|
+
span = tracer.start_span(span_name, start_time=start_ns)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
span.set_attribute(_core.SpanAttributes.TRACELOOP_SPAN_KIND, tlp_kind.value)
|
|
174
|
+
span.set_attribute(_core.SpanAttributes.TRACELOOP_ENTITY_NAME, name)
|
|
175
|
+
if version is not None:
|
|
176
|
+
span.set_attribute(
|
|
177
|
+
_core.SpanAttributes.TRACELOOP_ENTITY_VERSION, version
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
association_props = {
|
|
181
|
+
"event_id": self._event_id,
|
|
182
|
+
"user_id": self._user_id,
|
|
183
|
+
"event": self._event,
|
|
184
|
+
"convo_id": self._convo_id,
|
|
185
|
+
}
|
|
186
|
+
for key, value in association_props.items():
|
|
187
|
+
if value is not None:
|
|
188
|
+
span.set_attribute(f"traceloop.association.properties.{key}", value)
|
|
189
|
+
|
|
190
|
+
if properties:
|
|
191
|
+
for key, value in properties.items():
|
|
192
|
+
if key in association_props:
|
|
193
|
+
continue
|
|
194
|
+
if value is None:
|
|
195
|
+
continue
|
|
196
|
+
if isinstance(value, (str, bool, int, float)):
|
|
197
|
+
attr_val: Any = value
|
|
198
|
+
else:
|
|
199
|
+
try:
|
|
200
|
+
attr_val = json.dumps(value, cls=_core.JSONEncoder)
|
|
201
|
+
except Exception:
|
|
202
|
+
attr_val = str(value)
|
|
203
|
+
span.set_attribute(
|
|
204
|
+
f"traceloop.association.properties.{key}", attr_val
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if duration_ms is not None:
|
|
208
|
+
span.set_attribute("traceloop.entity.duration_ms", dur_ms)
|
|
209
|
+
|
|
210
|
+
if _core._should_send_prompts():
|
|
211
|
+
if input is not None:
|
|
212
|
+
try:
|
|
213
|
+
json_input = json.dumps(
|
|
214
|
+
{"args": [input]}, cls=_core.JSONEncoder
|
|
215
|
+
)
|
|
216
|
+
span.set_attribute(
|
|
217
|
+
_core.SpanAttributes.TRACELOOP_ENTITY_INPUT,
|
|
218
|
+
_core._truncate_json_if_needed(json_input),
|
|
219
|
+
)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
_core.logger.debug(
|
|
222
|
+
f"[raindrop] Could not serialize input for span: {e}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if output is not None:
|
|
226
|
+
try:
|
|
227
|
+
json_output = json.dumps(output, cls=_core.JSONEncoder)
|
|
228
|
+
span.set_attribute(
|
|
229
|
+
_core.SpanAttributes.TRACELOOP_ENTITY_OUTPUT,
|
|
230
|
+
_core._truncate_json_if_needed(json_output),
|
|
231
|
+
)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
_core.logger.debug(
|
|
234
|
+
f"[raindrop] Could not serialize output for span: {e}"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if error is not None:
|
|
238
|
+
exc = (
|
|
239
|
+
error if isinstance(error, BaseException) else Exception(str(error))
|
|
240
|
+
)
|
|
241
|
+
span.set_status(_core.Status(_core.StatusCode.ERROR, str(exc)))
|
|
242
|
+
span.record_exception(exc)
|
|
243
|
+
else:
|
|
244
|
+
span.set_status(_core.Status(_core.StatusCode.OK))
|
|
245
|
+
finally:
|
|
246
|
+
span.end(end_time=end_ns)
|
|
247
|
+
|
|
248
|
+
if _core.debug_logs:
|
|
249
|
+
_core.logger.debug(
|
|
250
|
+
f'[raindrop] track_tool: logged tool span "{name}" (duration_ms={duration_ms})'
|
|
251
|
+
)
|
|
252
|
+
|
|
110
253
|
# convenience
|
|
111
254
|
@property
|
|
112
255
|
def id(self) -> str:
|
raindrop/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = "0.0.
|
|
1
|
+
VERSION = "0.0.37"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: raindrop-ai
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.37
|
|
4
4
|
Summary: Raindrop AI (Python SDK)
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Raindrop AI
|
|
@@ -15,6 +15,7 @@ Requires-Dist: opentelemetry-sdk (>=1.39.0)
|
|
|
15
15
|
Requires-Dist: pydantic (>=2.09,<3)
|
|
16
16
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
17
17
|
Requires-Dist: traceloop-sdk (>=0.46.0)
|
|
18
|
+
Requires-Dist: urllib3 (>=2.6.0)
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
|
|
20
21
|
# Raindrop Python SDK
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
raindrop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
raindrop/analytics.py,sha256=dL64bp5Sk1gq5PYcKAdQBNlmrn-5aW-LXTa0C3CApKA,30335
|
|
3
|
+
raindrop/interaction.py,sha256=qMTGN-nMDeYmujLhBqIUPq4pKDQx6HX0fqVwB2aq0eA,8639
|
|
4
|
+
raindrop/models.py,sha256=9lOOUQ2FF11RPkntuLZwN3e54pa9HtR8lGvCbzlWOPM,5198
|
|
5
|
+
raindrop/redact.py,sha256=rMNUoI90KxOY3d_zcHAr0TFD2yQ_CDgpDz-1XJLVmHs,7658
|
|
6
|
+
raindrop/version.py,sha256=bPu3tNWPJ9tg3HaQ8cbDkapv1vcKPLFyWLaaRG7eW2Q,19
|
|
7
|
+
raindrop/well-known-names.json,sha256=9giJF6u6W1R0APW-Pf1dvNUU32OXQEoQ9CBQXSnA3ks,144403
|
|
8
|
+
raindrop_ai-0.0.37.dist-info/METADATA,sha256=BO2HCUOURX8Rlhyu4L_XCon7XwS9ln1EP3qEfk6topY,1346
|
|
9
|
+
raindrop_ai-0.0.37.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
10
|
+
raindrop_ai-0.0.37.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
raindrop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
raindrop/analytics.py,sha256=uS4D8_r9VEzIVJgPDBpT7Nkr2i1PS9Yj_QCWgqGEBNM,28507
|
|
3
|
-
raindrop/interaction.py,sha256=Kw8HNaDSHVIi1p-2HK5SsYGI4Xg1zQFml4dX2EhIT7w,3123
|
|
4
|
-
raindrop/models.py,sha256=9lOOUQ2FF11RPkntuLZwN3e54pa9HtR8lGvCbzlWOPM,5198
|
|
5
|
-
raindrop/redact.py,sha256=rMNUoI90KxOY3d_zcHAr0TFD2yQ_CDgpDz-1XJLVmHs,7658
|
|
6
|
-
raindrop/version.py,sha256=XbHQAtEbxGWuqWT-2KR0HWozWyaEyD9WkdIKQJfT_OA,19
|
|
7
|
-
raindrop/well-known-names.json,sha256=9giJF6u6W1R0APW-Pf1dvNUU32OXQEoQ9CBQXSnA3ks,144403
|
|
8
|
-
raindrop_ai-0.0.35.dist-info/METADATA,sha256=0mJdipZfyB_rnDfBZjsnINUjiKqiJBSyUEMokqy6Bpg,1313
|
|
9
|
-
raindrop_ai-0.0.35.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
10
|
-
raindrop_ai-0.0.35.dist-info/RECORD,,
|
|
File without changes
|