@raindrop-ai/wizard 0.0.1

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 (135) hide show
  1. package/LICENSE +47 -0
  2. package/dist/bin.d.ts +2 -0
  3. package/dist/bin.js +117 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/src/docs/browser.md +105 -0
  6. package/dist/src/docs/python.md +618 -0
  7. package/dist/src/docs/typescript.md +584 -0
  8. package/dist/src/docs/vercel-ai-sdk.md +304 -0
  9. package/dist/src/lib/agent-interface.d.ts +46 -0
  10. package/dist/src/lib/agent-interface.js +292 -0
  11. package/dist/src/lib/agent-interface.js.map +1 -0
  12. package/dist/src/lib/agent-prompts.d.ts +10 -0
  13. package/dist/src/lib/agent-prompts.js +49 -0
  14. package/dist/src/lib/agent-prompts.js.map +1 -0
  15. package/dist/src/lib/config.d.ts +39 -0
  16. package/dist/src/lib/config.js +549 -0
  17. package/dist/src/lib/config.js.map +1 -0
  18. package/dist/src/lib/constants.d.ts +27 -0
  19. package/dist/src/lib/constants.js +165 -0
  20. package/dist/src/lib/constants.js.map +1 -0
  21. package/dist/src/lib/handlers.d.ts +68 -0
  22. package/dist/src/lib/handlers.js +420 -0
  23. package/dist/src/lib/handlers.js.map +1 -0
  24. package/dist/src/lib/integration-testing.d.ts +44 -0
  25. package/dist/src/lib/integration-testing.js +123 -0
  26. package/dist/src/lib/integration-testing.js.map +1 -0
  27. package/dist/src/lib/mcp.d.ts +14 -0
  28. package/dist/src/lib/mcp.js +134 -0
  29. package/dist/src/lib/mcp.js.map +1 -0
  30. package/dist/src/lib/sdk-messages.d.ts +17 -0
  31. package/dist/src/lib/sdk-messages.js +278 -0
  32. package/dist/src/lib/sdk-messages.js.map +1 -0
  33. package/dist/src/lib/wizard.d.ts +6 -0
  34. package/dist/src/lib/wizard.js +131 -0
  35. package/dist/src/lib/wizard.js.map +1 -0
  36. package/dist/src/run.d.ts +8 -0
  37. package/dist/src/run.js +53 -0
  38. package/dist/src/run.js.map +1 -0
  39. package/dist/src/ui/App.d.ts +15 -0
  40. package/dist/src/ui/App.js +27 -0
  41. package/dist/src/ui/App.js.map +1 -0
  42. package/dist/src/ui/cancellation.d.ts +14 -0
  43. package/dist/src/ui/cancellation.js +17 -0
  44. package/dist/src/ui/cancellation.js.map +1 -0
  45. package/dist/src/ui/components/ClarifyingQuestionsPrompt.d.ts +17 -0
  46. package/dist/src/ui/components/ClarifyingQuestionsPrompt.js +359 -0
  47. package/dist/src/ui/components/ClarifyingQuestionsPrompt.js.map +1 -0
  48. package/dist/src/ui/components/ContinuePrompt.d.ts +14 -0
  49. package/dist/src/ui/components/ContinuePrompt.js +23 -0
  50. package/dist/src/ui/components/ContinuePrompt.js.map +1 -0
  51. package/dist/src/ui/components/DiffDisplay.d.ts +18 -0
  52. package/dist/src/ui/components/DiffDisplay.js +110 -0
  53. package/dist/src/ui/components/DiffDisplay.js.map +1 -0
  54. package/dist/src/ui/components/FeedbackSelectPrompt.d.ts +20 -0
  55. package/dist/src/ui/components/FeedbackSelectPrompt.js +132 -0
  56. package/dist/src/ui/components/FeedbackSelectPrompt.js.map +1 -0
  57. package/dist/src/ui/components/HistoryItemDisplay.d.ts +14 -0
  58. package/dist/src/ui/components/HistoryItemDisplay.js +140 -0
  59. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -0
  60. package/dist/src/ui/components/Logo.d.ts +10 -0
  61. package/dist/src/ui/components/Logo.js +47 -0
  62. package/dist/src/ui/components/Logo.js.map +1 -0
  63. package/dist/src/ui/components/OrgInfoBox.d.ts +11 -0
  64. package/dist/src/ui/components/OrgInfoBox.js +16 -0
  65. package/dist/src/ui/components/OrgInfoBox.js.map +1 -0
  66. package/dist/src/ui/components/PendingPrompt.d.ts +18 -0
  67. package/dist/src/ui/components/PendingPrompt.js +57 -0
  68. package/dist/src/ui/components/PendingPrompt.js.map +1 -0
  69. package/dist/src/ui/components/PersistentTextInput.d.ts +21 -0
  70. package/dist/src/ui/components/PersistentTextInput.js +117 -0
  71. package/dist/src/ui/components/PersistentTextInput.js.map +1 -0
  72. package/dist/src/ui/components/PlanApprovalPrompt.d.ts +19 -0
  73. package/dist/src/ui/components/PlanApprovalPrompt.js +62 -0
  74. package/dist/src/ui/components/PlanApprovalPrompt.js.map +1 -0
  75. package/dist/src/ui/components/PromptContainer.d.ts +14 -0
  76. package/dist/src/ui/components/PromptContainer.js +18 -0
  77. package/dist/src/ui/components/PromptContainer.js.map +1 -0
  78. package/dist/src/ui/components/SelectPrompt.d.ts +14 -0
  79. package/dist/src/ui/components/SelectPrompt.js +62 -0
  80. package/dist/src/ui/components/SelectPrompt.js.map +1 -0
  81. package/dist/src/ui/components/SpinnerDisplay.d.ts +13 -0
  82. package/dist/src/ui/components/SpinnerDisplay.js +11 -0
  83. package/dist/src/ui/components/SpinnerDisplay.js.map +1 -0
  84. package/dist/src/ui/components/ToolApprovalPrompt.d.ts +14 -0
  85. package/dist/src/ui/components/ToolApprovalPrompt.js +142 -0
  86. package/dist/src/ui/components/ToolApprovalPrompt.js.map +1 -0
  87. package/dist/src/ui/components/ToolCallDisplay.d.ts +14 -0
  88. package/dist/src/ui/components/ToolCallDisplay.js +83 -0
  89. package/dist/src/ui/components/ToolCallDisplay.js.map +1 -0
  90. package/dist/src/ui/components/WriteKeyDisplay.d.ts +15 -0
  91. package/dist/src/ui/components/WriteKeyDisplay.js +13 -0
  92. package/dist/src/ui/components/WriteKeyDisplay.js.map +1 -0
  93. package/dist/src/ui/contexts/WizardContext.d.ts +210 -0
  94. package/dist/src/ui/contexts/WizardContext.js +362 -0
  95. package/dist/src/ui/contexts/WizardContext.js.map +1 -0
  96. package/dist/src/ui/hooks/useCancellation.d.ts +15 -0
  97. package/dist/src/ui/hooks/useCancellation.js +25 -0
  98. package/dist/src/ui/hooks/useCancellation.js.map +1 -0
  99. package/dist/src/ui/render.d.ts +34 -0
  100. package/dist/src/ui/render.js +94 -0
  101. package/dist/src/ui/render.js.map +1 -0
  102. package/dist/src/ui/types.d.ts +184 -0
  103. package/dist/src/ui/types.js +6 -0
  104. package/dist/src/ui/types.js.map +1 -0
  105. package/dist/src/utils/clack-utils.d.ts +13 -0
  106. package/dist/src/utils/clack-utils.js +131 -0
  107. package/dist/src/utils/clack-utils.js.map +1 -0
  108. package/dist/src/utils/debug.d.ts +13 -0
  109. package/dist/src/utils/debug.js +47 -0
  110. package/dist/src/utils/debug.js.map +1 -0
  111. package/dist/src/utils/environment.d.ts +5 -0
  112. package/dist/src/utils/environment.js +131 -0
  113. package/dist/src/utils/environment.js.map +1 -0
  114. package/dist/src/utils/logging.d.ts +9 -0
  115. package/dist/src/utils/logging.js +38 -0
  116. package/dist/src/utils/logging.js.map +1 -0
  117. package/dist/src/utils/oauth.d.ts +12 -0
  118. package/dist/src/utils/oauth.js +497 -0
  119. package/dist/src/utils/oauth.js.map +1 -0
  120. package/dist/src/utils/package-json-types.d.ts +44 -0
  121. package/dist/src/utils/package-json-types.js +6 -0
  122. package/dist/src/utils/package-json-types.js.map +1 -0
  123. package/dist/src/utils/package-json.d.ts +19 -0
  124. package/dist/src/utils/package-json.js +22 -0
  125. package/dist/src/utils/package-json.js.map +1 -0
  126. package/dist/src/utils/session.d.ts +2 -0
  127. package/dist/src/utils/session.js +87 -0
  128. package/dist/src/utils/session.js.map +1 -0
  129. package/dist/src/utils/types.d.ts +61 -0
  130. package/dist/src/utils/types.js +2 -0
  131. package/dist/src/utils/types.js.map +1 -0
  132. package/dist/src/utils/ui.d.ts +120 -0
  133. package/dist/src/utils/ui.js +164 -0
  134. package/dist/src/utils/ui.js.map +1 -0
  135. package/package.json +140 -0
