Skip to main content

Raindrop Python SDK

The official Python SDK for Raindrop AI — track AI events, collect user signals, and instrument LLM applications with OpenTelemetry-based tracing.

Installation

pip install raindrop-ai

Requires Python 3.10+

Quick Start

import raindrop.analytics as raindrop

raindrop.init(api_key="your-api-key", tracing_enabled=True)

# Track an AI event
raindrop.track_ai(
    user_id="user-123",
    event="chat-completion",
    model="gpt-4",
    input="What is the weather?",
    output="It's sunny and 72°F.",
    convo_id="conv-456",
)

Application Git provenance

The SDK reports the application-under-test commit on events and owning spans as raindrop.app.commit_sha. When known it also reports raindrop.app.commit_dirty; branch reporting is opt-in. This is application Git metadata, not the raindrop-ai package version, and it does not rewrite release or SDK identity fields.

By default, explicit Raindrop environment values are read once at client initialization and a local Git lookup is attempted once on a bounded daemon thread. Event and span calls never run Git or wait for discovery; an operation that starts before discovery finishes keeps an empty metadata snapshot.

Automatic sources are selected without combining identities. A nonempty source_directory or RAINDROP_GIT_SOURCE_DIRECTORY explicitly names the application repository and takes priority over ambient deployment and CI metadata, with no fallback if that repository cannot be read. Otherwise, marked Vercel, Railway, or Heroku deployment metadata is preferred, then the current directory's local Git repository, then marked CI metadata from GitHub Actions, GitLab CI, Buildkite, or CircleCI. Generic unmarked CI variables are ignored. Source directories are resolved to an absolute path when the client is initialized. Automatic SHAs must be full 40- or 64-character hex; dirty state and branch are omitted unless a bounded final HEAD check confirms they describe the reported commit.

Local Git lookup requires nonblocking pipe support and is skipped on Windows with Python earlier than 3.12. Explicit app_git values and supported deployment or CI environment sources remain available on those runtimes.

from raindrop import AppGitOptions, Raindrop

git_options: AppGitOptions = {
    "source_directory": "/srv/my-application",
    "detect_branch": True,
}
rd = Raindrop(api_key="...", app_git=git_options)

# Disable all application-Git enrichment.
rd_without_git = Raindrop(api_key="...", app_git=False)

The same app_git option is accepted by raindrop.analytics.init(). Explicit commit_sha, commit_dirty, and branch values may be supplied in the mapping. Existing canonical operation properties always win, including null, empty, or invalid values:

rd.track_ai(
    user_id="user-123",
    event="chat-completion",
    input="hello",
    properties={"raindrop.app.commit_sha": supplied_revision},
)

Git contexts are bounded by max_queue_size (10,000 by default). Completed operations free their entries. If simultaneously active partial operations overflow that bound, new IDs omit client/default Git context until the client is recreated; explicit per-operation properties still work. Small custom queue limits reach this boundary sooner. This fail-closed behavior prevents an overflowed operation from acquiring a different revision midway through its lifetime and does not block or fail the agent's execution.

Track a plain, non-AI event through the same local queue and batched events/track transport:

raindrop.track(
    user_id="user-123",
    event="cache-hit",
    properties={"region": "us-east-1", "tags": ["hot", "shared"]},
    event_id="event-456",       # optional; generated when omitted
    timestamp="2026-07-13T18:00:00Z",
    attachments=[
        raindrop.Attachment(type="text", value="cached response", role="output")
    ],
)

Plain events have no conversation association — there is no convo_id parameter. To group events into a conversation, use track_ai / begin (partials), which carry convo_id on the AI-data branch.

Instance-based clients (multiple projects in one process)

raindrop.Raindrop is the instance-shaped counterpart of the module API — the same shape as the JS (new Raindrop({...})), Go, Rust, and Java SDKs. Each instance owns its configuration and event pipeline, so one service can route different agents to different projects concurrently:

from raindrop import Raindrop

rd_support = Raindrop(api_key=KEY, project_id="support-agent", tracing_enabled=True)
rd_billing = Raindrop(api_key=KEY, project_id="billing-agent", tracing_enabled=True)

