Installation
Install with your package manager of choice:Configuration
First, import the SDK and initialize it with your write key. You’ll see your write key when you log into app.raindrop.ai.Auto-instrumentation
Whentracing_enabled=True, the SDK automatically instruments detected LLM
client libraries (OpenAI, Anthropic, Bedrock, etc.) so their calls appear as
spans in your traces.
You can use the Instruments enum to control which libraries are instrumented:
Projects
Passproject_id to raindrop.init(...) to scope events to a specific project. This sets the X-Raindrop-Project-Id header on each request.
project_id (or passing "default") sends to the default Production project, which is the existing behavior. See Projects for isolation, archival, and the full behavior table.
Multiple projects in one process
Available inraindrop-ai>=0.0.56. When one service runs several agents that
should report to different projects — e.g. a FastAPI app serving a support
agent and a billing agent — create one Raindrop client per project instead
of using the module-level init(). This is the same instance-based shape as
the TypeScript, Go, Rust, and
Java SDKs:
rd.flush() / rd.shutdown()
drain that client only (an atexit hook drains every client at process
exit under one shared deadline); Raindrop(max_text_field_chars=...) caps
that client’s payload fields without affecting other clients (module-level
init(max_text_field_chars=...) still sets the process-wide default,
1,000,000 characters per field); and bypass_otel_for_tools=True ships
interaction.track_tool() spans on the client’s own connection, so they
route per instance with no shared-pipeline caveats.
Tracing across multiple projects. OpenTelemetry auto-instrumentation is
shared per process: the first client constructed with tracing_enabled=True
initializes it, and later clients share the pipeline. begin() scopes spans
produced in the current request/task to its client’s project until the
matching finish() (an interaction that is never finished releases its
scope once garbage-collected). For LLM calls made outside an interaction,
scope them explicitly with as_current():
Tracking AI Interactions
To track AI interactions, you can use thetrack_ai function. It takes the following parameters:
user_id(str): The unique identifier of the user.event(str): The name of the AI event you want to track.event_id(Optional[str]): A unique identifier for this specific event. If not provided, a UUID will be generated.model(Optional[str]): The name of the AI model used.input(Optional[str]): The input provided to the AI model. (Either this oroutputis required if AI data is logged)output(Optional[str]): The output generated by the AI. (Either this orinputis required if AI data is logged)convo_id(Optional[str]): The conversation ID associated with the interaction. Helpful in AI apps with multiple conversations per user (e.g. ChatGPT).properties(Optional[Dict[str, Any]]): Additional properties associated with the AI event.timestamp(Optional[str]): An ISO 8601 formatted timestamp for the event. If not provided, the SDK generates a UTC timestamp.attachments(Optional[List[Attachment]]): A list of attachments associated with the event. See the Attachments section below.
Attachments
Attachments allow you to include context from the user (e.g. an attached image), or stuff that the model outputted (whether that is an image, document, code, or even an entire web page). Each attachment is an object with the following properties:type(string): The type of attachment. Can be “code”, “text”, “image”, or “iframe”.name(optional string): A name for the attachment.value(string): The content or URL of the attachment.role(string): Either “input” or “output”, indicating whether the attachment is part of the user input or AI output.language(optional string): For code attachments, specifies the programming language.
Identifying Users
To associate traits with users, you can use theidentify function. It takes the following parameters:
user_id(str): The unique identifier of the user.traits(Dict[str, Union[str, int, bool, float]]): The traits associated with the user.
Partial Event Tracking (Interactions)
For multi-turn conversations or when event data arrives incrementally, you can use thebegin() and Interaction object to send partial updates.
begin()
Starts or resumes an interaction and returns an Interaction helper object.
user_id(str): The user’s identifier.event(str): The name of the event.event_id(Optional[str]): A unique ID for the event. If not provided, one is generated.properties(Optional[Dict[str, Any]]): Initial properties for the event.input(Optional[str]): Initial input for the AI.attachments(Optional[List[Attachment]]): Initial attachments.convo_id(Optional[str]): Conversation ID.
resume_interaction()
If you already have an event_id for an ongoing interaction, you can get an Interaction object:
Interaction Object Methods
The Interaction object has the following methods to update the event:
interaction.set_input(text: str): Updates the AI input.interaction.add_attachments(attachments: List[Attachment]): Adds more attachments.interaction.set_properties(props: Dict[str, Any]): Merges new properties with existing ones.interaction.set_property(key: str, value: str): Convenience for setting a single property.interaction.finish(output: Optional[str] = None, **extra): Marks the interaction as complete.output: The final AI output.**extra: Any other top-levelTrackAIEventfields to update (e.g.,properties,attachments).
finish() is called.
Example usage:
Tracking Signals
Signals are used to attach user feedback (such as thumbs down or thumbs up) or other labels to existing events. Use thetrack_signal function:
event_id(str): The ID of the event to attach the signal to.name(str): Name of the signal (e.g., “thumbs_up”, “copied_code”).signal_type(Literal[“default”, “feedback”, “edit”]): Type of signal. Defaults to"default".- For
"feedback"signals, a"comment"string must be included in theproperties. - For
"edit"signals, an"after"string (representing the content after edit) must be included in theproperties.
- For
timestamp(Optional[str]): ISO 8601 formatted timestamp. Defaults to current UTC time.properties(Optional[Dict[str, Any]]): Additional properties for the signal.attachment_id(Optional[str]): ID of a specific attachment within the original event to associate this signal with.sentiment(Optional[Literal[“POSITIVE”, “NEGATIVE”]]): Optional sentiment indicating whether the signal is positive or negative.comment(Optional[str]): Convenience parameter for feedback signals. If provided, it’s added topropertiesas{"comment": "your comment"}.after(Optional[str]): Convenience parameter for edit signals. If provided, it’s added topropertiesas{"after": "new content"}.
Timestamp
For functions liketrack_ai and track_signal, you can optionally provide a timestamp parameter (an ISO 8601 formatted string) if you need to specify a custom time for the event. If not provided, the SDK generates a UTC timestamp at the moment of the call.
Flushing Events
The Raindrop SDK uses a buffering mechanism to efficiently send events in batches. The events are automatically flushed when the buffer reaches a certain size or after a specified timeout. You can manually flush the events by calling theflush function. Make sure this happens before the process exits or you will lose events:
Shutting Down
To ensure all events are processed before your application exits, call the shutdown function:Error Handling
The SDK will retry a request up to 3 times. Failed requests will be logged, regardless of if debug_logs is true.Configuration
The SDK has several configurable parameters:max_queue_size: Maximum number of events to store in the buffer (default: 10_000)upload_size: Number of events to send in a single API request (default: 10)upload_interval: Time interval in seconds between automatic flushes (default: 1.0). You can modify these parameters if needed:
Debugging
If you want to enable debug logs to see the events being added to the buffer, you can use theset_debug_logs function:
Tracing
- Enable tracing by passing
tracing_enabled=Truetoraindrop.init(...). - Decorate your entry-point function with
@raindrop.interaction. - Decorate tool functions with
@raindrop.tool.
begin(...), and finishes it later via resume_interaction().
Using the @raindrop.interaction() decorator
You can wrap your flow with the @raindrop.interaction("name") decorator to ensure a tracing context exists, which allows resume_interaction() to find the current Interaction without passing an event_id:
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 calledbegin(...)(for example, inside a function decorated with@raindrop.task,@raindrop.tool, or@raindrop.interaction).- 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="..."). - If neither a matching trace context nor an
event_idis available, a newInteractioninstance will be created.
Using the tool_span and task_span context managers
When decorators aren’t feasible (e.g., dynamic tool selection, complex control flow), you can use context managers for fine-grained tracing control.
Requirements:
- Initialize with
tracing_enabled=True - Decorate your entry-point function with
@raindrop.interactionto establish a tracing context - Use
with raindrop.tool_span(...)orwith raindrop.task_span(...)for traced blocks
record_input(data): Record input data for the spanrecord_output(data): Record output data for the spanset_properties(props): Set custom properties on the span
Using interaction.start_span() for Manual Spans
When the span lifecycle doesn’t fit within a with block (e.g., the span starts in one function and ends in another), use interaction.start_span() to create a ManualSpan with an explicit .end() call.
kind(Literal[“task”, “tool”]): The type of span.name(str): Name of the span.version(Optional[int]): Version number for the span.
ManualSpan methods:
record_input(data): Record input data for the span.record_output(data): Record output data for the span.set_properties(props): Set custom properties on the span.end(error=None): End the span. Pass an exception to mark it as failed.
ManualSpan properties:
event_id: The interaction’s event_id.
event_id, user_id, event, convo_id) from the interaction.
Example:
Retroactive Tool Logging with interaction.track_tool()
Use track_tool to log a tool call after it has completed:
If your OTEL exporter pipeline is unavailable or flaky, set
bypass_otel_for_tools=True in raindrop.init(...) to ship only
interaction.track_tool() spans directly to POST /v1/traces.
Detached sub-agents (Beta)
A detached sub-agent runs asynchronously in another process, worker, or machine. Unlikeresume_interaction(), which joins the parent’s event, it reports as its
own event. The parent mints the child event and sends a hand-off carrier; the
worker resumes that event and reports its result.
Beta. The detached sub-agent API surface may change in a minor release.
Requires
raindrop-ai>=0.0.65, the first published version containing these
symbols.withSubagentRun() helper. The idiomatic form
is the SubagentRun context manager shown below.
1
Dispatch from the parent
Each process creates its own
Raindrop instance. The parent allocates the
child event before sending the job:2
Send the hand-off
subagent() records telemetry and returns a dispatch object; it does not
transport the job. Send the job and the carrier separately:dispatch.headers, not only traceparent. The carrier
includes traceparent, baggage, and x-raindrop-handoff; cherry-picking
only traceparent silently unlinks the child. dispatch.langsmith_headers
is also available when the worker uses LangSmith-style instrumentation.The child event ID is allocated before dispatch, so the link is resolvable
while the job is queued. The dispatch span is named launch_subagent by
default; pass tool_name to choose another name.3
Resume in the worker
The carrier travels with the job. Use the context manager as the worker’s
entry point:
run.interaction is the child’s ordinary interaction object. Every span
emitted while the run is open carries the reverse reference to the parent
dispatch. If the worker exits the block without reporting an outcome, the
context manager closes the run with an abort outcome instead of leaving the
parent stuck in queued. If the body raises, it reports an abort and
re-raises the exception. A cancellation exception is reported as cancelled
rather than failed.4
Report the outcome
Use the method that matches the worker’s result:
finish() reports the child’s output. fail() marks the child’s telemetry
as errored and synthesizes output from the reason. cancel() writes
raindrop.handoff.terminal="cancelled" on the child event without marking
the run as failed. A run that reports no outcome is what the context manager
rescues on exit.