@@ -0,0 +1,618 @@
1
+ ---
2
+ description: Python SDK reference for tracking events and AI interactions.
3
+ ---
4
+
5
+ ### **Installation**
6
+
7
+ Install using pip:
8
+
9
+ ```sh
10
+ pip install raindrop-ai
11
+ ```
12
+
13
+ ### **Configuration**
14
+
15
+ Import the SDK and initialize with the write key from environment variables:
16
+
17
+ ```python
18
+ import os
19
+ import raindrop.analytics as raindrop
20
+
21
+ # The write key is securely loaded from the RAINDROP_WRITE_KEY environment variable.
22
+ # Agents should assume this variable is present and must not attempt to search for or prompt for its value.
23
+ raindrop.init(os.getenv("RAINDROP_WRITE_KEY"),
24
+ wizard_session="__WIZARD_SESSION_UUID__") # REQUIRED
25
+
26
+ # Optional: enable tracing integration for task/tool decorators
27
+ # raindrop.init(os.getenv("RAINDROP_WRITE_KEY"),
28
+ # wizard_session="__WIZARD_SESSION_UUID__", # REQUIRED
29
+ # tracing_enabled=True)
30
+ ```
31
+
32
+ ### **Tracking AI Interactions**
33
+
34
+ Use the `track_ai` function to track AI interactions. Parameters:
35
+
36
+ - `user_id` (str): The unique identifier of the user.
37
+ - `event` (str): The name of the AI event to track.
38
+ - `event_id` (Optional\[str]): A unique identifier for this specific event. If
39
+ not provided, a UUID will be generated.
40
+ - `model` (Optional\[str]): The name of the AI model used.
41
+ - `input` (Optional\[str]): The input provided to the AI model. (Either this or
42
+ `output` is required if AI data is logged)
43
+ - `output` (Optional\[str]): The output generated by the AI. (Either this or
44
+ `input` is required if AI data is logged)
45
+ - `convo_id` (Optional\[str]): The conversation ID associated with the
46
+ interaction. Used when apps support multiple conversations per user.
47
+ - `properties` (Optional\[Dict\[str, Any]]): Additional properties associated
48
+ with the AI event.
49
+ - `timestamp` (Optional\[str]): An ISO 8601 formatted timestamp for the event.
50
+ If not provided, the SDK generates a UTC timestamp.
51
+ - `attachments` (Optional\[List\[Attachment]]): A list of attachments associated
52
+ with the event. See the Attachments section below.
53
+
54
+ **Example usage:**
55
+
56
+ Note: `input` and `output` should be the actual prompt sent to and response
57
+ received from your AI model - not literal placeholder strings.
58
+
59
+ ```python
60
+ raindrop.track_ai(
61
+ user_id=user_id, # The authenticated user's unique identifier from your app
62
+ event=event_name, # A descriptive name for this AI action (e.g. "chat_message", "code_generation")
63
+ model=model_name, # The model being called (e.g. "gpt-4o")
64
+ input=user_prompt, # The actual prompt sent to the model
65
+ output=model_response, # The actual response from the model
66
+ convo_id=convo_id, # Your app's conversation/thread ID (if applicable)
67
+ properties={ # Optional: any custom metadata relevant to your app
68
+ "system_prompt": system_prompt,
69
+ },
70
+ attachments=[
71
+ {
72
+ "type": "text",
73
+ "name": "Additional Info",
74
+ "value": "A very long document",
75
+ "role": "input",
76
+ },
77
+ {
78
+ "type": "image",
79
+ "value": "https://example.com/image.png",
80
+ "role": "output",
81
+ },
82
+ {
83
+ "type": "iframe",
84
+ "name": "Generated UI",
85
+ "value": "https://newui.generated.com",
86
+ "role": "output",
87
+ },
88
+ ],
89
+ )
90
+ ```
91
+
92
+ ### **Attachments**
93
+
94
+ Attachments include additional context (images, documents, code, or embedded
95
+ content) as input or output. Each attachment has the following properties:
96
+
97
+ - `type` (string): The type of attachment. Can be "code", "text", "image", or
98
+ "iframe".
99
+ - `name` (optional string): A name for the attachment.
100
+ - `value` (string): The content or URL of the attachment.
101
+ - `role` (string): Either "input" or "output", indicating whether the attachment
102
+ is part of the user input or AI output.
103
+ - `language` (optional string): For code attachments, specifies the programming
104
+ language.
105
+
106
+ Example of different attachment types:
107
+
108
+ ```python
109
+ attachments = [
110
+ {
111
+ "type": "code",
112
+ "name": "Example Code",
113
+ "value": "console.log('Hello, World!');",
114
+ "role": "input",
115
+ "language": "javascript",
116
+ },
117
+ {
118
+ "type": "text",
119
+ "name": "Additional Info",
120
+ "value": "Some extra text",
121
+ "role": "input",
122
+ },
123
+ {"type": "image", "value": "https://example.com/image.png", "role": "output"},
124
+ {"type": "iframe", "value": "https://example.com/embed", "role": "output"},
125
+ ]
126
+ ```
127
+
128
+ ### **Identifying Users**
129
+
130
+ Use the `identify` function to associate traits with users. Parameters:
131
+
132
+ - `user_id` (str): The unique identifier of the user.
133
+ - `traits` (Dict\[str, Union\[str, int, bool, float]]): The traits associated
134
+ with the user.
135
+
136
+ **Example usage:**
137
+
138
+ ```python
139
+ # Pass actual user data from your app's auth/session
140
+ raindrop.identify(
141
+ user_id=user_id, # The authenticated user's unique identifier
142
+ traits={ # Pass any user traits available in your app — all keys are optional and freeform
143
+ "name": user_name,
144
+ "email": user_email,
145
+ }
146
+ )
147
+ ```
148
+
149
+ ### **Partial Event Tracking (Interactions)**
150
+
151
+ For multi-turn conversations or when event data arrives incrementally, use
152
+ `begin()` and the `Interaction` object to send partial updates.
153
+
154
+ #### `begin()`
155
+
156
+ Starts or resumes an interaction and returns an `Interaction` helper object.
157
+
158
+ - `user_id` (str): The user's identifier.
159
+ - `event` (str): The name of the event.
160
+ - `event_id` (Optional\[str]): A unique ID for the event. If not provided, one
161
+ is generated.
162
+ - `properties` (Optional\[Dict\[str, Any]]): Initial properties for the event.
163
+ - `input` (Optional\[str]): Initial input for the AI.
164
+ - `attachments` (Optional\[List\[Attachment]]): Initial attachments.
165
+ - `convo_id` (Optional\[str]): Conversation ID.
166
+
167
+ ```python
168
+ interaction = raindrop.begin(
169
+ user_id=user_id, # The authenticated user's unique identifier from your app
170
+ event=event_name, # A descriptive name for this AI action
171
+ input=user_message # The actual user input
172
+ )
173
+ # interaction.id contains the event_id
174
+ ```
175
+
176
+ #### `resume_interaction()`
177
+
178
+ If you already have an `event_id` for an ongoing interaction, you can get an
179
+ `Interaction` object:
180
+
181
+ ```python
182
+ interaction = raindrop.resume_interaction(event_id=event_id) # The event ID from a previous begin() call
183
+ ```
184
+
185
+ #### `Interaction` Object Methods
186
+
187
+ The `Interaction` object has the following methods to update the event:
188
+
189
+ - `interaction.set_input(text: str)`: Updates the AI input.
190
+ - `interaction.add_attachments(attachments: List[Attachment])`: Adds more
191
+ attachments.
192
+ - `interaction.set_properties(props: Dict[str, Any])`: Merges new properties
193
+ with existing ones.
194
+ - `interaction.set_property(key: str, value: str)`: Convenience for setting a
195
+ single property.
196
+ - `interaction.finish(output: Optional[str] = None, **extra)`: Marks the
197
+ interaction as complete.
198
+ - `output`: The final AI output.
199
+ - `**extra`: Any other top-level `TrackAIEvent` fields to update (e.g.,
200
+ `properties`, `attachments`).
201
+
202
+ The SDK automatically sends updates to the backend after a short period of
203
+ inactivity or when `finish()` is called.
204
+
205
+ **Example usage:**
206
+
207
+ ```python
208
+ # Start an interaction — use actual values from your app
209
+ interaction = raindrop.begin(user_id=user_id, event=event_name, input=user_input)
210
+
211
+ # ... later, user adds more context
212
+ interaction.add_attachments([{"type": "text", "value": "It should be recursive", "role": "input"}])
213
+
214
+ # ... AI generates output
215
+ interaction.finish(output="def fib(n): if n <= 1: return n else: return fib(n-1) + fib(n-2)")
216
+ ```
217
+
218
+ ### **Tracking Signals**
219
+
220
+ Signals are used to attach user feedback (such as thumbs down or thumbs up) or
221
+ other labels to existing events. Use the `track_signal` function:
222
+
223
+ - `event_id` (str): The ID of the event to attach the signal to.
224
+ - `name` (str): Name of the signal (e.g., "thumbs_up", "copied_code").
225
+ - `signal_type` (Literal\["default", "feedback", "edit"]): Type of signal.
226
+ Defaults to `"default"`.
227
+ - For `"feedback"` signals, a `"comment"` string must be included in the
228
+ `properties`.
229
+ - For `"edit"` signals, an `"after"` string (representing the content after
230
+ edit) must be included in the `properties`.
231
+ - `timestamp` (Optional\[str]): ISO 8601 formatted timestamp. Defaults to
232
+ current UTC time.
233
+ - `properties` (Optional\[Dict\[str, Any]]): Additional properties for the
234
+ signal.
235
+ - `attachment_id` (Optional\[str]): ID of a specific attachment within the
236
+ original event to associate this signal with.
237
+ - `sentiment` (Optional\[Literal\["POSITIVE", "NEGATIVE"]]): Optional sentiment
238
+ indicating whether the signal is positive or negative.
239
+ - `comment` (Optional\[str]): Convenience parameter for feedback signals. If
240
+ provided, it's added to `properties` as `{"comment": "your comment"}`.
241
+ - `after` (Optional\[str]): Convenience parameter for edit signals. If provided,
242
+ it's added to `properties` as `{"after": "new content"}`.
243
+
244
+ **Example usage:**
245
+
246
+ ```python
247
+ # Example: Tracking a thumbs-up signal
248
+ raindrop.track_signal(
249
+ event_id=event_id, # The ID of the AI event being rated (from begin() or track_ai())
250
+ name="thumbs_up",
251
+ signal_type="default",
252
+ sentiment="POSITIVE"
253
+ )
254
+
255
+ # Example: Tracking a thumbs-down signal
256
+ raindrop.track_signal(
257
+ event_id=event_id, # The ID of the AI event being rated (from begin() or track_ai())
258
+ name="thumbs_down",
259
+ signal_type="default",
260
+ sentiment="NEGATIVE"
261
+ )
262
+
263
+ # Example: Tracking feedback
264
+ raindrop.track_signal(
265
+ event_id=event_id,
266
+ name="user_feedback",
267
+ signal_type="feedback",
268
+ comment=user_comment # The user's feedback comment
269
+ )
270
+ ```
271
+
272
+ ### **Timestamp**
273
+
274
+ Functions like `track_ai` and `track_signal` accept an optional `timestamp`
275
+ parameter (ISO 8601 formatted string) to specify a custom event time. If not
276
+ provided, the SDK generates a UTC timestamp at the moment of the call.
277
+
278
+ ### **Flushing Events**
279
+
280
+ The Raindrop SDK uses a buffering mechanism to efficiently send events in
281
+ batches. The events are automatically flushed when the buffer reaches a certain
282
+ size or after a specified timeout.
283
+
284
+ You can manually flush the events by calling the `flush` function. Make sure
285
+ this happens before the process exits or you will lose events:
286
+
287
+ ```python
288
+ raindrop.flush()
289
+ ```
290
+
291
+ ### Shutting Down
292
+
293
+ To ensure all events are processed before your application exits, call the
294
+ shutdown function:
295
+
296
+ ```python
297
+ raindrop.shutdown()
298
+ ```
299
+
300
+ This will also flush any pending partial events from interactions.
301
+
302
+ ### **Error Handling**
303
+
304
+ The SDK will retry a request up to 3 times. Failed requests will be logged,
305
+ regardless of if debug_logs is true.
306
+
307
+ ### Configuration
308
+
309
+ The SDK has several configurable parameters:
310
+
311
+ - `max_queue_size`: Maximum number of events to store in the buffer (default:
312
+ 10_000)
313
+ - `upload_size`: Number of events to send in a single API request (default: 10)
314
+ - `upload_interval`: Time interval in seconds between automatic flushes
315
+ (default: 1.0). You can modify these parameters if needed:
316
+
317
+ ```python
318
+ raindrop.max_queue_size = 20_000
319
+ raindrop.upload_size = 200
320
+ raindrop.upload_interval = 2.0
321
+ ```
322
+
323
+ ### **Debugging**
324
+
325
+ To enable debug logs showing events being added to the buffer:
326
+
327
+ ```python
328
+ raindrop.set_debug_logs(True)
329
+ ```
330
+
331
+ ### **Tracing (Beta)**
332
+
333
+ 1. Enable tracing by passing `tracing_enabled=True` to `raindrop.init(...)`.
334
+ 2. Decorate the entry-point function with `@raindrop.interaction`.
335
+ 3. Decorate tool functions with `@raindrop.tool`.
336
+
337
+ The example below traces OpenAI tool calls. It enables tracing, decorates a
338
+ tool, starts an interaction with `begin(...)`, and finishes it later via
339
+ `resume_interaction()`.
340
+
341
+ ```python
342
+ import json
343
+ import os
344
+ from openai import OpenAI
345
+ import raindrop.analytics as raindrop
346
+
347
+ @raindrop.tool("get_current_weather")
348
+ def get_current_weather(location: str, unit: str = "celsius"):
349
+ """Mock weather tool."""
350
+ return {"location": location, "temperature": 22, "unit": unit}
351
+
352
+ def send_to_user(text: str) -> None:
353
+ # Resume the current interaction from the tracing context and finish elsewhere
354
+ # Pass the actual model response to output (not a placeholder string)
355
+ raindrop.resume_interaction().finish(output=text)
356
+ print(f"Sending to user: {text}")
357
+
358
+ @raindrop.interaction(interaction_name) # A descriptive name for this interaction flow
359
+ def main() -> None:
360
+ client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
361
+
362
+ # Create an interaction for observability (begin → finish)
363
+ # Note: input should be the actual user prompt sent to the model
364
+ user_query = "What's the weather in Boston, MA today?"
365
+ interaction = raindrop.begin(
366
+ user_id=user_id, # The authenticated user's unique identifier from your app
367
+ event=event_name, # A descriptive name for this AI action
368
+ input=user_query, # Pass the actual user input
369
+ convo_id=convo_id, # Your app's conversation/thread ID (if applicable)
370
+ )
371
+
372
+ messages = [
373
+ {"role": "system", "content": "You are helpful. Use tools when needed."},
374
+ {"role": "user", "content": user_query},
375
+ ]
376
+
377
+ # Let the model request tool invocations if needed
378
+ first = client.chat.completions.create(
379
+ model="gpt-4o-mini",
380
+ messages=messages,
381
+ tools=[{
382
+ "type": "function",
383
+ "function": {
384
+ "name": "get_current_weather",
385
+ "parameters": {
386
+ "type": "object",
387
+ "properties": {
388
+ "location": {"type": "string"},
389
+ "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
390
+ },
391
+ "required": ["location"],
392
+ },
393
+ },
394
+ }],
395
+ tool_choice="auto",
396
+ temperature=0.2,
397
+ )
398
+
399
+ choice = first.choices[0]
400
+ tool_calls = getattr(choice.message, "tool_calls", None)
401
+
402
+ if tool_calls:
403
+ messages.append({
404
+ "role": "assistant",
405
+ "content": getattr(choice.message, "content", None),
406
+ "tool_calls": [{
407
+ "id": tc.id,
408
+ "type": "function",
409
+ "function": {"name": tc.function.name, "arguments": tc.function.arguments},
410
+ } for tc in tool_calls],
411
+ })
412
+
413
+ for tc in tool_calls:
414
+ args = json.loads(tc.function.arguments or "{}")
415
+ result = (
416
+ get_current_weather(**args)
417
+ if tc.function.name == "get_current_weather"
418
+ else {"error": "unknown tool"}
419
+ )
420
+ messages.append({
421
+ "role": "tool",
422
+ "tool_call_id": tc.id,
423
+ "name": tc.function.name,
424
+ "content": json.dumps(result),
425
+ })
426
+
427
+ # Final model response after tools
428
+ second = client.chat.completions.create(
429
+ model="gpt-4o-mini", messages=messages, temperature=0.2
430
+ )
431
+ final_text = second.choices[0].message.content or ""
432
+ else:
433
+ final_text = choice.message.content or ""
434
+
435
+ print("Assistant:\n", final_text)
436
+ send_to_user(final_text)
437
+ raindrop.flush()
438
+ raindrop.shutdown()
439
+
440
+ if __name__ == "__main__":
441
+ raindrop.init(os.getenv("RAINDROP_WRITE_KEY"),
442
+ wizard_session="__WIZARD_SESSION_UUID__", # REQUIRED
443
+ tracing_enabled=True)
444
+ main()
445
+ ```
446
+
447
+ #### Using the `@raindrop.interaction()` decorator (Beta)
448
+
449
+ You can wrap your flow with the `@raindrop.interaction("name")` decorator to
450
+ ensure a tracing context exists, which allows `resume_interaction()` to find the
451
+ current Interaction without passing an `event_id`:
452
+
453
+ ```python
454
+ import os
455
+ import raindrop.analytics as raindrop
456
+
457
+ raindrop.init(os.getenv("RAINDROP_WRITE_KEY"),
458
+ wizard_session="__WIZARD_SESSION_UUID__", # REQUIRED
459
+ tracing_enabled=True)
460
+
461
+ def send_to_user():
462
+ interaction = raindrop.resume_interaction()
463
+ interaction.finish(output=model_response) # The actual model response
464
+
465
+ @raindrop.interaction(interaction_name) # A descriptive name for this interaction flow
466
+ def run_weather_flow():
467
+ interaction = raindrop.begin(user_id=user_id, event=event_name, input=user_query)
468
+ send_to_user()
469
+
470
+
471
+ run_weather_flow()
472
+ ```
473
+
474
+ <Note>
475
+ <strong>Resume caveats:</strong>
476
+ - `resume_interaction()` resolves the current Interaction by reading the active tracing context (current span). It will only find an existing Interaction when called under the same traced execution that called `begin(...)` (for example, inside a function decorated with `@raindrop.task`, `@raindrop.tool`, or `@raindrop.interaction`).
477
+ - If your code runs outside that tracing context (separate thread/process, lost OpenTelemetry context, background job, etc.), pass the event ID explicitly: `resume_interaction(event_id="...")`.
478
+ - If neither a matching trace context nor an `event_id` is available, a new `Interaction` instance will be created.
479
+ </Note>
480
+
481
+ #### Using the `tool_span` and `task_span` context managers (Beta)
482
+
483
+ When decorators aren't feasible (e.g., dynamic tool selection, complex control
484
+ flow), you can use context managers for fine-grained tracing control.
485
+
486
+ **Requirements:**
487
+
488
+ 1. Initialize with `tracing_enabled=True`
489
+ 2. Decorate your entry-point function with `@raindrop.interaction` to establish
490
+ a tracing context
491
+ 3. Use `with raindrop.tool_span(...)` or `with raindrop.task_span(...)` for
492
+ traced blocks
493
+
494
+ Context managers support both synchronous and asynchronous code. Spans
495
+ automatically inherit the current trace context and no-op when tracing is
496
+ disabled.
497
+
498
+ **Available methods on the span object:**
499
+
500
+ - `record_input(data)`: Record input data for the span
501
+ - `record_output(data)`: Record output data for the span
502
+ - `set_properties(props)`: Set custom properties on the span
503
+
504
+ **Example:**
505
+
506
+ ```python
507
+ import asyncio
508
+ import os
509
+ import raindrop.analytics as raindrop
510
+ import time
511
+
512
+ raindrop.init(os.getenv("RAINDROP_WRITE_KEY"),
513
+ wizard_session="__WIZARD_SESSION_UUID__", # REQUIRED
514
+ tracing_enabled=True)
515
+
516
+ @raindrop.task(task_name) # A descriptive name for this task
517
+ async def main():
518
+ interaction = raindrop.begin(
519
+ user_id=user_id, # The authenticated user's unique identifier from your app
520
+ event=event_name, # A descriptive name for this AI action
521
+ input=user_query # Pass the actual user input
522
+ )
523
+
524
+ # Synchronous tool call: web_search
525
+ with raindrop.tool_span("web_search", version=1) as tool:
526
+ tool.record_input({"query": user_query}) # Pass actual tool call param value
527
+ time.sleep(0.1) # Simulate API call
528
+ search_results = ["https://docs.example.com/api", "https://blog.example.com/tutorial"]
529
+ tool.set_properties({"results_count": len(search_results)})
530
+ tool.record_output({"urls": search_results})
531
+
532
+ # Async tool call: document_reranker
533
+ async with raindrop.tool_span("document_reranker") as tool:
534
+ tool.record_input({
535
+ "documents": search_results,
536
+ "query": user_query
537
+ })
538
+ await asyncio.sleep(0.05) # Simulate async reranking
539
+ best_doc = search_results[0]
540
+ tool.record_output({"top_document": best_doc})
541
+
542
+ # Nested task span for summarization
543
+ with raindrop.task_span("summarization") as task:
544
+ task.record_input({"document": best_doc})
545
+ summary = call_summarization_model(best_doc)
546
+ task.record_output({"summary": summary})
547
+
548
+ interaction.finish(output=summary) # Pass actual model response
549
+ raindrop.shutdown()
550
+
551
+ if __name__ == "__main__":
552
+ asyncio.run(main())
553
+ ```
554
+
555
+ #### Using `interaction.start_span()` for Manual Spans (Beta)
556
+
557
+ When the span lifecycle doesn't fit within a `with` block (e.g., the span starts
558
+ in one function and ends in another), use `interaction.start_span()` to create a
559
+ `ManualSpan` with an explicit `.end()` call.
560
+
561
+ ```python
562
+ span = interaction.start_span(kind="tool", name="my_tool")
563
+ ```
564
+
565
+ **Parameters:**
566
+
567
+ - `kind` (Literal["task", "tool"]): The type of span.
568
+ - `name` (str): Name of the span.
569
+ - `version` (Optional[int]): Version number for the span.
570
+
571
+ **`ManualSpan` methods:**
572
+
573
+ - `record_input(data)`: Record input data for the span.
574
+ - `record_output(data)`: Record output data for the span.
575
+ - `set_properties(props)`: Set custom properties on the span.
576
+ - `end(error=None)`: End the span. Pass an exception to mark it as failed.
577
+
578
+ **`ManualSpan` properties:**
579
+
580
+ - `event_id`: The interaction's event_id.
581
+
582
+ The span automatically inherits association properties (`event_id`, `user_id`,
583
+ `event`, `convo_id`) from the interaction.
584
+
585
+ **Example:**
586
+
587
+ ```python
588
+ import os
589
+ import raindrop.analytics as raindrop
590
+
591
+ raindrop.init(os.getenv("RAINDROP_WRITE_KEY"),
592
+ wizard_session="__WIZARD_SESSION_UUID__", # REQUIRED
593
+ tracing_enabled=True)
594
+
595
+ @raindrop.interaction(interaction_name) # A descriptive name for this interaction flow
596
+ def main():
597
+ user_input = get_user_input() # Your actual user input
598
+ interaction = raindrop.begin(user_id=user_id, event=event_name, input=user_input)
599
+
600
+ # Start a span
601
+ span = interaction.start_span(kind="tool", name="external_api")
602
+ span.record_input({"query": api_query}) # Pass actual query data
603
+
604
+ try:
605
+ result = call_external_api()
606
+ span.record_output(result)
607
+ span.end()
608
+ except Exception as e:
609
+ span.end(error=e)
610
+
611
+ interaction.finish(output=final_response) # Pass actual model response
612
+ raindrop.shutdown()
613
+ ```
614
+
615
+ ### **Troubleshooting**
616
+
617
+ - Each event has a limit of 1 MB. Properties will be truncated for larger
618
+ events.