# Everything hangs off the instance — same method names as the module API.
rd_support.track(user_id="u1", event="session-started", properties={"plan": "pro"})
interaction = rd_billing.begin(user_id="u1", event="billing-chat", input="...")
interaction.track_tool(name="invoice_lookup", input=..., output=...)
interaction.finish(output="...")

rd_support.track_ai(user_id="u1", event="support-chat", input="q", output="a")
rd_support.track_signal(event_id=..., name="thumbs_up")

Create instances once at startup and reuse them (see example/fastapi_multi_project.py for a two-agent FastAPI app). Manual events (track_ai, begin/finish, signals, identify) ship on each instance's own connections with its own headers — fully isolated per instance. The module-level API keeps working unchanged; it is simply the default, process-wide instance of the same machinery.

  • rd.track_ai_partial(event) streams an incremental track_ai patch (a PartialTrackAIEvent) into that instance's own buffers — the per-instance counterpart used by integration wrappers that emit partial events. Its read-only rd.write_key property exposes the key backing the instance (e.g. to compare whether two clients are equivalent) without reaching into internals.

Tracing with multiple instances

OpenTelemetry tracing is a process singleton (one tracer provider, one exporter, global auto-instrumentation). The first instance constructed with tracing_enabled=True initializes it; later tracing instances share it. Span routing is per-span: begin() binds the current request/task to its client's project until the matching finish() (which restores the previous binding — abandoned interactions release theirs when garbage collected), and every span started while bound carries a raindrop.project_id attribute that the ingest boundary routes on. For LLM/library calls made outside an interaction, scope them explicitly:

with rd_billing.as_current():
    openai_client.chat.completions.create(...)   # spans route to billing-agent

Two limitations, by design:

  • One tracing key/org per process. Instances created with a different api_key than the pipeline owner's get a constructor warning, and from that point the export guard only ships spans positively attributed to the owner: the foreign client's spans and any unattributed spans (emitted outside begin()/as_current()) are dropped at export — nothing can ride the wrong org's credential. Single-key processes are unaffected (spans stay byte-identical). Manual events always route correctly. Run a second org's tracing in its own process.
  • interaction.track_tool() with bypass_otel_for_tools=True skips the shared pipeline entirely and ships on the instance's own connection — those spans route per instance with no caveats.

Payload size limits

As of 0.0.52, text fields (ai input/output, tool span I/O, LLM span content) are capped at 1,000,000 characters per field by default and truncated with a ...[truncated by raindrop] marker. Fields up to 1M chars — i.e. everything the ingest API accepts today — round-trip unchanged. The cap is enforced before (or during) serialization, so oversized payloads cost the cap — not the payload — on your calling thread, and a single capped ASCII field still fits under the 1 MB event-level ingest limit. Tune it via:

raindrop.init(api_key="...", max_text_field_chars=250_000)   # process-wide default

# Instance clients cap themselves only (never other clients or the default):
rd = Raindrop(api_key="...", max_text_field_chars=100_000)

A stricter OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT env var is still honored for span content. All outbound HTTP carries finite timeouts, and the atexit shutdown flush runs under a 10s deadline so a dead network can never wedge your process exit.

Projects

If your org has a single project, you don't need to do anything — events go to the default Production project automatically. When you have more than one project, route events to a specific one by passing its slug as project_id to init(). When set, every outbound request attaches an X-Raindrop-Project-Id: <slug> header so events land under the named project instead of the org default:

raindrop.init(api_key="your-api-key", project_id="my-project")

The header is attached to every channel — manual events (track_ai, identify, track_signal, partial events, direct tool/trace POSTs), the local Workshop mirror, and auto-instrumented OpenTelemetry spans exported via Traceloop — so all telemetry routes to the same project.

Slugs must match ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$ (lowercase alphanumerics and dashes, not starting or ending with a dash). Invalid values are logged as a warning and ignored — no exception is raised and no header is sent. Omitting project_id (or passing "default") is fully backward compatible: events fall back to your org's default Production project.

Interactions

Use begin() and finish() for multi-step AI workflows:

interaction = raindrop.begin(
    user_id="user-123",
    event="agent-run",
    input="Search for weather data",
    convo_id="conv-456",
)

# Update incrementally
interaction.set_property("region", "us-east")
interaction.add_attachments([
    raindrop.Attachment(type="code", value="print('hello')", language="python")
])

# Complete the interaction
interaction.finish(output="Found weather data for NYC")

Resuming Interactions

Access the current interaction from nested functions:

@raindrop.tool("sentiment_analyzer")
def analyze_sentiment(text: str):
    interaction = raindrop.resume_interaction()
    interaction.set_property("sentiment", "positive")
    return {"sentiment": "positive"}

Decorators

Instrument functions with automatic span creation:

@raindrop.interaction("my_workflow")
def run_workflow():
    ...

@raindrop.task("process_data")
def process():
    ...

@raindrop.tool("search")
def search(query: str):
    ...

Spans

Context Managers

with raindrop.task_span("process_data"):
    result = do_processing()

with raindrop.tool_span("web_search"):
    results = search(query)

Manual Spans

For async or distributed operations where you need explicit control:

span = raindrop.start_span(kind="tool", name="async_search")
span.record_input({"query": "weather"})

# ... later, when the result arrives
span.record_output({"result": "sunny"})
span.end()

Retroactive Tool Logging

Log tool calls after they complete, without wrapping them in spans:

interaction = raindrop.begin(user_id="user-123", event="agent-run")

interaction.track_tool(
    name="web_search",
    input={"query": "weather in NYC"},
    output={"results": ["Sunny, 72°F"]},
    duration_ms=150,
)

interaction.track_tool(
    name="database_query",
    input={"query": "SELECT * FROM users"},
    duration_ms=50,
    error=ConnectionError("Connection timeout"),
)

interaction.finish(output="Done")

Signals

Track user feedback on AI outputs:

# Basic signal
raindrop.track_signal(event_id="evt-123", name="thumbs_up")

# Feedback with comment
raindrop.track_signal(
    event_id="evt-123",
    name="user_feedback",
    signal_type="feedback",
    comment="This answer was helpful",
    sentiment="POSITIVE",
)

# Edit signal
raindrop.track_signal(
    event_id="evt-123",
    name="user_edit",
    signal_type="edit",
    after="The corrected response text",
)

User Identification

raindrop.identify("user-123", traits={"plan": "pro", "company": "Acme"})

PII Redaction

Enable automatic redaction of emails, phone numbers, credit cards, SSNs, and other PII from AI inputs and outputs:

raindrop.set_redact_pii(True)

Auto-Instrumentation

By default, Raindrop auto-instruments detected LLM libraries (OpenAI, Anthropic, Bedrock, etc.) via Traceloop. To disable:

raindrop.init(api_key="your-key", tracing_enabled=True, auto_instrument=False)

Or selectively control which libraries are instrumented:

from raindrop.analytics import Instruments

raindrop.init(
    api_key="your-key",
    tracing_enabled=True,
    instruments={Instruments.OPENAI},
)

Note: 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.

Buffering and Performance

All event-tracking calls (track, track_ai, identify, track_signal, and Interaction.set_input / set_properties / add_attachments / finish) are non-blocking from the caller's perspective. They append to an in-memory buffer that a background daemon thread drains every second by POSTing to the Raindrop API. The HTTP request never runs on the calling thread, so it is safe to call these from a request hot path.

shutdown() is registered via atexit and drains any still-pending events before the process exits. Call flush() explicitly if you need to force a drain at a known point. flush() is a Class-2 operation and may block on network delivery. Plain track() batches use the bounded delivery policy on the caller thread (up to three attempts with backoff), while other event batches retain a single attempt during explicit flush. Do not call flush() from a latency-sensitive hot path. All delivery paths treat non-429 4xx responses as permanent rejections and do not retry them.

# Tune the in-memory buffer size (default 10_000 events)
import raindrop.analytics as raindrop
raindrop.max_queue_size = 500

Configuration

