Skip to main content

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

When tracing_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:
When auto-instrumentation is enabled, the SDK automatically suppresses noisy warnings from instrumentors for providers you don’t use (e.g. “Error initializing MistralAI instrumentor”) and from OTel attribute type validation (e.g. provider SDKs using sentinel types like Omit). Enable set_debug_logs(True) to see these messages for troubleshooting.

Projects

Pass project_id to raindrop.init(...) to scope events to a specific project. This sets the X-Raindrop-Project-Id header on each request.
Omitting 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 in raindrop-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:
Each client owns its configuration and delivery pipeline, so concurrent requests handled by different agents route independently — there is no shared mutable project state to race on. The module-level API keeps working unchanged; it is simply the default, process-wide client. Per-instance configuration and lifecycle: 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():
One tracing API key per process: all auto-instrumented spans export under the first tracing-enabled client’s key. Clients created with a different api_key (a different workspace) get a warning, and from then on only spans scoped to a client via begin()/as_current() are exported — the other workspace’s spans and any unscoped spans are dropped at export rather than delivered to the wrong workspace. Run a second workspace’s tracing in its own process. Single-key processes (the normal case) are unaffected. Manual events (track_ai, begin/finish, signals, identify) always ship with each client’s own key and are unaffected.

Tracking AI Interactions

To track AI interactions, you can use the track_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 or output is required if AI data is logged)
  • output (Optional[str]): The output generated by the AI. (Either this or input is 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.
Example usage:

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.
Example of different attachment types:
Each event has a limit of 1 MB. Properties will be truncated for larger events. Contact us if you have custom requirements.

Identifying Users

To associate traits with users, you can use the identify 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.
Example usage:

Partial Event Tracking (Interactions)

For multi-turn conversations or when event data arrives incrementally, you can use the begin() 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-level TrackAIEvent fields to update (e.g., properties, attachments).
The SDK automatically sends updates to the backend after a short period of inactivity or when 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 the track_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 the properties.
    • For "edit" signals, an "after" string (representing the content after edit) must be included in the properties.
  • 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 to properties as {"comment": "your comment"}.
  • after (Optional[str]): Convenience parameter for edit signals. If provided, it’s added to properties as {"after": "new content"}.
Example usage:

Timestamp

For functions like track_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 the flush 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:
This will also flush any pending partial events from interactions.

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 the set_debug_logs function:
That’s it! You should be ready to go. Please let us know if you have any questions.

Tracing

  1. Enable tracing by passing tracing_enabled=True to raindrop.init(...).
  2. Decorate your entry-point function with @raindrop.interaction.
  3. Decorate tool functions with @raindrop.tool.
The example below traces OpenAI tool calls. It enables tracing, decorates a tool, starts an interaction with 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 called begin(...) (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_id is available, a new Interaction instance 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:
  1. Initialize with tracing_enabled=True
  2. Decorate your entry-point function with @raindrop.interaction to establish a tracing context
  3. Use with raindrop.tool_span(...) or with raindrop.task_span(...) for traced blocks
Context managers support both synchronous and asynchronous code. Spans automatically inherit the current trace context and no-op when tracing is disabled. Available methods on the span object:
  • 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
Example:

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.
Parameters:
  • 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.
The span automatically inherits association properties (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. Unlike resume_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.
Python does not have a separate 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:
Send all of 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.
Only accept carriers inside your own trust boundary. A carrier from an untrusted caller can attribute the child to another tenant’s event.
The carrier is the same across Raindrop SDKs, so a hand-off can cross languages: a Python parent can dispatch to a TypeScript worker, and the reverse also works. The worker resumes the parent-minted child event either way.