raindrop-pydantic-ai
Raindrop integration for Pydantic AI. Automatically captures Agent run() and run_sync() calls including input, output, model name, token usage, and finish reason.
Installation
pip install raindrop-pydantic-ai pydantic-ai
Quick Start
from raindrop_pydantic_ai import RaindropPydanticAI
from pydantic_ai import Agent
raindrop = RaindropPydanticAI(
api_key="your-write-key",
user_id="user-123",
)
agent = Agent("openai:gpt-4o", system_prompt="Be helpful")
raindrop.wrap(agent)
result = agent.run_sync("What is the capital of France?")
print(result.output)
raindrop.flush()
Async Usage
import asyncio
from raindrop_pydantic_ai import RaindropPydanticAI
from pydantic_ai import Agent
raindrop = RaindropPydanticAI(api_key="rk_...", user_id="user-123")
agent = Agent("openai:gpt-4o")
raindrop.wrap(agent)
async def main():
result = await agent.run("What is the capital of France?")
print(result.output)
raindrop.flush()
asyncio.run(main())
Factory Function (Legacy)
The create_raindrop_pydantic_ai() factory function is still available for backwards compatibility:
from raindrop_pydantic_ai import create_raindrop_pydantic_ai
raindrop = create_raindrop_pydantic_ai(api_key="rk_...", user_id="user-123")
Projects
Route events to a specific project by passing its slug as project_id:
raindrop = RaindropPydanticAI(
api_key="your-write-key",
project_id="support-prod",
)
project_id sets the X-Raindrop-Project-Id header on every event. Omit it (or pass "default") to use your org's default Production project, which is the existing behavior. The same option is accepted by the create_raindrop_pydantic_ai(...) factory. Invalid slugs are ignored with a warning and no header is sent.
What Gets Captured
- Agent runs: input prompt, output text (including structured output), model name
- Token usage:
input_tokensandoutput_tokensfrom the result - Finish reason:
pydantic_ai.finish_reasoncaptured from the last model response (e.g."stop","length","tool_call") - Errors: error type and message captured in event properties, then re-raised
- Async support: both
run()(async) andrun_sync()(sync) are instrumented - Double-wrap guard: calling
wrap()twice on the same agent is a safe no-op
API Reference
RaindropPydanticAI(api_key, user_id=None, convo_id=None, project_id=None, tracing_enabled=True, bypass_otel_for_tools=True, disable_auto_instrument=True, debug=False)
Create a new Raindrop wrapper instance.
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str | None |
None |
Raindrop API key. When None or empty, telemetry is disabled |
user_id |
str | None |
None |
Associate all events with a user |
convo_id |
str | None |
None |
Group events into a conversation |
project_id |
str | None |
None |
Route events to a specific project (slug); omit for the default Production project |
tracing_enabled |
bool |
True |
Enable/disable tracing in raindrop.init() |
bypass_otel_for_tools |
bool |
True |
Bypass OpenTelemetry for tool calls |
disable_auto_instrument |
bool |
True |
Library auto-instrumentation is opt-in (see below) |
debug |
bool |
False |
Enable DEBUG-level logging for the package |
Library auto-instrumentation is opt-in
As of 0.0.4, disable_auto_instrument defaults to True: the
integration no longer lets Traceloop monkey-patch every LLM client library
it recognizes in your process (OpenAI, Anthropic, botocore, google-genai, etc.). The
wrapper captures input/output, token usage, model name, and finish_reason
directly from run() / run_sync() results, so no library patching is needed for full
dashboards.
If you specifically want LLM-call-level spans from library instrumentation
and have verified compatibility in your environment, opt back in with
disable_auto_instrument=False.
Debug Mode
raindrop = RaindropPydanticAI(
api_key="rk_...",
debug=True, # enables DEBUG-level logging
)
identify(user_id, traits=None)
Identify a user with optional traits:
raindrop.identify("user-123", traits={"name": "Alice", "plan": "pro", "age": 30})
track_signal(event_id, name, signal_type="default", ...)
Track a signal event (feedback, edits, etc.):
raindrop.track_signal(
event_id="evt-abc",
name="thumbs_up",
signal_type="feedback",
sentiment="POSITIVE",
comment="Great answer!",
)
flush() / shutdown()
Flush pending events before your process exits:
raindrop.flush() # flush pending data
raindrop.shutdown() # flush + release resources
Methods
| Method | Description |
|---|---|
wrap(agent) |
Instrument a Pydantic AI Agent |
flush() |
Flush pending events to Raindrop |
shutdown() |
Flush and shut down the client |
identify(user_id, traits=None) |
Identify a user with optional traits |
track_signal(event_id, name, signal_type="default", ...) |
Track a custom signal event |
Tool catalog capture (ai.prompt.tools)
Every model span records the tools Pydantic AI handed the model on that request as ai.prompt.tools: an OTel string array, one JSON document per tool with name, description and the full JSON Schema as inputSchema ({"type": "function", ...}), or {"type": "provider-defined", "name": "web_search"} for native tools such as WebSearchTool. The capture is exact, per request: Pydantic AI passes every Model.request() / Model.request_stream() a ModelRequestParameters whose function_tools, output_tools and native_tools are precisely what goes into the provider request. wrap(agent) wraps those two methods on the agent's model (and on a Model instance passed to run(model=...)) and binds that list through the core's prompt_tools_scope mechanism for the duration of the request, so the provider span started inside it carries the attribute. A request with no tools records [].
Model spans come from the OpenLLMetry instrumentor of the underlying provider client (the OpenAI instrumentor for OpenAIChatModel), which only runs with disable_auto_instrument=False. OpenLLMetry 0.62 writes the same list to gen_ai.tool.definitions, not llm.request.functions.*, so raindrop-ai does not rebuild ai.prompt.tools from the instrumentor; the wrapper-side scope is required. With the default disable_auto_instrument=True there is no model span at all, and nothing to carry the attribute. Pydantic AI's own instrument=True spans are not model spans in this sense and do not receive the attribute.
Model resolution: an agent created with a "provider:model" string has that string resolved to a Model once, on the first wrapped run, so agent.model becomes the resolved instance; a string passed per run (run(model="openai:...")) is resolved the same way and the wrapped instance is handed to run() in its place. A Model instance shared by several agents is wrapped once and still reports each agent's own tools, because the list is read from the request, not from the agent. A tools= override is scoped to the run it belongs to: a wrapped agent run from inside another wrapped agent's tool still reports its own list (or its own override), not the outer agent's. An explicit raindrop.prompt_tools(...) scope, whether opened around the run or from inside one of its tools, still wins over the inferred list.
Override:
rd = RaindropPydanticAI(api_key="...", user_id="u1", disable_auto_instrument=False,
tools=[{"type": "function", "name": "get_weather", "inputSchema": {...}}])
agent = rd.wrap(Agent(model)) # constructor default applies to every run
agent = rd.wrap(Agent(model), tools=[...]) # per-wrap value replaces the default
tools=[] at either level records an empty catalog. An enclosing raindrop.prompt_tools(...) / begin(..., tools=...) scope is also treated as an override and is never shadowed by the request's list.
Semantics shared with every Raindrop SDK:
- Attribute absent: the wrapper could not see the tool list (or there is no model span, see below).
[]: the model had no tools.- Override replaces, never merges. A
tools=you pass wins over anything inferred;tools=[]records an empty catalog. - Content gate. Tool definitions are prompt content. With
TRACELOOP_TRACE_CONTENT=falsenothing is recorded, overrides included. - Malformed entries are skipped with a debug log; nothing here raises into your application.
Accepted shapes for tools=: canonical {"type": "function", "name", "description", "inputSchema"}, OpenAI {"type": "function", "function": {...}}, Anthropic {"name", "description", "input_schema"}, and {"type": "provider-defined", "name", ...}. The core normalises them; the JSON Schema is kept exactly as given.
Needs raindrop-ai>=0.0.70; on an older core the wrapper logs at debug and records nothing.
Application Git metadata
RaindropPydanticAI(...) and create_raindrop_pydantic_ai(...) accept the keyword-only app_git option. It defaults to True: explicit Raindrop Git environment or deployment context is applied immediately, and the base SDK may perform one bounded background local-Git lookup from the process working directory. Event capture, flush, and shutdown never wait for that lookup. Pass False to disable enrichment, or pass an AppGitOptions mapping with commit_sha, commit_dirty, branch, source_directory, detect_branch, and/or auto_detect. Automatic branch discovery remains opt-in through detect_branch=True (or RAINDROP_GIT_DETECT_BRANCH=true).
For an ordinary in-process application, the process working directory is treated as the application-under-test checkout. A remote, coding, workflow, or observer process must not rely on its own checkout: pass app_git=False, provide explicit revision values, or set source_directory to the actual application checkout. Canonical per-operation properties remain authoritative. When supplying client=, configure app_git while constructing that Raindrop client; the supplied client is authoritative and the wrapper's app_git argument does not reconfigure it.
Release order is deliberate: first publish the base SDK feature, then publish the wrapper feature release with its minimum dependency coordinated to that base release. The existing raindrop-ai lower bound remains compatible, but application Git metadata is unavailable on an older core and must not be claimed complete until the base is upgraded. Until coordination assigns a released version, the wrapper checks for an explicit base app_git parameter and omits the option when unsupported. Explicit non-default configuration is debug-logged and omitted. Unsupported app_git is determined by signature inspection before construction, not by retrying initialization after a TypeError; Git configuration adds no initialization attempts and does not change any existing framework-specific initialization fallback.
Testing
cd packages/pydantic-ai-python
pip install -e ".[dev]"
python -m pytest tests/ -v # unit tests (no external services)
End-to-end behavior is verified by the cross-SDK conformance harness. This
package ships a thin conformance driver at
conformance/driver.py that maps the shared scenario
corpus onto the wrapper's public API; known gaps are tracked as ticket-linked
entries in conformance/failures.txt. The fault
lane runs on every PR touching packages/*-python/**
(.github/workflows/conformance-wrappers-python.yml) against a local capture
server; the prod lane verifies delivery by reading back through the public
Query API. The harness is
pinned by commit SHA (HARNESS_REF). See the harness docs:
HOW-IT-WORKS ·
AGENTS ·
README.
Full Documentation
See Raindrop Pydantic AI Integration Docs for full documentation.
License
MIT
Metadata
Release files for raindrop-pydantic-ai 0.0.12
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| raindrop_pydantic_ai-0.0.12.tar.gz | 44.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| raindrop_pydantic_ai-0.0.12-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 65.1 kB
Release files / raindrop_pydantic_ai-0.0.12.tar.gz
| Download URL | raindrop_pydantic_ai-0.0.12.tar.gz |
|---|---|
| Size | 44.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
17dad0d6c9b6262841ac60e338b761622ec92873241062a43c6edd9287c4beff
|
|
BLAKE2b-256 checksum How to use checksums |
1444c0cb5be9efc50505ff92c3f99e70d9da7caf41ccec3f7a55aa3ba4ae3d74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Release files / raindrop_pydantic_ai-0.0.12-py3-none-any.whl
| Download URL | raindrop_pydantic_ai-0.0.12-py3-none-any.whl |
|---|---|
| Size | 20.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f4aba4d368f3d749d09ca283eab4474cfa0021543cfe412e3a82f25825fc1f8a
|
|
BLAKE2b-256 checksum How to use checksums |
9bec968a221e71631388e984ee94e7d067ee1ca5f36cf3be143b15a4c99be1b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|