Function Description
init(api_key, tracing_enabled=False, auto_instrument=True) Initialize the SDK (module-level default client)
init(..., project_id="my-project") Route events to a named project via the X-Raindrop-Project-Id header
init(..., app_git=True) Configure application Git provenance; pass False to opt out or an AppGitOptions mapping to override values/discovery
Raindrop(api_key, project_id=..., ...) Independent client instance; multiple projects per process
rd.as_current() Scope auto-instrumented spans in a with block to that instance's project
set_debug_logs(True) Enable debug logging
set_redact_pii(True) Enable PII redaction
flush() / rd.flush() Flush buffered events
shutdown() / rd.shutdown() Graceful shutdown (called automatically on exit)

Environment Variables

Variable Description
TRACELOOP_TRACE_CONTENT Enable/disable content capture (default: "true")
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT Max span attribute value length
RAINDROP_COMMIT_SHA Explicit application commit SHA
RAINDROP_COMMIT_DIRTY Explicit application dirty state (true or false)
RAINDROP_BRANCH Explicit application branch
RAINDROP_GIT_AUTO_DETECT Set to false to disable automatic sources (explicit config/environment still applies)
RAINDROP_GIT_DETECT_BRANCH Set to true to opt into automatic branch discovery
RAINDROP_GIT_SOURCE_DIRECTORY Application source directory used for the one background Git lookup

Development

# Install dependencies
pip install poetry
poetry install

# Run tests
poetry run pytest

# Run with coverage
poetry run pytest --cov=raindrop

# Run specific test file
poetry run pytest tests/test_analytics.py -v

Cross-SDK conformance

Beyond pytest, behavioral parity across Raindrop SDKs is verified by the cross-SDK conformance harness, which replays a shared scenario corpus against every SDK. It runs a fault lane (local capture server, on every PR) and a prod lane (real ingest verified via the public Query API readback). This SDK's thin driver lives in conformance/driver.py (public API only) and known gaps are ratcheted with ticket links in conformance/failures.txt, against a commit-SHA-pinned HARNESS_REF. See AGENTS.md for how to run it and how to add a new integration.

License

MIT

Release files for raindrop-ai 0.0.69

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for raindrop-ai 0.0.69
File Size Uploaded
raindrop_ai-0.0.69.tar.gz 137.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for raindrop-ai 0.0.69
File Interpreter ABI Platform
raindrop_ai-0.0.69-py3-none-any.whl Python 3 none any Details

Total release size: 273.7 kB

Release files / raindrop_ai-0.0.69.tar.gz

Download URL raindrop_ai-0.0.69.tar.gz
Size 137.5 kB
Tags Source
SHA-256 checksum
How to use checksums
00964abd34094b88e54f7803b3a6c30bfbc36dad62208cf7119b9754843812c6
BLAKE2b-256 checksum
How to use checksums
d5cfa76bb191b8448039ed23ac3a733a76a928e314b29e83dfd92e4e4f484553
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / raindrop_ai-0.0.69-py3-none-any.whl

Download URL raindrop_ai-0.0.69-py3-none-any.whl
Size 136.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c5e3f77b14245b36d963a9dc65647f406c5a1e8fd506a83d648b7bbd6d978660
BLAKE2b-256 checksum
How to use checksums
f6f3996eab618aa04a72e7879b75d5011999a58449a8fa2c406791d6d1f59425
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

0.0.69 This release

2 release files

0.0.68

2 release files

0.0.67

2 release files

0.0.65

2 release files

0.0.64

2 release files

0.0.63

2 release files

0.0.62

2 release files

0.0.61

2 release files

0.0.60

2 release files

0.0.58

2 release files

0.0.55

2 release files

0.0.54

2 release files

0.0.53

2 release files

0.0.52

2 release files

0.0.51

2 release files

0.0.50

2 release files

0.0.49

2 release files

0.0.43

2 release files

0.0.39

2 release files

0.0.36

2 release files

0.0.35

2 release files

0.0.34

2 release files

0.0.33

2 release files

0.0.32

2 release files

0.0.31

2 release files

0.0.30

2 release files

0.0.29

2 release files

0.0.28

2 release files

0.0.24

2 release files

0.0.23

2 release files

0.0.21

2 release files

0.0.19

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page