raindrop-ai 0.0.33__tar.gz → 0.0.35__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: raindrop-ai
3
- Version: 0.0.33
3
+ Version: 0.0.35
4
4
  Summary: Raindrop AI (Python SDK)
5
5
  License: MIT
6
6
  Author: Raindrop AI
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: opentelemetry-sdk (>=1.39.0)
14
15
  Requires-Dist: pydantic (>=2.09,<3)
15
16
  Requires-Dist: requests (>=2.32.3,<3.0.0)
16
17
  Requires-Dist: traceloop-sdk (>=0.46.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "raindrop-ai"
3
- version = "0.0.33"
3
+ version = "0.0.35"
4
4
  description = "Raindrop AI (Python SDK)"
5
5
  authors = ["Raindrop AI <sdk@raindrop.ai>"]
6
6
  license = "MIT"
@@ -12,6 +12,7 @@ python = ">=3.10,<3.12.1 || >3.12.1,<4.0"
12
12
  pydantic = ">=2.09,<3"
13
13
  requests = "^2.32.3"
14
14
  traceloop-sdk = ">=0.46.0"
15
+ opentelemetry-sdk = ">=1.39.0"
15
16
 
16
17
 
17
18
  [tool.poetry.group.dev.dependencies]
@@ -63,6 +63,8 @@ __all__ = [
63
63
  "tool",
64
64
  "task_span",
65
65
  "tool_span",
66
+ "start_span",
67
+ "ManualSpan",
66
68
  "set_span_properties",
67
69
  "flush",
68
70
  "shutdown",
@@ -492,7 +494,7 @@ def begin(
492
494
  {k: v for k, v in span_attributes.items() if v is not None}
493
495
  )
494
496
 
495
- interaction = Interaction(eid)
497
+ interaction = Interaction(eid, user_id=user_id, event=event, convo_id=convo_id)
496
498
  INTERACTION_EVENT_ID_REGISTRY[eid] = interaction
497
499
  if current_trace_id is not None and current_trace_id != 0:
498
500
  INTERACTION_TRACE_ID_REGISTRY[current_trace_id] = interaction
@@ -635,6 +637,63 @@ class TraceEntitySpan:
635
637
  Traceloop.set_association_properties(props)
636
638
 
637
639
 
640
+ class ManualSpan:
641
+ """
642
+ A manually-controlled span for async/distributed operations.
643
+ Unlike context-managed spans, this requires explicit .end() calls.
644
+ """
645
+
646
+ def __init__(self, span, kind: str, name: str, event_id: str | None = None):
647
+ self._span = span
648
+ self._kind = kind
649
+ self._name = name
650
+ self._event_id = event_id
651
+ self._ended = False
652
+
653
+ @property
654
+ def event_id(self) -> str | None:
655
+ return self._event_id
656
+
657
+ def record_input(self, data: Any) -> None:
658
+ if self._span and _should_send_prompts():
659
+ try:
660
+ json_input = json.dumps({"args": [data]}, cls=JSONEncoder)
661
+ truncated = _truncate_json_if_needed(json_input)
662
+ self._span.set_attribute(
663
+ SpanAttributes.TRACELOOP_ENTITY_INPUT, truncated
664
+ )
665
+ except TypeError as e:
666
+ logger.debug(f"[raindrop] Could not serialize input for span: {e}")
667
+
668
+ def record_output(self, data: Any) -> None:
669
+ if self._span and _should_send_prompts():
670
+ try:
671
+ json_output = json.dumps(data, cls=JSONEncoder)
672
+ truncated = _truncate_json_if_needed(json_output)
673
+ self._span.set_attribute(
674
+ SpanAttributes.TRACELOOP_ENTITY_OUTPUT, truncated
675
+ )
676
+ except TypeError as e:
677
+ logger.debug(f"[raindrop] Could not serialize output for span: {e}")
678
+
679
+ def set_properties(self, props: Dict[str, Any]) -> None:
680
+ if self._span and props:
681
+ for key, value in props.items():
682
+ if value is not None:
683
+ self._span.set_attribute(
684
+ f"traceloop.association.properties.{key}", value
685
+ )
686
+
687
+ def end(self, error: Exception | None = None) -> None:
688
+ if self._ended or not self._span:
689
+ return
690
+ self._ended = True
691
+ if error is not None:
692
+ self._span.set_status(Status(StatusCode.ERROR, str(error)))
693
+ self._span.record_exception(error)
694
+ self._span.end()
695
+
696
+
638
697
  class _EntitySpanContext:
639
698
  def __init__(self, kind: Literal["task", "tool"], name: str, version: int | None):
640
699
  self._kind = kind
@@ -708,6 +767,63 @@ def tool_span(name: str, version: int | None = None) -> _EntitySpanContext:
708
767
  return _EntitySpanContext("tool", name, version)
709
768
 
710
769
 
770
+ def start_span(
771
+ kind: Literal["task", "tool"],
772
+ name: str,
773
+ version: int | None = None,
774
+ event_id: str | None = None,
775
+ user_id: str | None = None,
776
+ event: str | None = None,
777
+ convo_id: str | None = None,
778
+ ) -> ManualSpan:
779
+ """
780
+ Create a manual span that must be explicitly ended with .end().
781
+
782
+ Use this for async/distributed operations where the span lifecycle
783
+ extends beyond a single context manager scope.
784
+
785
+ Args:
786
+ kind: Type of span - "task" or "tool"
787
+ name: Name of the span
788
+ version: Optional version number
789
+ event_id: Optional event_id for tracing association
790
+ user_id: Optional user_id for tracing association
791
+ event: Optional event name for tracing association
792
+ convo_id: Optional conversation ID for tracing association
793
+
794
+ Returns:
795
+ ManualSpan instance (safe to use even if tracing is disabled)
796
+ """
797
+ if not _tracing_enabled or not TracerWrapper.verify_initialized():
798
+ return ManualSpan(None, kind, name, event_id)
799
+
800
+ tlp_kind = (
801
+ TraceloopSpanKindValues.TASK if kind == "task" else TraceloopSpanKindValues.TOOL
802
+ )
803
+ span_name = f"{name}.{tlp_kind.value}"
804
+
805
+ with get_tracer() as tracer:
806
+ span = tracer.start_span(span_name)
807
+
808
+ span.set_attribute(SpanAttributes.TRACELOOP_SPAN_KIND, tlp_kind.value)
809
+ span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_NAME, name)
810
+ if version is not None:
811
+ span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_VERSION, version)
812
+
813
+ # Set association properties directly on the span (not on current context)
814
+ association_props = {
815
+ "event_id": event_id,
816
+ "user_id": user_id,
817
+ "event": event,
818
+ "convo_id": convo_id,
819
+ }
820
+ for key, value in association_props.items():
821
+ if value is not None:
822
+ span.set_attribute(f"traceloop.association.properties.{key}", value)
823
+
824
+ return ManualSpan(span, kind, name, event_id)
825
+
826
+
711
827
  def resume_interaction(event_id: str | None = None) -> Interaction:
712
828
  """Return an Interaction associated with the current trace or given event_id."""
713
829
 
@@ -3,7 +3,9 @@ from typing import (
3
3
  Any,
4
4
  Dict,
5
5
  List,
6
+ Literal,
6
7
  Optional,
8
+ TYPE_CHECKING,
7
9
  Union,
8
10
  Iterator,
9
11
  )
@@ -14,6 +16,9 @@ from .models import Attachment, PartialTrackAIEvent
14
16
  from . import analytics as _core
15
17
  from opentelemetry import context as context_api
16
18
 
19
+ if TYPE_CHECKING:
20
+ from .analytics import ManualSpan
21
+
17
22
 
18
23
  class Interaction:
19
24
  """
@@ -23,6 +28,9 @@ class Interaction:
23
28
 
24
29
  __slots__ = (
25
30
  "_event_id",
31
+ "_user_id",
32
+ "_event",
33
+ "_convo_id",
26
34
  "_analytics",
27
35
  "__weakref__",
28
36
  )
@@ -30,8 +38,14 @@ class Interaction:
30
38
  def __init__(
31
39
  self,
32
40
  event_id: Optional[str] = None,
41
+ user_id: Optional[str] = None,
42
+ event: Optional[str] = None,
43
+ convo_id: Optional[str] = None,
33
44
  ):
34
45
  self._event_id = event_id or str(uuid4())
46
+ self._user_id = user_id
47
+ self._event = event
48
+ self._convo_id = convo_id
35
49
  self._analytics = _core
36
50
 
37
51
  # -- mutators ----------------------------------------------------------- #
@@ -63,6 +77,36 @@ class Interaction:
63
77
  )
64
78
  self._analytics._track_ai_partial(payload)
65
79
 
80
+ def start_span(
81
+ self,
82
+ kind: Literal["task", "tool"],
83
+ name: str,
84
+ version: int | None = None,
85
+ ) -> "ManualSpan":
86
+ """
87
+ Create a manual span tied to this interaction.
88
+
89
+ The span automatically inherits association properties from this interaction
90
+ (event_id, user_id, event, convo_id) for proper tracing.
91
+
92
+ Args:
93
+ kind: Type of span - "task" or "tool"
94
+ name: Name of the span
95
+ version: Optional version number
96
+
97
+ Returns:
98
+ ManualSpan instance that must be explicitly ended with .end()
99
+ """
100
+ return self._analytics.start_span(
101
+ kind,
102
+ name,
103
+ version,
104
+ event_id=self._event_id,
105
+ user_id=self._user_id,
106
+ event=self._event,
107
+ convo_id=self._convo_id,
108
+ )
109
+
66
110
  # convenience
67
111
  @property
68
112
  def id(self) -> str:
@@ -0,0 +1 @@
1
+ VERSION = "0.0.35"
@@ -1 +0,0 @@
1
- VERSION = "0.0.33"
File without changes