Installation
Quick Start
What Gets Traced
The LangChain integration automatically captures:- LLM calls — model name, input messages, output text, token usage (prompt/completion/total), finish reason
- Tool calls — tool name, input arguments, output, duration (via
interaction.track_tool()spans) - Chains — chain execution with nested spans
- Retrievers — query text and document count
- Agent actions — tool selection and execution
- Errors — captured with error status on the span
- Tags and metadata — LangChain tags and metadata are forwarded to Raindrop event properties
- Extended token categories — cached tokens (
ai.usage.cached_tokens) and reasoning tokens (ai.usage.thoughts_tokens) when available from the provider (e.g. OpenAI) - Finish reason — captured as
ai.finish_reasonin event properties (e.g."stop","length")
Configuration
Event name
Every event this integration ships carries an event name (theevent field in
the dashboard). It defaults to ai_generation. Set eventName (event_name in
Python) to label LangChain traffic — e.g. distinguish a support agent from a
billing agent:
Projects
Route events to a specific project by passing its slug asprojectId (project_id in Python):
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. Single-project orgs need nothing new.
Multiple projects in one process
Available inraindrop-ai>=0.0.56. When one service runs several LangChain
agents that should report to different projects, create one
RaindropLangchain wrapper per project. Each wrapper owns its own
raindrop.Raindrop client, so the two route independently — there is no shared
module-level state:
raindrop.Raindrop yourself and pass it via client=:
Using with LangGraph
The Raindrop handler works with LangGraph out of the box. It automatically filters LangGraph-internal chain events (graph executor,__start__, __end__, channel nodes)
and deduplicates LLM callbacks that LangGraph may fire multiple times.
Pass the handler both at graph invocation time and inside your LLM node:
LangGraph Best Practices
- Pass callbacks to the model, not the graph — use
callbacks: [raindrop.handler]inside your LLM node function only. Do NOT pass callbacks tograph.invoke()— LangGraph’s internal callback propagation causes duplicate events and noisy chain spans when callbacks are on the graph level. - Create a new handler per request in server environments to avoid state collisions between concurrent graph executions.
- LangGraph-internal filtering is on by default — the
filterLangGraphInternalsoption (default:true) skips noisy internal chain events from the graph executor and node wrappers. Set tofalseif you want full visibility into LangGraph internals.
Using with LangSmith
Raindrop and LangSmith can coexist — both use LangChain’s callback system and receive the same events independently. There is no conflict, but you should be aware of the following:- Both tracers are active simultaneously when
LANGSMITH_TRACING=trueis set. Each builds its own trace tree from the same callback events. This is safe but means LLM calls are traced twice (once by each system). - To use Raindrop only, disable LangSmith tracing:
- To use both, no changes needed. Both handlers receive callbacks and ship data independently. Raindrop traces appear in the Raindrop dashboard; LangSmith traces appear in LangSmith.
- Performance: having two tracers adds minimal overhead since both operate asynchronously and don’t block the LLM pipeline.
Usage with Chains
The same handler works with plain LangChain chains:Passing Tags and Metadata
LangChain tags and metadata are forwarded to Raindrop event properties:Identify Users
Associate events with a user identity after initialization:Track Signals
Send feedback, edits, or custom signals tied to a specific event:Tool catalog capture (ai.prompt.tools)
TypeScript. Every model span (llm) records the tools LangChain handed the provider for that call as ai.prompt.tools: one JSON document per tool with name, description and the full JSON Schema as inputSchema. The list comes from the callback’s invocation_params.tools (or legacy functions), so bindTools() / bind({ tools }) on ChatOpenAI, ChatAnthropic and similar models is captured automatically; provider server tools (Anthropic web_search_*, …) are recorded as provider-defined. When the model reports no list the attribute is absent; pass tools on createRaindropLangChain(...) to record a catalog explicitly (tools: [] records an empty one).
Python. raindrop-langchain ≥ 0.0.13 reads the list from the callback itself: RaindropCallbackHandler.on_chat_model_start / on_llm_start receive invocation_params["tools"] (or legacy functions), the list bind_tools() / bind(tools=...) put on the request, and the handler binds it until on_llm_end / on_llm_error, so the model span the OpenLLMetry langchain instrumentation starts inside the call carries ai.prompt.tools with the JSON Schema intact. Capture is exact; a call with neither key records []; sync, async and streaming calls are covered. The instrumentation writes the same list to gen_ai.tool.definitions (OpenLLMetry ≥ 0.62 no longer emits llm.request.functions.*), which raindrop-ai ≥ 0.0.72 also rebuilds from; the wrapper does not depend on that, and its bound list takes precedence when both are present. Pass tools= on RaindropLangchain(...), RaindropCallbackHandler(...) or create_raindrop_langchain(...) as the default, or per call through config={"metadata": {"raindrop_tools": [...]}} (stripped from the event properties); the per-call value replaces the default, either replaces the inferred list, and tools=[] records an empty catalog. Follows TRACELOOP_TRACE_CONTENT; needs raindrop-ai ≥ 0.0.70 and a model span: RaindropLangchain(...) builds its client with auto_instrument=False, so pass client=Raindrop(..., tracing_enabled=True, instruments={Instruments.LANGCHAIN}) or there is no span to carry the attribute. See Tool catalog capture for the attribute format, the absent / [] semantics and the override shapes accepted.
Application Git metadata (Python)
RaindropLangchain(...) and create_raindrop_langchain(...) 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. RaindropCallbackHandler(...) is unchanged and does not accept this option.
Flushing and Shutdown
Always callflush() before your process exits to ensure all telemetry is shipped: